【学习记录】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函数,或者只提供了极小范围的溢出时,它们将是破局的关键。
📑 目录
- House of Einherjar:断链重生的 Off-by-null 合并术
- House of Rabbit:移花接木的 Fastbin 合并术
- House of Orange:无 free 时代的 IO_FILE 劫持鼻祖
- 总结与 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 利用条件
- Off-by-null 漏洞:能覆盖相邻 chunk 的
size最低字节为\x00。 - 可控的 prev_size:能伪造目标 chunk 的
prev_size,使得target_addr - prev_size指向我们控制的 Fake Chunk。 - 绕过 Unlink 检查:伪造的 Fake Chunk 必须满足
FD->bk == P && BK->fd == P(已知指针绕过法)。 - 触发 free:能够释放目标 chunk。
1.3 利用流程与数学布局
假设我们要在地址 A 处伪造一个 Fake Chunk,已知全局指针ptr指向 A。目标 chunk B 紧随其后。
- 构造 Fake Chunk (A):设置
size为一个不被 fastbin/tcache 管理的大小(如 0x90),设置fd = &ptr - 0x18,bk = &ptr - 0x10。 - 计算 prev_size:假设 B 的地址为
addr_B。我们需要设置 B 的prev_size = addr_B - addr_A。 - 触发 Off-by-null:溢出覆盖 B 的
size最低字节为\x00。 - 释放 B:glibc 检查
PREV_INUSE为 0,读取prev_size,找到 Fake Chunk A。对 A 执行 Unlink,合并 A 和 B。 - 结果:
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 法):
- 分配三个连续的 chunk A (0x31), B (0x31), C (0x91)。
- 释放 A 进入 fastbin。
- 利用溢出修改 A 的
size从0x31变为0x21。 - 触发
malloc_consolidate(例如申请一个 large chunk)。 - glibc 取出 A (size=0x21),认为它的下一个 chunk 是
A_addr + 0x20,这正好落在了原本属于 B 的头部!如果 B 此时处于 inuse 状态,glibc 会认为合并失败,将 A(0x21) 放入 unsorted bin。 - 但此时,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 --> F3.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 核心知识点总结
- House of Einherjar:利用 Off-by-null 清除
PREV_INUSE位,伪造prev_size触发后向合并,制造堆重叠并绕过 Unlink 检查。 - House of Rabbit:利用
malloc_consolidate合并 fastbin 的机制,通过修改 fastbin chunk 的 size 制造错位重叠。 - 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 的底层架构,为研究高版本堆利用打下坚实的理论基础。
参考文献:
- The Malloc Maleficarum - Phantasmal Phantasmagoria
- CTF Wiki - Heap Exploitation: House of Einherjar & Orange
- glibc malloc.c 源码分析
