A frustrating shape of bug: you overflow into the saved return address, but the
buffer ends a few bytes later — room for maybe two gadgets, nowhere near a full
ROP chain. The fix is to stop running off this stack. Pivot rsp to a region
you do control completely — a big input buffer, the .bss — and execute the
real chain from there.
- arch
- amd64-64-little
- overflow
- reaches saved RIP + ~16 bytes — not enough for a real chain
- NXon
- PIEoff
the pivot gadgets#
Pivoting means loading rsp from something you control. The common gadgets, in
rough order of how often they show up:
leave; ret—leaveismov rsp, rbp ; pop rbp. If you control the savedrbp, this is a pivot: the epilogue setsrspto your value. The workhorse.pop rsp; ret— direct, rare, lovely when it exists.xchg rax, rsp; ret— pivot throughrax(pair with a gadget that loadsrax).add rsp, 0xNN; ret— not a full pivot, but walksrspforward over junk into a chain you placed just ahead.
stack migration with leave;ret#
The classic move uses leave; ret twice. Read your full chain into a known
location (here, .bss), set the saved rbp to fake_stack - 8, and return into
a leave; ret. The function's own epilogue does the rest:
pwntools wraps the whole pattern — stage the long chain somewhere writable, then migrate:
|
from 16 bytes of room to a full chain
The cramped overflow only had to fit read + migrate; the real work runs
off .bss:
segfault{rsp_goes_where_i_say}
the gotchas that bite#
- You need a writable address for the fake stack. No-PIE gives you
.bssfree; with PIE, leak first. A leaked stack address works too — pivot onto your own input. leave; retclobbersrbp. After the pivotrbpis whatever you popped; if later gadgets need a sane frame, set it deliberately.- 16-byte alignment.
system/movapswill fault on a misalignedrsp; drop a bareretinto the chain to realign, exactly as you would in any libc call. - Don't overlap. If your fake stack sits where the current chain is still
executing, the
readcan stomp the gadget you're about to return into. Keep the staging buffer clear of the live chain.
Stack pivoting is the move that decouples "where the bug writes" from "where the
chain runs." Once rsp points at memory you own, the size of the original
overflow stops mattering — you have as much ROP as you can write.