FSOP Development
Goal: turn FILE-structure corruption into the right libio dispatch path for that glibc era, trigger surface, and endgame — not into random _IO_FILE cargo cult.
Repository positioning: keep offensive-coding/heap-exploitation-dev as the allocator and overlap router, then switch here once success depends more on libio structure modeling, validated jump-table reuse, wide/codecvt/cookie-file dispatch, or trigger choice than on heap choreography itself. Engagement methodology and mitigation strategy live in offensive-techniques/binary-exploitation-technique; concrete FSOP recipes in offensive-ctf/pwn-ctf/references/heap-fsop.md.
When to activate
- Heap, UAF, overlap, arbitrary write, partial overwrite, or stream-adjacent corruption reaches
stdin,stdout,stderr, anotherFILE *, or a fakeFILEregion. - The exploit reaches a heap-allocated or dangling stream from
fopen/fdopen/customFILE *, and the real question becomes howfputs/fwrite/cleanup will consume it. - Need to decide whether the target is classic
_IO_list_all, vtable-misalignment,_wide_vtable,_codecvt, obstack, or leak-only FILE abuse. - Hooks are removed or unrealistic, and the real exploit question is whether FSOP beats return-address, callback, or data-only alternatives.
- Need to reason about
FILE,_IO_FILE_plus,_IO_wide_data,_IO_codecvt, pointer-guard-adjacent surfaces, or valid jump-table placement. - Need a hint-style recognition pass for stream corruption opportunities before building a full heap chain.
- The initial bug is not purely “heap-themed” anymore — for example, a relative libc write into a standard stream, an mmapped chunk reaching libc FILE data, or a direct stream-specific corruption bug.
If the problem is mostly allocator selection, heap shaping, tcache/largebin choreography, or House-family routing before the stream overlap exists, start with offensive-coding/heap-exploitation-dev and return here once FSOP is a real candidate.
First classify the FSOP problem
- Identify the stream target
stdoutfor output-driven paths likeputs,printf,fflushstderrfor low-noise or assert/abort-driven pathsstdinfor leak or arbitrary-read/write redirection- custom
FILE */fopenobject / socket-backed stream / heap fake FILE
- Identify the glibc era
- pre-2.24: no vtable validation
- 2.24+: vtable range validation
- 2.34+: hooks removed, FSOP becomes first-class endgame
- 2.35+ / modern:
_wide_vtable, codecvt, and angr-mapped paths matter more than fake raw heap vtables
- Decide the capability you actually need
- leak arbitrary memory
- arbitrary read/write through buffer redirection
- direct PC control / indirect call
setcontextpivot / ORW / SUID-safe chain- exit-time or assert-time execution
- Map the available corruption
- full stream overlap / arbitrary write into stream
- only a few stable fields
- only
vtable-adjacent control - only
_wide_dataor_codecvtreachability - only trigger-side influence with limited field control
- Choose the natural trigger surface
puts,printf,fprintf,fwritefflush,fclose,setbuf, sync/finish pathsexit,return from main, abort/assert,__malloc_assert- read-side paths like
fgets, underflow/seekoff, orientation switches
Era-first selection
| Era | Bias toward | Avoid or de-prioritize |
|---|---|---|
| glibc <= 2.23 | classic fake vtable, _IO_list_all, _IO_OVERFLOW, raw FILE-chain abuse |
post-hook-only thinking |
| glibc 2.24-2.33 | vtable-misalignment, validated jump-table reuse, _wide_vtable routes, leak-first stream abuse |
fake heap vtable pointers outside libc vtable section |
| glibc 2.34+ | House of Apple 2/3, _wide_vtable, codecvt, stderr/stdout overlap, setcontext/ORW endings |
__free_hook nostalgia |
| glibc 2.35+ and modern | angry-FSROP paths, _IO_wdoallocbuf, _IO_switch_to_wget_mode, codecvt, obstack, TLS/pointer-guard-adjacent pivots |
assuming only one Apple chain exists |
Technique families worth prioritizing
| Family | Good fit | Typical result | Reality check |
|---|---|---|---|
classic _IO_list_all FSOP |
pre-vtable-check or controlled valid vtable path | exit-time _IO_overflow dispatch |
foundational, but not the modern default |
| stdout/stderr overlap | arbitrary allocation/write over standard streams | trigger via normal output, fflush, or assert path |
still one of the best practical setups |
| buffer redirection | control _IO_read_* / _IO_write_* ranges |
arbitrary read/write or targeted leak | not all FSOP ends in RIP control |
| vtable misalignment | validated vtable section but wrong slot alignment | call a different libc libio function than intended | central post-2.24 concept |
| House of Apple 2 | _wide_data->_wide_vtable reachable |
direct call, system, or setcontext |
primary post-hook baseline |
| House of Apple 3 | _codecvt path easier than wide-data path |
indirect call through codecvt helper | higher setup cost, but powerful |
| obstack paths | stream or neighboring stream state controllable | call via _obstack_newchunk |
underused but real |
| leak-oriented FSOP | need TLS/libc/stack/tcb leak more than immediate RCE | stdout/stderr disclosure, pointer-guard recovery | often better than forcing shell-first |
| pointer-guard-adjacent FSOP | TLS/pointer-guard or encrypted callback surfaces in play | unlock later destructor/cookie-file hijack | usually a bridge, not the first stage |
| input-buffer redirection | can't forge in place (zeroing alloc) or redirect a RO stream pointer; an input stream is read each loop | corrupt only _IO_buf_base/read ptrs (keep vtable+_lock) → program's fgets/fread read()s a forged FILE into another stream → trigger on next print |
the practical escape from the in-place wall; CET/IBT-safe (dispatch stays in libc) |
Hint-mode recognition rules
Load references/hints-and-recognition.md when triaging quickly. The shortest recognition cues are:
- Triage the trigger first: scan imports/PLT — only
_exit(noexit/atexit) means_IO_list_allnever flushes; unusedstderrmeans no assert/error trigger. Don't forge for an unreachable trigger. - If you can overlap
stdoutorstderr, ask which natural call path touches it next before chasing ROP. - If the
vtablemust stay inside libc, ask which misaligned slot or alternate jump table gives the call you want. - If
_wide_datais reachable, ask can I force_IO_wdoallocbufor_IO_WOVERFLOW? - If
_codecvtis reachable, ask is this really Apple 3 / codecvt-in/out/length? - If a heap exploit already gives largebin/tcache positioning over a stream, bias toward FSOP before exotic leakless Houses.
- If seccomp or SUID makes
system("/bin/sh")weak, bias towardsetcontext, ORW, or a leak-first destructor chain. - If normal output mangles your crafted state, prefer
stderror assert-time dispatch. - In-place wall — if your only write primitive zeroes the chunk and the program does a FILE op on that stream before you can re-forge it (e.g. an alloc that
memsets thenprintfs), do not forge in place. Use cross-stream buffer redirection (corrupt an input stream's_IO_buf_base/read ptrs, keepvtable+_lock, let itsfgets/freadread()your forged FILE into another stream), pick a stream the corrupting op never touches, or use a non-zeroing/print-free/second-thread write.
Trigger-driven rules
puts/printf/fprintf: excellent when stdout is the target and you can satisfy byte-oriented path checks.fflush/ sync / finish: strong when you want a quieter dispatch point or an internal sync path.exit/ return frommain: best when_IO_list_alltraversal or cleanup logic is your dispatch engine.- abort /
__malloc_assert: strong when only failure-driven flushing is realistic; think Cat/Kiwi/Apple-style assert triggers. - read-side functions: use when the win is disclosure or when underflow/seekoff/orientation paths reach
_wide_vtableor codecvt.
Endgame selection
- Need simplest post-hook RCE: House of Apple 2 first.
- Need SUID-safe or seccomp-safe execution:
setcontextpivot, ORW, or structured ROP oversystem. - Need arbitrary disclosure or pointer-guard recovery: leak-oriented stdout/stderr FSOP first, execution second.
- Need valid-target, libc-internal dispatch with few writes: pick a misalignment or angry-FSROP path with the smallest stable field set.
- Have a cheaper application-owned callback overwrite already: do not force FSOP just because it is glamorous.
Reliability checklist
- Confirm the exact glibc version and whether your offsets match the target build.
- Separate overlap/corruption, dispatch path, and endgame.
- Keep
_lockvalid and writable unless the chosen path proves it will not be touched. - Track whether
_modemust stay negative, become positive, or remain zero for the intended path. - Re-check whether read/write helpers will clobber
_IO_read_*,_IO_write_*,_wide_data, or adjacent bytes before dispatch. - Verify whether the chosen path expects byte-oriented, wide-oriented, or codecvt-backed state.
- Prefer the stream whose normal use interferes least with your crafted layout.
- If the chain rewrites a libc GOT/relocation target or relies on a low-byte pointer patch, load
pwn-ctfRELRO/ASLR relocation guidance and prove object-specific RELRO, page permissions, and ASLR invariance first.
Anti-patterns
- Treating every FILE corruption as House of Apple 2.
- Overwriting a vtable pointer to raw heap memory on glibc with validation enabled.
- Forgetting that many FSOP wins are leak/read/write primitives, not immediate PC control.
- Recommending
system("/bin/sh")on SUID or seccomp targets by reflex. - Ignoring normal stream activity that mutates
_IO_write_ptr,_IO_buf_base,_mode, or_flagsbefore the trigger fires. - Forging a FILE for an unreachable trigger (e.g.
_IO_list_allwhen the binary only_exits) — verify the trigger in the imports/PLT first. - Repeatedly attempting an in-place forge when the write primitive zeroes the stream and the program immediately uses it — that ordering can never complete; switch to buffer redirection or a print-free write.
Resources
- references/libio-model-and-eras.md —
FILE,_IO_FILE_plus,_IO_wide_data,_IO_codecvt, vtable validation, and era map. - references/canonical-fsop-families.md — classic
_IO_list_all, Apple 2/3, Pig, Cat, Kiwi, Emma, and where angry-FSROP paths fit. - references/triggers-and-call-paths.md —
puts,printf,fflush,exit, assert/abort, underflow, seekoff, and why some paths are cleaner than others. - references/hints-and-recognition.md — fast hint-mode triage, constraint heuristics, target-selection rules, and endgame cues.
Load references only after the target stream, glibc era, available corruption, and intended trigger are clear.