写在前面在上一篇中我们利用 House of Corrosion 成功将堆地址写入了_IO_list_all完成了 FSOP 攻击的“弹药装填”。然而当我们要引爆时却面临一个严峻的问题glibc 2.24 引入的 vtable 地址范围检查使得我们无法像 House of Orange 那样直接伪造一个指向system的虚表。虽然早先的 House of Pig 利用_IO_str_jumps中的malloc/free暂时绕过了限制但在更新的版本中这些路径也渐渐被堵死。今天我们将介绍 2023 年以来在顶级赛事中大放异彩的新一代 FSOP 技术House of Tangerine与House of Apple看它们如何利用合法的结构体指针完成精妙的控制流劫持。 目录现代困境vtable 检查与旧时代的落幕House of Tangerine利用_IO_obstack_jumps的暗度陈仓House of Apple利用_IO_wfile_overflow的借力打力核心精讲综合实战思维现代 FSOP 的路径选择总结与下篇预告1. 现代困境vtable 检查与旧时代的落幕在 glibc 2.23 的黄金时代FSOP 的终极操作是伪造IO_FILE_plus的vtable指向一个伪造的表直接让overflow函数指针指向system并让_IO_write_base指向/bin/sh。glibc 2.24 引入了IO_validate_vtable函数。每次调用虚表函数前都会检查vtable是否落在合法的__libc_IO_vtables段内。如果越界直接调用IO_vtable_check触发 abort。这直接宣告了“伪造任意虚表”手法的死刑。随后研究者们转向合法虚表内部寻找逻辑漏洞House of Pig找到了_IO_str_jumps中的malloc/free调用。但随着 glibc 进一步修复和检查加严_IO_str_overflow的利用条件也变得苛刻。于是攻击者的目光转向了其他合法虚表_IO_obstack_jumps衍生出 Tangerine和_IO_wfile_jumps衍生出 Apple。2. House of Tangerine利用_IO_obstack_jumps的暗度陈仓2.1 核心原理House of Tangerine 利用的是obstack对象栈相关的 IO 虚表_IO_obstack_jumps。当 IO 流的 vtable 指向这个表时其overflow函数_IO_obstack_overflow会执行类似如下的逻辑void _IO_obstack_overflow (_IO_FILE *fp, int c) { struct obstack *obstack fp-_IO_obstack; // 取出 obstack 指针 // 如果 obstack 存在且没有满 if (obstack) { // 调用 obstack 中的函数指针 obstack-chunkfun(obstack-extra_arg, ...); } }关键发现在执行overflow时程序会从IO_FILE结构体中取出一个指针_IO_obstack然后将其作为struct obstack结构体并调用该结构体内部的chunkfun函数指针这意味着虽然我们不能伪造vtable但我们可以使用合法的_IO_obstack_jumps虚表然后在堆上伪造一个struct obstack结构体将其中偏移0x8处的chunkfun指针覆盖为system并在偏移0x10处的extra_arg存入/bin/sh的地址。2.2 利用步骤伪造IO_FILE_plus设置vtable为_IO_obstack_jumps。设置fp-_IO_obstack指向我们伪造的struct obstack结构体。在伪造的obstack中设置chunkfun systemextra_arg /bin/sh。触发 FSOP如exit调用_IO_obstack_overflow最终执行system(/bin/sh)。注House of Tangerine 利用链相对较短但需要确保_IO_obstack_jumps符号存在且相关偏移正确。3. House of Apple利用_IO_wfile_overflow的借力打力核心精讲House of Apple 是由国内安全研究员roderick01提出的一系列极具创造性的利用技术目前已经成为 glibc 2.31~2.35 中出场率最高的 FSOP 手法。它利用的是宽字符处理相关的_IO_wfile_jumps虚表。3.1 核心原理当vtable指向_IO_wfile_jumps时触发overflow会调用_IO_wfile_overflow。这个函数内部有极其复杂的逻辑其中包含对_wide_data结构体的操作wint_t _IO_wfile_overflow (_IO_FILE *f, wint_t wch) { if (f-_flags _IO_NO_WRITES) { ... } _IO_wsetb (f, f-_wide_data-_IO_buf_base, f-_wide_data-_IO_buf_end, 0); // 如果 _IO_write_base 等于 _IO_write_ptr if (f-_wide_data-_IO_write_base 0) { _IO_wdoallocbuf (f); // 关键调用分配宽字符缓冲区 } // ... }在_IO_wdoallocbuf中void _IO_wdoallocbuf (_IO_FILE *fp) { if (fp-_wide_data-_IO_buf_base) return; // 如果已经有缓冲区则返回 // 调用宽字符虚表中的 __doallocate if (fp-_wide_data-_IO_buf_base 0) { (*fp-_wide_data-_wide_vtable-__doallocate)(fp); } }惊天发现在IO_FILE_plus内部有一个_wide_data指针指向一个_IO_wide_data结构体。而这个结构体内部竟然又包含一个虚表指针_wide_vtable更为致命的是glibc 在检查 vtable 时只检查了最外层的IO_FILE-vtable而没有检查内部的_wide_data-_wide_vtable这意味着我们可以让IO_FILE-vtable合法指向_IO_wfile_jumps通过外层检查。伪造_IO_wide_data结构体将其内部的_wide_vtable指向任意地址如堆上或栈上。控制执行流跳转到_wide_vtable中的__doallocate函数指针位置执行我们指定的地址如system。3.2 House of Apple 2 的精简布局为了顺利走到(*fp-_wide_data-_wide_vtable-__doallocate)(fp)我们需要精心布置如下字段伪造的IO_FILE_plus_flags: 设置为0x800(绕过_IO_NO_WRITES设置_IO_IS_FILEBUF)。_IO_write_base:0(或任意值保证不等于_IO_write_ptr)。_IO_write_ptr:0x1(保证base ! ptr)。_wide_data: 指向伪造的_IO_wide_data结构体。vtable: 指向_IO_wfile_jumps。伪造的_IO_wide_data结构体_IO_write_base:0。_IO_buf_base:0(触发_IO_wdoallocbuf的分配逻辑)。_wide_vtable: 指向fake_vtable_addr。伪造的fake_vtable在偏移0x68__doallocate的位置处写入system。触发逻辑当exit触发_IO_wfile_overflow时经过层层判断最终执行*(fake_vtable 0x68)(fp)。由于fp作为第一个参数传入如果我们把fp的开头即_flags所在地址布置为/bin/sh\x00那么实际执行的就是system(/bin/sh)触发 exit -- _IO_wfile_overflow检查外层 vtable合法: _IO_wfile_jumps访问 fp--_wide_data指向伪造的 _IO_wide_data检查 _IO_buf_base 0触发 _IO_wdoallocbuf访问 _wide_data--_wide_vtable无检查! 指向 fake_vtable调用 fake_vtable[0x68]即 __doallocate执行 system参数为 fp 起始地址, 即 /bin/sh3.3 House of Apple 的变种除了直接执行system(/bin/sh)House of Apple 最强大的变体是栈迁移 (Stack Pivoting)。如果题目环境无法直接执行system如开启了 seccomp 限制 execve我们可以将fake_vtable[0x68]指向__libc.gadget中的setcontext或gadget: xchg rax, rsp; ret。此时rax的值是fake_vtable 0x68的地址。通过精心布置可以将栈迁移到堆上随后执行 ROP 链如 orw 读取 flag。4. 综合实战思维现代 FSOP 的路径选择在现代 CTF 中选择哪条 FSOP 路径往往取决于题目环境和个人熟练度House of Apple绝对的首选。其利用链极其稳定适用于 glibc 2.31~2.35且既能打system又能做栈迁移 ROP适用面极广。House of Tangerine当 Apple 的某些条件受限时如无法布置完整的_IO_wide_dataTangerine 布局更简单只需伪造obstack结构体即可。House of Pig在 glibc 较低版本如 2.29~2.31且需要结合 Tcache 劫持malloc返回地址时依然好用。无论是哪种技术前置步骤通常是相通的利用堆漏洞如 House of Botcake获取任意地址写 - 结合 House of Corrosion 或 Largebin Attack 劫持_IO_list_all- 伪造结构体触发 FSOP。5. 总结与下篇预告5.1 核心知识点总结vtable 检查的盲区glibc 只检查了外层IO_FILE的vtable却忽略了_wide_data内部的_wide_vtable这是 House of Apple 的根基。合法虚表的滥用通过使用合法的_IO_wfile_jumps控制执行流进入复杂的宽字符处理逻辑最终实现“间接调用”伪造指针。栈迁移潜力House of Apple 不仅能 Getshell更是现代对抗 seccomp 进行 ROP 的利器。5.2 下篇预告在介绍完各种高阶的 House 技术后我们将迎来本周的收官之战综合练习UAF tcache poisoning 实战分析。我们将模拟一道完整的现代堆题从漏洞发现、利用 House of Botcake 制造重叠、信息泄露到最终利用 House of Apple 完成劫持手把手串联起整个现代堆利用的工程链。结语House of Apple 的出现证明了即使在严密的防御体系下只要数据结构存在嵌套和指针解引用就永远存在被“借力打力”的可能。它不仅是 CTF 的利器更是理解现代软件复杂状态机漏洞的绝佳教材。