Lion Cipher Lab
Hijack a file stream with the House of Emma: defeat glibc pointer guard on the _IO_cookie_jumps path
- Penetration Testing / Red Team
- Advanced
- ~90 min
- Free · no account required
Why it matters
House of Apple 2 taught you to reach an attacker-controlled function pointer through a legitimate glibc vtable. But glibc protects many of its stored function pointers with POINTER GUARD (PTR_MANGLE): the value in memory is not the real pointer, it is rol(pointer XOR a secret per-thread guard, 0x11), and glibc reverses it right before the call. So even with a full write primitive, you cannot just drop `system` into a guarded field -- write the raw address and it demangles to garbage and crashes. House of Emma is the FSOP technique for exactly this situation: it targets fopencookie streams,…
What you'll learn
- Why House of Apple 2 (the previous rung) needed NO pointer guard -- its final callee (_wide_data->_wide_vtable->__doallocate) is a plain function pointer in attacker-controlled memory -- while House of Emma's callee is a POINTER-GUARDED (PTR_MANGLE'd) callback, so the two techniques are chosen by whether the pointer you land on is mangled
- glibc pointer guard exactly: PTR_MANGLE(p) = rol(p XOR guard, 0x11) and PTR_DEMANGLE(p) = ror(p, 0x11) XOR guard on x86-64, with the per-thread guard stored in the TLS at %fs:0x30 -- so to make a demangled pointer equal `system` you must forge mangled = rol(system XOR guard, 0x11), which is impossible WITHOUT the guard value
- The _IO_cookie_jumps primitive end to end: a fopencookie stream's vtable is the LEGIT _IO_cookie_jumps (so it passes _IO_vtable_check), whose __overflow is the ordinary _IO_file_overflow; the flush path runs _IO_file_overflow -> _IO_do_write -> new_do_write -> _IO_SYSWRITE, and _IO_SYSWRITE on this vtable is _IO_cookie_write, which DEMANGLES fp->__io_functions.write and calls it as write_cb(fp->__cookie, buf, size) -- rdi = __cookie
- Reading the flush conditions from the glibc source and satisfying each branch: _IO_flush_all_lockp fires _IO_OVERFLOW when _mode<=0 and _IO_write_ptr>_IO_write_base; _IO_file_overflow skips its buffer-setup block when _IO_CURRENTLY_PUTTING is set and _IO_write_base is non-NULL; new_do_write avoids its seek branch when _IO_read_end == _IO_write_base and _IO_IS_APPENDING is clear
- Leaking a HIDDEN glibc symbol without it: call fopencookie() to make glibc install &_IO_cookie_jumps as a stream's vtable, then read the vtable pointer back at offset 0xd8 -- the same read-it-back-off-a-real-stream trick the House of Apple 2 lab used for _IO_wfile_jumps
- Building a fake _IO_cookie_file (a _IO_FILE_plus followed by __cookie at 0xe0 and __io_functions at 0xe8) so that __cookie points at a '/bin/sh' string and __io_functions.write holds the mangled system, then linking it into the stdio chain (stdout->_chain) so exit() flushes it
- Binary-exploitation JUDGMENT: House of Emma vs House of Apple 2 (guarded callee vs plain callee), the honest constraint that House of Emma needs the pointer guard AND a libc leak first (obtaining the guard -- a TLS/stack leak, or an arbitrary write that overwrites it -- is the real work), and what still stops these (glibc 2.36+ hardening, offset fragility, no-leak targets)
Steps in this lab
- Read this first: your own container, your own program, authorized-only
- The wall this climbs: a callee that is pointer-guarded
- Pointer guard, exactly: rol(p XOR guard, 0x11)
- The idea in one breath: borrow _IO_cookie_jumps, ride overflow into _IO_cookie_write
- Pin glibc 2.35 with Docker -- and confirm it
- Write the toy: leak system, leak &_IO_cookie_jumps, leak the guard
- Compile with mitigations off for teaching -- and note what stays ON
- Build the fake _IO_cookie_file: every field, and the branch it satisfies
- Fire it, and get the shell -- a call glibc made from its own exit path
- When it fails, and do it better: Emma vs Apple 2, and what still stops both