The usual ROP recipe needs a libc leak: dump a GOT entry, subtract an offset,
compute system. But what if the binary gives you no convenient leak primitive —
no puts(puts@got) gambit, nothing? On a lazy-bound binary you don't need one.
The dynamic linker already knows where libc is, and it will resolve a symbol by
name on demand. ret2dlresolve makes it resolve system
for you.
- arch
- amd64-64-little
- leak
- none available
- premise
- lazy binding is on -> _dl_runtime_resolve is reachable
- RELROpartial
- PIEoff
how lazy binding actually works#
The first time you call foo@plt, its GOT slot still points back into the PLT,
which pushes a relocation index and jumps to the resolver:
|
Every input to that walk is just memory at a computed offset. Nothing is
signed, nothing is authenticated — the resolver trusts that reloc_index lands
on a real Elf64_Rela. So point it somewhere you control.
forge the three structures#
In a writable region with a known address — on a no-PIE binary the .bss is
fixed — lay down a fake relocation, a fake symbol, and the string "system",
then pass an index that makes JMPREL + index*24 land on your fake Rela:
Then ROP: set rdi = &"/bin/sh", push your crafted index, and jump to the PLT
resolver stub (PLT[0]). The linker reads your Rela → your Sym →
"system", looks it up in the real libc wherever ASLR put it, and calls it:
|
shell, and the GOT never had system in it
No leak, no libc version guess — the linker did the lookup. system("/bin/sh"):
segfault{the_linker_resolved_it_for_me}
when it works, and when it doesn't#
- Lazy binding must be on. Full RELRO resolves every symbol at startup and
read-onlys the GOT, so
_dl_runtime_resolveis never used at runtime — this whole technique is dead. Partial RELRO / no RELRO keep it alive. - You need a known writable address to stage the fakes. No-PIE hands you
.bss; with PIE you need a leak first, which somewhat defeats the point. - Modern glibc bounds-checks the symbol index against the real
.dynsymsize, so the giant-index trick needs care (keep the fake structures close to the real tables, or use the version that stays in bounds). pwntools picks a working layout for the common cases; exotic ones you craft by hand.
ret2dlresolve is the answer to "I have a ROP chain but no leak." You're not beating ASLR — you're asking the one component that's exempt from it, the dynamic linker, to hand you the address it already knows.