GNU obstack function-pointer hijack
{{#include ../../banners/hacktricks-training.md}}
Overview
GNU obstacks embed allocator state together with two indirect call targets:
chunkfun(offset+0x38) with signaturevoid *(*chunkfun)(void *, size_t)freefun(offset+0x40) with signaturevoid (*freefun)(void *, void *)extra_argand ause_extra_argflag select whether_obstack_newchunkcallschunkfun(new_size)orchunkfun(extra_arg, new_size)
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.
Primitive: size_t desync β 0-byte allocation β pointer OOB write
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.
- Example:
elements = obstack_alloc(obs, sizeof(void *) * size);is compiled asSHL EAX,0x3forsize << 3. - With
size = 0x20000000andsizeof(void *) = 8, the multiplication wraps to0x0in 32-bit, so the pointer array is 0 bytes, but the recordedsizeremains0x20000000. - Subsequent
elements[curr++] = ptr;writes perform 8-byte OOB pointer stores into adjacent heap objects, giving a controlled cross-object overwrite primitive.
Leaking libc via obstack.chunkfun
- Place two heap objects adjacent (e.g., two stacks built with separate obstacks).
- Use the pointer-array OOB write from object A to overwrite object Bβs
elementspointer so that apop/read from B dereferences an address inside object Aβs obstack. - Read
chunkfun(mallocby default) at offset0x38to disclose a libc function pointer, then computelibc_base = leak - malloc_offsetand derive other symbols (e.g.,system,"/bin/sh").
Hijacking chunkfun with a fake obstack
Overwrite a victimβs stored struct obstack * to point at attacker-controlled data that mimics the obstack header. Minimal fields needed:
next_free == chunk_limitto force_obstack_newchunkon next pushchunkfun = system_addrextra_arg = binsh_addr,use_extra_arg = 1to select the two-argument call form
Then trigger an allocation on the victim obstack to execute system("/bin/sh") through the indirect call.
Example fake obstack layout (glibc 2.42 offsets):
fake = b""
fake += p64(0x1000) # chunk_size
fake += p64(heap_leak) # chunk
fake += p64(heap_leak) # object_base
fake += p64(heap_leak) # next_free == chunk_limit
fake += p64(heap_leak) # chunk_limit
fake += p64(0xF) # alignment_mask
fake += p64(0) # temp
fake += p64(system_addr) # chunkfun
fake += p64(0) # freefun
fake += p64(binsh_addr) # extra_arg
fake += p64(1) # use_extra_arg flag set
Attack recipe
- Trigger size wrap to create a 0-byte pointer array with a huge logical length.
- Groom adjacency so an OOB pointer store reaches a neighbor object containing an obstack pointer.
- Leak libc by redirecting a victim pointer to the neighbor obstackβs
chunkfunand reading the function pointer. - Forge obstack data with controlled
chunkfun/extra_argand force_obstack_newchunkto land in the forged header, yielding a function-pointer call of the attackerβs choice.
References
{{#include ../../banners/hacktricks-training.md}}