ret2libc

ret2libc

TL;DR: With NX on, redirect execution into existing libc code (system, execve, one-gadgets) by overwriting a return address and arranging arguments — the original code-reuse attack.

What it is

Return-to-libc reuses already-mapped executable bytes in libc.so instead of injecting shellcode. The classical target is system("/bin/sh"): set up the calling convention, return to system’s PLT or libc address, and inherit a shell. On x86_64 the first argument must live in rdi, which usually means chaining a pop rdi; ret gadget — making ret2libc a degenerate one-call ROP chain.

Preconditions / where it applies

  • Stack control sufficient to overwrite saved RIP / chain ≥2 quads.
  • NX/DEP enabled, ASLR present but defeatable by a libc leak or by partial overwrite if PIE is off.
  • glibc available in the address space (default for dynamically linked Linux binaries).

Technique

1. Leak libc. Print a libc pointer (PLT/GOT entry via puts(puts@got), format-string %s, or env-var leak) and subtract the symbol offset to recover libc_base.

2. Resolve symbols offline.

1
2
libc.symbols['system']        # libc-database / pwntools
libc.search(b'/bin/sh\x00')   # find the string

3. Build the chain (x86_64 example).

1
2
3
4
5
payload  = b'A' * offset
payload += p64(pop_rdi_gadget)
payload += p64(libc_base + bin_sh_offset)
payload += p64(ret_gadget)            # stack align for movaps
payload += p64(libc_base + system_off)

On x86_64 SysV ABI, system calls do_system → execve which executes movaps requiring 16-byte stack alignment; a single ret between pop rdi and system fixes alignment crashes.

4. one_gadget. one_gadget libc.so.6 lists single addresses that execute execve("/bin/sh", 0, 0) directly if constraints (rax==NULL, [rsp+0x50]==NULL, etc.) hold. When constraints are unmet, fall back to the system pattern.

5. Libc-database identifies the target’s libc version from a leaked symbol’s low bytes when you don’t have the binary’s copy.

Detection and defence

  • ASLR + PIE makes libc base unpredictable; without a leak the attack fails.
  • glibc 2.34+ removed __free_hook / __malloc_hook, removing some adjacent ret2libc-style write targets.
  • RELRO (full) marks the GOT read-only after relocation, killing puts@got overwrites.
  • Stack canaries on the leaking frame block trivial overflows from reaching saved RIP.
  • Seccomp filters can block execve even after a successful chain.

References

See also: ret2csu, srop, format-string-bugs.