Return-Oriented Programming Primer: Gadget Hunting and ROP Chain Construction
Cybersecurity
How to build ROP chains from scratch: gadget enumeration with ROPgadget, chain assembly in pwntools, syscall ROP, and ret2libc on modern hardened binaries.
By Arjun Raghavan, Security & Systems Lead, BIPI · December 26, 2024 · 10 min read
ROP came from the realization that NX killed shellcode but not control flow. With a stack overflow, instead of executing your bytes, you execute existing instruction sequences ending in ret, stitched together via the stack itself. Every ret pops the next gadget address.
Anatomy of a Gadget
A gadget is a short sequence of instructions ending in ret, jmp reg, or call reg. Classic example: pop rdi ; ret. Place a value on the stack, the pop loads it into RDI, then ret jumps to the next stack entry. Chain enough of these and you have a Turing-complete computation made entirely of existing code.
Finding Gadgets
- ROPgadget --binary target dumps every gadget with its offset, filter with --only 'pop|ret'
- ropper offers more advanced search, including JOP and COP gadgets and gadget chains
- pwntools ROP(elf).search(regs=['rdi']) finds the gadget you need by required register
- On ASLR-on PIE binaries, gadgets only useful after a code-base leak
- If the binary is small, grab gadgets from libc instead, with libc base leaked separately
Building a ret2libc Chain
Classic chain: leak puts@GOT to get libc base, calculate system address, place pop rdi ; ret then address of /bin/sh string then system. pwntools makes this trivial: rop = ROP(libc); rop.system(next(libc.search(b'/bin/sh'))); payload = padding + rop.chain().
Syscall ROP
When libc is stripped or untrusted, build a syscall chain directly. On x86_64, you need pop rax ; ret (syscall number), pop rdi ; ret, pop rsi ; ret, pop rdx ; ret, and a syscall ; ret gadget. Call execve('/bin/sh', 0, 0) with rax=59. The 'binary contains syscall gadget' assumption usually holds for static binaries and most musl-linked stuff.
If you have a stack overflow and the binary is static, you have everything you need. Static binaries are a gift to exploit developers.
Stack Pivoting
Sometimes you have a small overflow, just enough to redirect to a larger buffer elsewhere. Pivot the stack with leave ; ret (mov rsp, rbp ; pop rbp ; ret) or xchg eax, esp. Once RSP points at attacker-controlled memory, your full ROP chain runs from there.
SROP: Sigreturn ROP
If gadgets are scarce, abuse sigreturn (syscall 15 on x86_64). It pops a full ucontext from the stack into every register including RIP. One gadget plus a fake ucontext gives you full register control. Useful in seccomp-restricted or minimal-binary scenarios.
Chain Construction in pwntools
- p = process('./vuln'); elf = ELF('./vuln'); libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
- Leak: rop = ROP(elf); rop.puts(elf.got['puts']); rop.main(); send and parse leak
- libc.address = leak - libc.symbols['puts']
- rop2 = ROP(libc); rop2.system(next(libc.search(b'/bin/sh'))); rop2.exit(0)
- Payload: padding + rop2.chain(), interactive shell pops
Mitigations and Bypass
- Intel CET shadow stack: every ret validated against shadow copy, ROP fails. Bypass: JOP/COP within ENDBR landing pads
- Microsoft CFG: indirect calls validated against allowlist of targets. Bypass: find call-site whose target table is broad
- Clang CFI: type-checked indirect calls, hard but not impossible with type confusion
- ROP detection in EDR by stack-walking and looking for short call-less basic blocks, defeated by adding decoy CALLs (Call-Oriented Programming)
Recent Use in CVEs
- CVE-2024-6387 regreSSHion sshd exploit used ROP to call mprotect then jump to staged shellcode
- Pwn2Own 2024 entries against routers and printers leaned heavily on ROP-into-busybox techniques
- Browser pwns continue to use ROP plus JIT-spray-derived data for sandbox escapes
Practice Targets
- pwn.college dojos cover ROP from baby chains to SROP
- ROP Emporium has eight progressively harder challenges, free
- HackTheBox and TryHackMe pwn machines for realistic targets
- Real CTF write-ups from PlaidCTF and DEFCON Quals every year
ROP turned NX into a speed bump. The countermove (CET, CFG, CFI) is winning slowly, but for any binary without shadow stacks, ROP remains the standard payload.
Read more field notes, explore our services, or get in touch at info@bipi.in. Privacy Policy · Terms.