Chapter 37 Key Takeaways: Return-Oriented Programming and Modern Exploitation
-
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.
-
A gadget is a short instruction sequence ending in
ret: examples:pop rdi; ret,xor rax, rax; ret,mov [rdi], rax; ret. Theretat the end is what chains gadgets together by popping the next gadget's address from the stack. -
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'sretpops the next address. The stack pointer (RSP) walks through the forged chain like a program counter. -
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. -
ROPgadget and ropper find gadgets automatically:
ROPgadget --binary ./program --rop | grep "pop rdi"finds all sequences ending inretthat containpop rdi. Large binaries likelibc.so.6contain thousands of useful gadgets. -
ret2libc is the simpler special case: return directly to
system()in libc with/bin/shas the argument — no need to chain many gadgets. Requires knowing libc's address (broken by ASLR), or an information leak to find it. -
ret2plt + information leak defeats ASLR: call a PLT stub (like
puts@PLT) with a GOT address as argument to print a libc runtime address. Calculatelibc_base = leaked_address - known_symbol_offset. This breaks ASLR for the entire libc, enabling ret2libc. -
SROP achieves full register control with a single gadget (
syscall; ret): a forgedsigcontextstructure on the stack and thesigreturnsyscall (number 15) sets ALL registers simultaneously to attacker-chosen values, including RIP and RSP. -
JOP uses gadgets ending in
jmpinstead ofret: relevant because CET SHSTK protectsretbut notjmp. CET IBT addresses JOP by requiringENDBR64at indirect jump targets. -
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
retexecutes, 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 everyretin the chain triggers#CPexception. -
CET IBT marks valid indirect call targets with
ENDBR64(encodingF3 0F 1E FA): on non-CET CPUs this is a harmlessREP NOP. On CET CPUs, indirect calls that land anywhere withoutENDBR64raise#CP. This drastically reduces the usable gadget set for JOP and function-pointer attacks. -
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.
-
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.
-
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.
-
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.