【学习记录】Week11(二):House of 系列精讲—— 无 free 时代的破局与堆块合并的艺术

【学习记录】Week11(二):House of 系列精讲—— 无 free 时代的破局与堆块合并的艺术

写在前面:在上一篇中,我们学习了 House of Spirit、Force 和 Lore,掌握了伪造堆块、劫持 Top Chunk 和 Smallbin 的基本法。今天,我们将挑战 House 系列中更具实战意义、难度也更高的三种技术:House of Einherjar、House of Rabbit 与 House of Orange。这些技术常常出现在 glibc 2.23-2.27 的高质量 CTF 题目中,特别是当题目限制了free函数,或者只提供了极小范围的溢出时,它们将是破局的关键。

📑 目录

  1. House of Einherjar:断链重生的 Off-by-null 合并术
  2. House of Rabbit:移花接木的 Fastbin 合并术
  3. House of Orange:无 free 时代的 IO_FILE 劫持鼻祖
  4. 总结与 Week 11 展望

1. House of Einherjar:断链重生的 Off-by-null 合并术

1.1 漏洞原理

House of Einherjar 的核心在于利用Off-by-null(单字节 NULL 溢出)强行触发堆块的后向合并

在 glibc 中,当一个 chunk 被释放且非 fastbin/tcache 大小时,堆管理器会检查其前一个 chunk(通过PREV_INUSE位判断)。如果PREV_INUSE位为 0,说明前一个 chunk 处于空闲状态,glibc 会将当前 chunk 与前一个 chunk合并,以减少内存碎片。

如果我们能通过溢出将目标 chunk 的size字段最低位覆盖为\x00(清零PREV_INUSE位),并伪造目标 chunk 的prev_size字段,我们就能欺骗 glibc:让它以为目标 chunk 的前面(相距prev_size字节处)存在一个巨大的、空闲的 chunk。随后触发free,glibc 就会执行 Unlink 操作,将这个伪造的“前一个 chunk”从链表中摘除并合并。

1.2 利用条件

  1. Off-by-null 漏洞:能覆盖相邻 chunk 的size最低字节为\x00
  2. 可控的 prev_size:能伪造目标 chunk 的prev_size,使得target_addr - prev_size指向我们控制的 Fake Chunk。
  3. 绕过 Unlink 检查:伪造的 Fake Chunk 必须满足FD->bk == P && BK->fd == P(已知指针绕过法)。
  4. 触发 free:能够释放目标 chunk。

1.3 利用流程与数学布局

假设我们要在地址 A 处伪造一个 Fake Chunk,已知全局指针ptr指向 A。目标 chunk B 紧随其后。

  1. 构造 Fake Chunk (A):设置size为一个不被 fastbin/tcache 管理的大小(如 0x90),设置fd = &ptr - 0x18bk = &ptr - 0x10
  2. 计算 prev_size:假设 B 的地址为addr_B。我们需要设置 B 的prev_size = addr_B - addr_A
  3. 触发 Off-by-null:溢出覆盖 B 的size最低字节为\x00
  4. 释放 B:glibc 检查PREV_INUSE为 0,读取prev_size,找到 Fake Chunk A。对 A 执行 Unlink,合并 A 和 B。
  5. 结果ptr被修改为&ptr - 0x18,合并后的大 chunk 包含了 B 的区域,造成堆重叠,后续可利用 Tcache Poisoning 等手法获取 Shell。

1. 在已知指针处构造 Fake Chunk
满足 Unlink 检查

2. 计算偏移
设置目标 Chunk B 的 prev_size

3. 触发 Off-by-null
覆盖 B 的 size 低位为 \x00

4. 释放 Chunk B

5. glibc 触发后向合并
对 Fake Chunk 执行 Unlink

6. 合并产生堆重叠
全局指针被篡改

7. 任意地址写 -> Getshell

2. House of Rabbit:移花接木的 Fastbin 合并术

2.1 漏洞原理

House of Rabbit 利用的是 glibc 在将 fastbin 中的 chunk 放入 unsorted bin 时的malloc_consolidate机制。

当程序申请一个较大的内存(大于 fastbin 最大值)时,或者调用malloc_trim时,glibc 会遍历 fastbin 链表,将里面的 chunk 逐个取出,进行相邻 chunk 的合并,并放入 unsorted bin。
合并逻辑会检查 fastbin chunk 的前后相邻 chunk 是否空闲。如果空闲则合并。
如果我们通过漏洞(如堆溢出)修改了 fastbin 链表中某个 chunk 的size字段,使其变小,或者修改了其相邻 chunk 的prev_size,就能在malloc_consolidate过程中制造出堆块重叠

2.2 利用场景

这种技术通常用于绕过 fastbin 大小限制在无 free 的情况下制造重叠

典型利用步骤(缩小 size 法)

  1. 分配三个连续的 chunk A (0x31), B (0x31), C (0x91)。
  2. 释放 A 进入 fastbin。
  3. 利用溢出修改 A 的size0x31变为0x21
  4. 触发malloc_consolidate(例如申请一个 large chunk)。
  5. glibc 取出 A (size=0x21),认为它的下一个 chunk 是A_addr + 0x20,这正好落在了原本属于 B 的头部!如果 B 此时处于 inuse 状态,glibc 会认为合并失败,将 A(0x21) 放入 unsorted bin。
  6. 但此时,A 和 B 在逻辑上产生了错位。后续如果再次分配,可以从 unsorted bin 切割出 A,而 C 的区域依然存在,造成严重重叠。

