【学习记录】Week13(四):stdout/stderr 独立攻击面深度剖析与全周总结
2026/7/4 16:06:54 网站建设 项目流程

写在前面:欢迎来到 Week13 的收官之战!在前三篇中,我们系统梳理了_IO_FILE的结构布局、调用链、任意读写原语构造,以及结合exit机制的各路劫持手法。这些技术大多围绕“伪造一个全新的 FILE 结构体并挂入_IO_list_all”展开。然而,在实战中,libc 数据段里已经躺着三个现成的 FILE 结构体(stdin,stdout,stderr)。今天,我们将聚焦如何将这些现有流作为独立攻击面,并探讨当程序无法正常退出时,如何利用堆报错强行触发 IO 流刷新。最后,我们将对本周的 IO_FILE 理论体系进行全面总结。

📑 目录

  1. 独立攻击面:为什么是stdoutstderr
  2. stdout半盲劫持:无_IO_list_all修改的泄露与执行
  3. stderrmalloc_printerr的联动:强制触发 FSOP
  4. glibc IO 防御机制演进图谱 (2.23 ~ 2.35+)
  5. Week 13 全局总结与下期预告

1. 独立攻击面:为什么是stdoutstderr

伪造 FILE 结构体并劫持_IO_list_all虽然威力巨大,但往往需要一次“任意地址写堆地址”的原语(如 Largebin Attack 或 House of Corrosion)。在某些题目限制下,我们可能只拥有“任意地址写固定值”或“部分溢出”的能力。

此时,直接篡改 libc 中的stdout(_IO_2_1_stdout_) 或stderr(_IO_2_1_stderr_) 就成了更优解。

  1. 位置固定:它们都在 libc 数据段,地址可通过 Libc 基址直接推算。
  2. 已在链表中:它们天然挂在_IO_list_all的头部,无需修改链表指针,只需破坏其内部字段即可。
  3. 高频调用:程序只要执行putsprintf,就会直接访问stdout;只要触发堆错误,就会访问stderr

2.stdout半盲劫持:无_IO_list_all修改的泄露与执行

2.1 经典信息泄露 (glibc 2.23~2.27)

在早期版本中,stdout_flags通常是0xfbad2887。其内部指针的排布通常是:_IO_write_base<=_IO_write_ptr<=_IO_write_end
如果存在单字节溢出(Off-by-one)或部分写漏洞,将_IO_write_base的最低位覆盖为\x00,会导致_IO_write_base向前扩展,覆盖到 libc 数据段中前面的内存(通常是_IO_2_1_stdin_的内容或函数指针)。
当程序下次调用printf("...")时,glibc 会把从_IO_write_base_IO_write_ptr之间的所有数据当作缓冲区输出,从而无脑泄露 Libc 内部地址

2.2 执行流劫持 (结合 House of Apple)

在高版本(2.34+)中,我们依然可以打stdout

  1. 通过 Tcache Poisoning 或其他任意写手段,直接分配到stdout所在的内存。
  2. 不修改_chain,也不修改_IO_list_all,直接把stdoutvtable改为_IO_wfile_jumps
  3. 伪造其_wide_data指针指向堆块,并在堆上布置system或 ROP 链。
  4. 当程序下一次执行puts或退出时,对stdout调用overflow,直接触发 House of Apple 链。
    优势:这种手法极其隐蔽,不需要大动干戈地修改全局指针,只在局部做文章。

3.stderrmalloc_printerr的联动:强制触发 FSOP

在 CTF 中,经常遇到一种尴尬的情况:我们已经布置好了伪造的 FILE 结构体并劫持了_IO_list_all,但程序既没有调用exit,也没有return,而是卡在一个死循环里。FSOP 无法触发怎么办?

答案是自己创造错误,利用 glibc 的报错机制!

3.1malloc_printerr调用链

当 glibc 的堆管理器检测到堆元数据被破坏(如double freesize校验失败、unlink检查失败等),会调用malloc_printerr打印错误信息并终止程序。
其内部调用链大致为:
malloc_printerr->__libc_message->abort->_IO_flush_all_lockp

3.2 实战应用:主动触发崩溃

  1. 我们利用漏洞劫持_IO_list_all,布置好 House of Apple 链。
  2. 我们故意破坏堆块的size字段,或者制造一个必定会被 glibc 检测到的 Double Free。
  3. 下次调用mallocfree时,glibc 触发malloc_printerr
  4. abort阶段,glibc 会尝试调用stderr打印报错信息。如果我们劫持了stderr,直接触发执行!
  5. 即使没劫持stderr_IO_flush_all_lockp也会被调用,遍历被我们篡改的_IO_list_all,触发伪造结构的overflow

核心思维:在堆漏洞利用中,“触发崩溃”往往不是终点,而是我们主动按下的一枚“引爆按钮”。

4. glibc IO 防御机制演进图谱 (2.23 ~ 2.35+)

为了方便大家记忆和对照,我们将本周涉及的所有防御机制演进与绕过思路总结为下图:

glibc 演进与攻防博弈

伪造任意 vtable

滥用合法 vtable 内部逻辑

转向嵌套结构体盲区

结合 Largebin/Corrosion 劫持 _IO_list_all

结合 exit/tls_dtor_list 多路径

House of Orange (直接 system)

glibc 2.23: 无校验时代

glibc 2.24: vtable 范围检查

House of Pig (_IO_str_jumps)

glibc 2.29~2.31: 封堵 str_overflow, Tcache Key

House of Apple (_IO_wfile_jumps)

glibc 2.34: 移除 Hook, 强化 PTR_MANGLE

现代 FSOP 标准链

glibc 2.35+: 检查内部指针一致性

综合复合利用

5. Week 13 全局总结

5.1 核心知识点回顾

本周我们彻底解剖了_IO_FILE机制,这是现代 PWN 的灵魂。核心要点如下:

  1. 结构即攻击面_IO_FILE中的_chain构成链表,_IO_buf_base/end控制缓冲区,vtable实现多态。每一部分都能被转化为漏洞原语(链表劫持、任意读写、控制流转移)。
  2. 触发路径多样化:FSOP 不再只依赖exitmalloc_printerr造成的abort是更强大的强制触发点。
  3. 绕过思维的根本转变:从“伪造非法数据骗过系统”转变为“使用合法数据结构(合法 vtable),通过操纵其内部复杂的逻辑跳板,将控制流导向未校验的角落(如_wide_vtable)”。
  4. 复合利用:现代题目往往是“堆漏洞制造原语 (Botcake) -> 全局指针劫持 -> IO 结构体伪造 -> 强制触发”的流水线作业。

5.2 进阶建议

  1. 源码追踪:用 GDB 跟踪一次_IO_wfile_overflow的完整执行流,观察_wide_data是如何被解引用的。
  2. 偏移记忆:熟记stdout在常见 libc 版本中的偏移,以及_IO_FILE_plusvtable(0xd8) 和_chain(0x68) 的偏移,这是实战中拼手速的关键。

结语:IO_FILE 的学习曲线是陡峭的,但它的回报也是丰厚的。掌握了它,你就掌握了在 glibc 2.34+ 无 Hook 时代生存的王牌。当你能熟练地在脑中构建出_IO_FILE_plus的立体结构,并清晰地看到数据流如何通过虚表流转时,所有的防护机制在你眼中都将只是透明的壁垒。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询