You have an arbitrary jump — a hook to overwrite, a saved return address, a
function pointer — but not enough room or leaks for a clean
system("/bin/sh") call. A one-gadget is the shortcut: a
single libc address that, when executed, spawns a shell. No argument setup, no
chain. The catch is that each one only fires if the surrounding register/stack
state already satisfies its constraints.
- arch
- amd64-64-little
- have
- one arbitrary jump (a hook / saved RIP) + a libc leak
- want
- a shell without building a full ROP chain
what one_gadget finds#
These addresses live inside libc's do_system/exec_comm path, where /bin/sh
is already staged — but reaching execve cleanly needs the argument registers to
be NULL. one_gadget enumerates them and prints the constraints that must
hold at the jump:
The address is the easy part — libc_base + 0x4f302. The constraint is the whole
game: at the instant control reaches it, the listed register or stack slot has to
already be zero.
picking the one that fits your context#
You don't get to choose your register state — the call site that hands you control
does. The trick is knowing what's NULL there:
- right after
free(p)— on many glibc buildsrax/some args are predictable; the[rsp+0x40] == NULLvariants often hold; - overwriting
__free_hook(pre-2.34) —free(p)callshook(p), sordi=p; a gadget that doesn't care aboutrdiand only needs a NULL stack slot is ideal; - a saved return address — you control the stack around
rsp, so a[rsp+0x40] == NULLconstraint is something you can arrange by placing a zero there.
when one fires, it's a shell in one write
No args, no chain — the gadget did execve("/bin/sh", …) because the one slot
it cared about was zero:
segfault{one_address_one_shell}
when none of them match#
It happens — every gadget needs a slot you can't zero. Options, roughly in order:
- Arrange the constraint. Pivot the stack so the required
[rsp+X]lands on memory you control, then writeNULLthere. Most "impossible" one_gadgets become possible after a small pivot. - Pick a different jump site. The same one_gadget that fails from
__free_hookmay satisfy its constraint from a saved return address, where you own the stack. - Fall back to the long way. No matching gadget → set up
rdi = "/bin/sh"and callsystemwith a real (if longer) chain. one_gadget is a convenience, not a dependency.
The mental model: system("/bin/sh") is the reliable workhorse; a one_gadget is
the same destination reached in a single write, if the calling context already
did the argument setup for you. Read the constraints first — they tell you
instantly whether this shortcut is open from where you're standing.