Return Oriented Programming (ROP)¶
8. ROP and “ret to libc” – high level overview¶
Modern systems often mark the stack as non-executable(NX/DEP). This means you can’t just put shellcode on the stack and jump to it.
To get around that, attackers use Return Oriented Programming, or ROP.
The idea behind ROP is:
- Instead of injecting new code, you reuse small instruction sequences that already exist in the program’s code or in shared libraries.
- Each of these small sequences ends with a
retinstruction and is called a gadget. - By carefully overwriting the stack with a sequence of gadget addresses, you can build a chain of tiny operations that collectively do something powerful. One common pattern is a ret to libc attack:
- Libc is the standard C library, which contains useful functions such as
system. - You find gadgets that let you set up arguments, like a gadget that does “
pop rdi, ret”, which lets you put an address into theRDIregister. - Then you return into
systemwithRDIset to the address of the string “/bin/sh”. - The result: the program calls
system("/bin/sh"), giving you a shell.
You don’t need to be able to write new code; you just reuse existing code in clever ways by chaining return addresses on the stack.
Example gadgets you often look for:
pop rdi; ret→ set up the first function argument (on x86-64 ABI).pop rsi; ret→ second argument.pop rdx; ret→ third argument. Tool from slides: ROPGadget:
Then a simple ROP chain to call:
would be:
pop rdi; ret→ put address of"/bin/sh"intordi.rettosystem. So payload stack could look like:
When the function returns, it:
- Goes to
pop rdi; ret(setsrdi = "/bin/sh"). - Returns to
system→system("/bin/sh"), giving you a shell.