ROP chains
TL;DR: Stitch short instruction sequences ending in
ret(gadgets) from already-mapped executable modules into a chain that performs an arbitrary computation — the standard primitive for defeating DEP and reachingVirtualProtect-style APIs.
What it is
Return-orientated programming, formalised by Shacham (CCS 2007), turns a stack-write primitive into Turing-complete execution without injecting any new code. Each “gadget” is a small sequence of legitimate instructions ending in ret; by stacking gadget addresses interleaved with data values on the controlled stack, every ret pops the next gadget and resumes execution. Combined with API calls, a ROP chain typically marks an attacker-controlled buffer executable and pivots to shellcode (see dep-bypass).
Preconditions / where it applies
- Stack-write primitive: saved-RIP overwrite, SEH chain hijack, structured pivot
- Non-PIE module or a leak so gadget addresses are known (see aslr-bypass)
- DEP enforced (otherwise just jump to shellcode); CET shadow stack (cet-shadow-stack) absent — CET breaks classic ret chains
Technique
Workflow:
- Find gadgets. ROPgadget, rp++, mona, or pwntools’ ROP module dump every
ret-ended sequence.1 2 3
ROPgadget --binary target.dll --only "pop|ret" rp++ --file=target.exe --rop=4 --unique !mona rop -m *.dll -cp nonull -n
- Plan the API call. x86 stdcall pushes args right-to-left then calls; x86_64 fastcall puts args in rcx/rdx/r8/r9 then
call. PickVirtualProtect,VirtualAlloc, orWriteProcessMemory. - Assemble pad → arg-setting gadgets → API → return-to-shellcode. Use writable BSS/.data for
lpflOldProtect. - Bad-character handling. Eliminate null bytes, newlines, etc. via gadget swaps or encoders — see bad-character-handling.
Skeleton (x86, calling VirtualProtect):
1
2
3
4
5
6
7
8
[ pop_eax_ret ] ; eax = junk (placeholder)
[ &VirtualProtect ] ; pop_eax_ret target
[ ... ; set ebx, ecx, edx, esi, edi to args via pop_X_ret gadgets
[ &jmp_esp / shellcode_addr ] ; return address landing in shellcode
[ shellcode_addr ] ; lpAddress
[ 0x1000 ] ; dwSize
[ 0x40 ] ; PAGE_EXECUTE_READWRITE
[ &writable_dword ] ; lpflOldProtect
Advanced patterns:
- Stack pivot —
xchg eax, esp ; retormov esp, ebp ; retto move the chain into a sprayed heap buffer when stack space is tight - JOP/COP — when shadow stack is on, pivot to jump/call-oriented gadgets that survive backward-edge CFI
- SROP — Linux analogue using sigreturn (see srop)
Detection and defence
- DEP
AlwaysOn, ASLRMandatoryASLR, CFG (control-flow-guard), XFG (xfg), and CET shadow stack (cet-shadow-stack) each break a layer - EAF/IAF (Export Address Filtering) trips on early gadgets that walk the PEB to resolve APIs
- EDR detection:
VirtualProtect/VirtualAlloccallers from non-image memory regions, especially withPAGE_EXECUTE_READWRITE - Build with
/GUARD:CF /CETCOMPAT /HIGHENTROPYVA /DYNAMICBASEand audit non-compliant DLLs withmona modules
References
- Shacham: The Geometry of Innocent Flesh on the Bone (CCS 2007) — original ROP paper
- Corelan ROP tutorials — practical chain construction
- pwntools ROP module — automation
Related: dep-bypass, aslr-bypass, mona-py, cet-shadow-stack