SROP gets you every register from the
kernel's restore path. libc ships its own version of that gadget, and you don't
need a syscall to reach it: setcontext. After the malloc hooks died and
House of Apple 2 made "one controlled indirect call" the
common primitive, setcontext is what you point that call at to escalate from a
single jump into total control.
- arch
- amd64-64-little
- have
- one controlled call where you control a pointer register (rdi or rdx)
- want
- rsp + the full register file, i.e. a pivot into ROP
what setcontext restores#
setcontext(ucontext_t *) is the userland twin of rt_sigreturn: it loads the
saved CPU state out of a ucontext_t. The exploitation-relevant slice is a run of
movs partway in — the setcontext+53 gadget on older glibc — that reloads
the general registers, the stack pointer, and the return address straight from the
struct:
|
Point a register at a fake ucontext and one jump to this gadget gives you rsp,
rdi, rsi, rdx, rbx, rbp, r12–r15, and rip. With rsp controlled,
you've pivoted onto a ROP chain; with the arg registers controlled, you can
instead land a single execve or mprotect directly.
the version wrinkle: rdi → rdx#
Then glibc 2.29 reorganised setcontext, and the gadget started reading the
context pointer from rdx instead of rdi (setcontext+61 on those builds):
That matters because your delivery primitive usually controls rdi (a hook
calls hook(rdi=ptr); House of Apple's call passes the FILE in rdi). On 2.29+
you need rdx pointed at the fake context, so you chain a "magic gadget" that
moves a controlled value into rdx first — e.g. a libc mov rdx, [rdi+8]; ... ;call [rdi+...] fragment (the svcudp_reply / getkeyserv_handle gadgets people
catalogue) — then it lands in setcontext with rdx set.
the fake context, and the delivery#
Craft the ucontext with rsp → your ROP chain and rip → a ret (or the first
gadget), then drive it from whatever single call you control:
|
one call, then the machine is yours
The hook called setcontext(rdi), rsp moved onto the ROP chain, and the
chain runs mprotect + shellcode (or straight execve):
segfault{one_call_became_a_whole_rop_chain}
why it's the modern keystone#
- It converts a call into a pivot. Most modern primitives (FSOP, a function
pointer, a leftover hook) give you one indirect call with one controlled
register. setcontext multiplies that into
rsp+ every argument register — the difference between calling a single function and running an arbitrary chain. - It pairs with everything. House of Apple gives the call; setcontext gives the pivot; the chain gives the shell. That triad — leak → controlled call → setcontext → ROP — is the spine of a large fraction of current glibc heap exploits.
- It degrades gracefully across versions as long as you track the rdi→rdx move
and carry the matching
mov rdxgadget.
If SROP is the kernel handing you the register file, setcontext is libc doing the same favour — and because you reach it with an ordinary call rather than a syscall, it slots in wherever your bug already gave you a single jump. That's why "I control one call" is, on a modern libc, almost always enough.