Return-to-libc is a subset of ROP in which the attacker chains together libc functions rather than arbitrary gadgets. The target functions are high-level routines already present in the standard C library — system, open, read, write — which let a short chain perform an effect (run a shell, open a file, exfiltrate data) that would otherwise need many small gadgets.
16-byte Alignment Pitfall
GCC assumes the stack is 16-byte aligned on calls
GCC-generated code assumes that on entry to a function the stack pointer is 16-byte aligned — that rsp ends in a 0 nibble. Some SSE instructions issued inside libc enforce this: the movaps (AVX2) instruction used inside system segfaults if rsp is misaligned.
A naive ret2libc chain overwrites the saved rip and pushes an odd number of 8-byte words afterwards, so by the time system is reached rsp ends in 8 rather than 0 — and system crashes before doing anything useful. The misalignment comes from the chain’s own arithmetic, not from the bug.
Fix
Add a ret to slide the stack by 8 bytes
Insert a lone ret gadget into the chain so that one extra add rsp, 8 happens before system is reached, sliding the stack back into 16-byte alignment:
& (pop rdi ; ret) ← load rdi with the string address & "/bin/sh" ← argument for system & (ret) ← alignment gadget & system ← system("/bin/sh") with rsp aligned
The fix is mechanical — count the pops in the chain, and if the count is odd, prepend or interpose one more ret to make it even.
Example
system("/bin/sh") via ret2libc
With libc addresses known (from a leak — see ASLR), the shortest shell-spawning chain is:
a pop rdi ; ret gadget to place "/bin/sh" in rdi;
the address of the libc string "/bin/sh" itself;
a ret gadget to repair alignment;
the address of system.
system("/bin/sh") runs under the program’s privileges, giving the attacker a shell. Contrast this with the shellcode approach — same goal, fewer gadgets, no code executed on the stack.