Skip to content

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 ret instruction 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 the RDI register.
  • Then you return into system with RDI set 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:
    ROPgadget --binary ./vuln | grep "pop rdi; ret"
    

Then a simple ROP chain to call:

system("/bin/sh");

would be:

  • pop rdi; ret → put address of "/bin/sh" into rdi.
  • ret to system. So payload stack could look like:
    "A" * offset
    + address_of_pop_rdi_ret
    + address_of_bin_sh_string
    + address_of_system
    

When the function returns, it:

  1. Goes to pop rdi; ret (sets rdi = "/bin/sh").
  2. Returns to systemsystem("/bin/sh"), giving you a shell.