写在前面:在上一篇中,我们利用 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 = system,extra_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/sh'
3.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_data),Tangerine 布局更简单,只需伪造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 的利器,更是理解现代软件复杂状态机漏洞的绝佳教材。