Return-Oriented Programming (ROP) is the technique for performing arbitrary computation despite NX by reusing existing instruction snippets in the target binary’s own code. Instead of injecting a payload and executing it, the attacker chains together short fragments of code that the binary already contains, ending each in a ret so that the next snippet’s address is pulled off the stack by the same return mechanism.
How It Works
ret as the dispatcher
The ret instruction copies the top of the stack into the instruction pointer:
ret ≡ mov rip, [rsp] ; add rsp, 8
So if the saved rip slot has been overwritten to point at a gadget, the gadget’s final ret pops the next gadget’s address off the stack, runs it, and its ret pops the next, and so on. The stack becomes a script of addresses the attacker wrote during the overflow; the gadgets are read-only code already in the binary. Each gadget’s effect on rsp (pops, pushes, arithmetic) must be accounted for when laying out the chain.
Gadget
What a gadget is
A ROP gadget is a short sequence of instructions, usually followed by a ret. Gadgets are mined from the binary by scanning for ret (0xc3) and walking backwards; on x86-64 execution can start mid-instruction, so gadgets even exist where the disassembler shows none. Typical gadgets:
pop rdi ; ret
pop rax ; pop rbx ; ret
xor rax, rax ; ret
mov qword [rsi], rax ; ret
ROP Chain
What a chain is
A ROP chain is a sequence of ROP gadgets laid out on the stack to perform a single task — usually spawning a shell, as a shellcode payload would. The chain emulates shellcode without executing a single attacker-written byte.
Worked Example
Writing 0xb000000f to a guard variable via ROP
The target binary has a guard that’s checked after readstuff returns. The chain writes the “Win” value into guard using three gadgets found statically in the binary.
stack after the overflow: 0xf348 & (pop rdx ; ret) ← overwritten saved rip 0xf350 0xb000000f ← value popped into rdx 0xf358 & (pop rax ; ret) 0xf360 0x4c00f0 ← address of `guard` popped into rax 0xf368 & (mov [rax], rdx ; ret)
gadget 1 — pop rdx ; ret loads 0xb000000f into rdx
gadget 2 — pop rax ; ret loads the address of guard into rax
gadget 3 — mov [rax], rdx ; ret performs the store: *guard = 0xb000000f
after — ret falls through to main+13, where the if (guard == 0xb000000f) now fires and prints “Win”
The chain is doing what a *guard = 0xb000000f; statement would do, using only gadgets that the binary happened to already contain.
Expanding the Gadget Set
glibc is huge, so the gadget set is richer
A binary on its own may not contain all the gadgets a chain needs. Attaching glibc remedies this: the C library is large enough that almost any needed gadget exists somewhere inside it, giving attackers “almost limitless options” once library code is reachable. This motivates both the ret2libc subclass (deliberately chaining libc functions rather than gadgets) and the ASLR mitigation (which hides library addresses so those gadgets can’t be aimed at).