|
| 1 | +# GNU obstack function-pointer hijack |
| 2 | + |
| 3 | +{{#include ../../banners/hacktricks-training.md}} |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +GNU obstacks embed allocator state together with two indirect call targets: |
| 8 | + |
| 9 | +- `chunkfun` (offset `+0x38`) with signature `void *(*chunkfun)(void *, size_t)` |
| 10 | +- `freefun` (offset `+0x40`) with signature `void (*freefun)(void *, void *)` |
| 11 | +- `extra_arg` and a `use_extra_arg` flag select whether `_obstack_newchunk` calls `chunkfun(new_size)` or `chunkfun(extra_arg, new_size)` |
| 12 | + |
| 13 | +If an attacker can corrupt an application-owned `struct obstack *` or its fields, the next growth of the obstack (when `next_free == chunk_limit`) triggers an indirect call through `chunkfun`, enabling code execution primitives. |
| 14 | + |
| 15 | +## Primitive: size_t desync → 0-byte allocation → pointer OOB write |
| 16 | + |
| 17 | +A common bug pattern is using a **32-bit register** to compute `sizeof(ptr) * count` while storing the logical length in a 64-bit `size_t`. |
| 18 | + |
| 19 | +- Example: `elements = obstack_alloc(obs, sizeof(void *) * size);` is compiled as `SHL EAX,0x3` for `size << 3`. |
| 20 | +- With `size = 0x20000000` and `sizeof(void *) = 8`, the multiplication wraps to `0x0` in 32-bit, so the pointer array is **0 bytes**, but the recorded `size` remains `0x20000000`. |
| 21 | +- Subsequent `elements[curr++] = ptr;` writes perform **8-byte OOB pointer stores** into adjacent heap objects, giving a controlled cross-object overwrite primitive. |
| 22 | + |
| 23 | +## Leaking libc via `obstack.chunkfun` |
| 24 | + |
| 25 | +1. Place two heap objects adjacent (e.g., two stacks built with separate obstacks). |
| 26 | +2. Use the pointer-array OOB write from object A to overwrite object B’s `elements` pointer so that a `pop`/read from B dereferences an address inside object A’s obstack. |
| 27 | +3. Read `chunkfun` (`malloc` by default) at offset `0x38` to disclose a libc function pointer, then compute `libc_base = leak - malloc_offset` and derive other symbols (e.g., `system`, `"/bin/sh"`). |
| 28 | + |
| 29 | +## Hijacking `chunkfun` with a fake obstack |
| 30 | + |
| 31 | +Overwrite a victim’s stored `struct obstack *` to point at attacker-controlled data that mimics the obstack header. Minimal fields needed: |
| 32 | + |
| 33 | +- `next_free == chunk_limit` to force `_obstack_newchunk` on next push |
| 34 | +- `chunkfun = system_addr` |
| 35 | +- `extra_arg = binsh_addr`, `use_extra_arg = 1` to select the two-argument call form |
| 36 | + |
| 37 | +Then trigger an allocation on the victim obstack to execute `system("/bin/sh")` through the indirect call. |
| 38 | + |
| 39 | +Example fake obstack layout (glibc 2.42 offsets): |
| 40 | + |
| 41 | +```python |
| 42 | +fake = b"" |
| 43 | +fake += p64(0x1000) # chunk_size |
| 44 | +fake += p64(heap_leak) # chunk |
| 45 | +fake += p64(heap_leak) # object_base |
| 46 | +fake += p64(heap_leak) # next_free == chunk_limit |
| 47 | +fake += p64(heap_leak) # chunk_limit |
| 48 | +fake += p64(0xF) # alignment_mask |
| 49 | +fake += p64(0) # temp |
| 50 | +fake += p64(system_addr) # chunkfun |
| 51 | +fake += p64(0) # freefun |
| 52 | +fake += p64(binsh_addr) # extra_arg |
| 53 | +fake += p64(1) # use_extra_arg flag set |
| 54 | +``` |
| 55 | + |
| 56 | +## Attack recipe |
| 57 | + |
| 58 | +1. **Trigger size wrap** to create a 0-byte pointer array with a huge logical length. |
| 59 | +2. **Groom adjacency** so an OOB pointer store reaches a neighbor object containing an obstack pointer. |
| 60 | +3. **Leak libc** by redirecting a victim pointer to the neighbor obstack’s `chunkfun` and reading the function pointer. |
| 61 | +4. **Forge obstack** data with controlled `chunkfun`/`extra_arg` and force `_obstack_newchunk` to land in the forged header, yielding a function-pointer call of the attacker’s choice. |
| 62 | + |
| 63 | +## References |
| 64 | + |
| 65 | +- [Flagvent 2025 FV25.08 obstack exploit (0xdf)](https://0xdf.gitlab.io/flagvent2025/hard) |
| 66 | + |
| 67 | +{{#include ../../banners/hacktricks-training.md}} |
0 commit comments