What is Return-Oriented Programming (ROP) and why does it bypass DEP/NX?
ROP chains together "gadgets" — short snippets of the program's own already-executable code ending in ret — so it never injects new code, sidestepping DEP/NX (No-eXecute memory protection) which only blocks executing data.
* A ROP chain reuses existing code: each gadget ends in ret, popping the next gadget address — no new code is injected, so DEP/NX is sidestepped. *
Problem for attackers: DEP/NX prevents executing code on the stack. Injected shellcode won't run.
ROP solution: Instead of injecting new code, reuse existing code that's already marked executable.
How ROP works:
- Find "gadgets" - small sequences of instructions ending in
ret(e.g.,pop rdi; ret) - Chain gadgets by placing their addresses on the stack
- Each
retpops the next gadget address and jumps to it - Gadgets combine to perform arbitrary operations
Example chain to call execve("/bin/sh"):
Stack: [pop rdi; ret] ["/bin/sh" addr] [pop rsi; ret] [0] [execve addr]
Each gadget executes, then ret transfers to the next.
Why DEP doesn't stop it: ROP uses code from the program itself or libc - already executable. No new code is injected.
Defense: ASLR randomizes gadget locations. Without knowing addresses, attacker can't build the chain.
Tip: Think of ROP like writing a ransom note by cutting out individual words from a newspaper — you don't write new text, you rearrange existing text to say what you want.
Go deeper:
Return-oriented programming (Wikipedia) — gadgets, evolution from return-into-libc, defenses (ASLR, PAC/BTI), Shacham 2007.