2.3 限制与注意

  • 高版本 glibc 对 fastbin 的size检查变严,修改 size 必须保证仍在 fastbin 范围内且对齐。
  • 需要精确控制相邻 chunk 的状态,防止在 consolidate 时触发corrupted size vs. prev_size的崩溃。

3. House of Orange:无 free 时代的 IO_FILE 劫持鼻祖

3.1 漏洞原理

House of Orange 是堆漏洞利用史上的里程碑。它解决了两个难题:程序没有 free 函数如何释放堆块?以及如何在没有控制函数指针的情况下劫持控制流?

第一阶段:无 free 释放堆块 (Top Chunk 劫持)
当程序没有 free 时,我们只能通过malloc来操作堆。如果通过溢出修改 Top Chunk 的 size,使其变得很小(但必须满足对齐和剩余空间大于 MINSIZE 等条件),下一次申请一个大于该 size 的内存时,glibc 会认为 Top Chunk 不够用,触发sysmalloc
sysmalloc会将旧的 Top Chunk 放入 Unsorted Bin(相当于执行了 free),并向操作系统申请新的页作为新的 Top Chunk。这样我们就成功在无 free 的情况下获得了 Unsorted Bin 中的 chunk,可以用来泄露 libc 地址。

第二阶段:FSOP (File Stream Oriented Programming) 劫持控制流
在 glibc 2.23 中,当malloc遇到错误(如堆块校验失败)时,会调用malloc_printerr->__libc_message->abort->_IO_flush_all_lockp
这个刷新 IO 流的操作会遍历_IO_list_all链表。如果我们能通过 Unsorted Bin Attack 将_IO_list_all修改为main_arena + 88(即 Unsorted Bin 的链表头),并将一个伪造的 IO_FILE 结构体链接进去,当程序再次触发 malloc 错误时,就会调用我们伪造的 IO_FILE 里的虚表函数(通常是overflow函数),从而劫持控制流执行system("/bin/sh")

3.2 利用流程图

flowchart TD subgraph 第一阶段:泄露与伪造 A[1. 溢出修改 Top Chunk size] --> B[2. malloc 触发 sysmalloc] B --> C[3. 旧 Top Chunk 进入 Unsorted Bin] C --> D[4. 泄露 Libc 地址与堆地址] D --> E[5. 在 Unsorted Bin 构造 Fake IO_FILE] end subgraph 第二阶段:触发与劫持 F[6. Unsorted Bin Attack<br>覆盖 _IO_list_all = main_arena+88] --> G[7. 故意触发 malloc 错误<br>如破坏链表] G --> H[8. 调用 abort -> _IO_flush_all_lockp] H --> I[9. 遍历到 Fake IO_FILE] I --> J[10. 调用伪造的 vtable->__overflow] J --> K[11. 执行 system('/bin/sh')] end E --> F

3.3 现代演变

在 glibc 2.24+ 中,引入了_IO_str_jumps虚表检查和对 IO_FILE vtable 的范围限制,传统的 House of Orange 失效。但其核心思想演变出了House of Orange 2.0 (利用_IO_str_overflow)House of Roman等利用 IO_FILE 结构的新技术。理解 Orange 是掌握所有高版本 IO_FILE 攻击的基础。

4. 总结与 Week 11 展望

4.1 核心知识点总结

  1. House of Einherjar:利用 Off-by-null 清除PREV_INUSE位,伪造prev_size触发后向合并,制造堆重叠并绕过 Unlink 检查。
  2. House of Rabbit:利用malloc_consolidate合并 fastbin 的机制,通过修改 fastbin chunk 的 size 制造错位重叠。
  3. House of Orange:无 free 环境下,通过破坏 Top Chunk 触发sysmalloc释放堆块,结合 Unsorted Bin Attack 劫持_IO_list_all,利用 FSOP 调用伪造虚表获取 Shell。

4.2 防御与缓解

  • glibc 2.29+:引入了严格的prev_size一致性检查,使得 Einherjar 的触发条件变得极其苛刻。
  • glibc 2.24+:引入了 vtable 范围检查,封堵了原版 House of Orange 的虚表劫持。
  • 安全编码:杜绝 NULL 字节溢出(特别是strncpy的误用),严格控制堆溢出边界。

4.3 展望

本周我们深入探讨了 House of 系列的经典技术。从中我们可以看到,堆漏洞利用的本质是“通过破坏堆元数据,欺骗堆管理器执行非预期的操作”。随着 glibc 防御机制的升级,这些老技术或被修补,或演化出新的绕过姿势。在未来的学习中,我们将遇到更多结合 IO_FILE、Tcache Stashing Unlink 等新型攻击链。

最终结论:House of 系列是堆漏洞利用的艺术结晶。掌握它们不仅能让你在面对古董级 CTF 题目时游刃有余,更能让你深刻理解 ptmalloc2 的底层架构,为研究高版本堆利用打下坚实的理论基础。

参考文献

  1. The Malloc Maleficarum - Phantasmal Phantasmagoria
  2. CTF Wiki - Heap Exploitation: House of Einherjar & Orange
  3. glibc malloc.c 源码分析