Lukas' Notes

security memory

Definition

Return-Oriented Programming (ROP)

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 1pop rdx ; ret loads 0xb000000f into rdx
  • gadget 2pop rax ; ret loads the address of guard into rax
  • gadget 3mov [rax], rdx ; ret performs the store: *guard = 0xb000000f
  • afterret 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).