Chapter 37 Key Takeaways: Return-Oriented Programming and Modern Exploitation

  1. ROP exists because NX/DEP prevented shellcode injection: NX marked the stack non-executable, so attackers could not run injected code there. ROP's insight: don't inject new code, chain together existing code fragments (gadgets) already in the executable text sections, which must remain executable.

  2. A gadget is a short instruction sequence ending in ret: examples: pop rdi; ret, xor rax, rax; ret, mov [rdi], rax; ret. The ret at the end is what chains gadgets together by popping the next gadget's address from the stack.

  3. The ROP chain is a forged stack: a sequence of gadget addresses (and their data arguments) placed on the stack. When the vulnerable function executes ret, it pops the first gadget address and execution begins. Each gadget's ret pops the next address. The stack pointer (RSP) walks through the forged chain like a program counter.

  4. x86-64's variable-length instructions create "unintended gadgets": since instructions can be 1-15 bytes and are not aligned, any byte in the code section can be the start of a valid instruction sequence. A single byte in the middle of one instruction may begin an entirely different instruction sequence ending in ret. This provides far more gadgets than intentional code sequences alone.

  5. ROPgadget and ropper find gadgets automatically: ROPgadget --binary ./program --rop | grep "pop rdi" finds all sequences ending in ret that contain pop rdi. Large binaries like libc.so.6 contain thousands of useful gadgets.

  6. ret2libc is the simpler special case: return directly to system() in libc with /bin/sh as the argument — no need to chain many gadgets. Requires knowing libc's address (broken by ASLR), or an information leak to find it.

  7. ret2plt + information leak defeats ASLR: call a PLT stub (like puts@PLT) with a GOT address as argument to print a libc runtime address. Calculate libc_base = leaked_address - known_symbol_offset. This breaks ASLR for the entire libc, enabling ret2libc.

  8. SROP achieves full register control with a single gadget (syscall; ret): a forged sigcontext structure on the stack and the sigreturn syscall (number 15) sets ALL registers simultaneously to attacker-chosen values, including RIP and RSP.

  9. JOP uses gadgets ending in jmp instead of ret: relevant because CET SHSTK protects ret but not jmp. CET IBT addresses JOP by requiring ENDBR64 at indirect jump targets.

  10. CET SHSTK defeats ROP at the fundamental level: the shadow stack stores return addresses in hardware-protected pages that user-space cannot write to. When ret executes, the CPU compares the regular stack return address against the shadow stack. A forged ROP chain never matched the shadow stack (which contains legitimate CALL-pushed addresses), so every ret in the chain triggers #CP exception.

  11. CET IBT marks valid indirect call targets with ENDBR64 (encoding F3 0F 1E FA): on non-CET CPUs this is a harmless REP NOP. On CET CPUs, indirect calls that land anywhere without ENDBR64 raise #CP. This drastically reduces the usable gadget set for JOP and function-pointer attacks.

  12. Blind ROP works against fork()-based servers because forked children share the parent's address layout. Gradual probing via crash behavior can map the address space without re-randomization. Defense: re-exec (not just re-fork) to get a new random layout per connection.

  13. ROP is formally Turing complete: Shacham (2007) proved that any computation expressible in assembly can be expressed as a ROP chain, given sufficient gadget diversity. NX/DEP cannot prevent ROP computation; it only changes the form. This motivated CFI and CET.

  14. Post-SHSTK attack surface shifts to non-return-address targets: data-only attacks (corrupting UID, flags, sizes without redirecting code), heap function pointer corruption (vtables, callbacks — which SHSTK does not protect), and logic vulnerabilities. The exploitation bar is significantly higher but not eliminated.

  15. Defense in depth against ROP requires: ASLR + PIE (randomize addresses), Full RELRO (prevent GOT overwrites), no information leaks (prevent ASLR bypass), CET SHSTK (defeat return-address forgery), CET IBT (defeat JOP and function-pointer corruption). Each layer addresses a different pathway; all are needed for a robust defense.