【学习记录】Week11(一):House of 系列精讲—— 堆漏洞利用的艺术殿堂

📅 2026/7/4 6:47:35
【学习记录】Week11(一):House of 系列精讲—— 堆漏洞利用的艺术殿堂
写在前面欢迎进入 Week11 的学习在前两周的堆基础与进阶利用中我们掌握了 UAF、Double Free、Tcache Poisoning 以及 Off-by-one 等核心漏洞的利用套路。然而当面对一些限制极为严格的题目如没有 free 函数、无法直接控制 fd/bk 等常规套路往往会失效。此时就需要请出堆漏洞利用中的“十八般武艺”——House of 系列。本周我们将精选 6 种经典的 House 技术今天首先带来House of Spirit、House of Force 与 House of Lore的深度解析。 目录House of 系列 概述House of Spirit无中生有的伪造术House of Force以力破巧的 Top Chunk 劫持House of Lore暗度陈仓的 Smallbin 劫持总结与下篇预告1. House of 系列 概述“House of XXX” 这一命名传统起源于著名安全研究员 Phantasmal Phantasmagoria 在 2004 年左右发布的一系列文章《The Malloc Maleficarum》。这些技术最初是针对早期 glibc 堆管理器漏洞的理论构想。随着 glibc 的演进和 CTF 比赛的发展许多理论变成了现实并衍生出了更多的变体如 House of Orange, House of Botcake 等。House of 系列的本质是针对 ptmalloc2 中特定的数据结构和管理机制如 Top Chunk、Fastbin、Unsorted Bin、IO_FILE 等利用特定的漏洞哪怕只有一个字节的溢出或一个非零的写原语来实现信息泄露或任意代码执行。2. House of Spirit无中生有的伪造术2.1 漏洞原理House of Spirit 的核心思想是“伪造堆块”。如果程序中存在一个漏洞允许我们在目标地址如栈上、BSS 段写入数据我们可以按照 glibc chunk 的结构伪造一个 chunk 的size和next/fd等字段然后将这个伪造 chunk 的指针传递给free()函数。当 glibc 执行free(ptr)时它会检查ptr附近ptr - 0x8或ptr - 0x10的size字段。如果size合法它就会把这个“伪造的 chunk”放入对应的 bin通常是 fastbin 或 tcache中。一旦这个伪造 chunk 进入了 bin下一次我们调用malloc申请相同大小时glibc 就会把这个伪造的地址返回给我们从而实现了任意地址分配。2.2 利用条件存在写原语能在目标地址如栈溢出覆盖返回地址前的区域写入伪造的 chunk header。可控的 free能将目标地址作为参数传递给free()。绕过检查伪造的size字段必须满足对应 bin 的检查要求如 fastbin 大小范围、IS_MMAPPED位为 0 等且目标地址需要符合内存对齐。后续 malloc程序在 free 之后必须还有机会以相同大小调用malloc。2.3 利用流程图1. 确定目标地址如栈上的返回地址附近2. 伪造 Chunk Header写入合法的 size 和对齐填充3. 触发漏洞调用 freefree(target_addr)4. glibc 检查 size 合法将伪造 chunk 放入 Fastbin/Tcache5. 调用 malloc 申请相同大小6. glibc 从 bin 中取出伪造 chunk返回目标地址7. 在目标地址写入 shellcode/ROP链劫持控制流2.4 实战场景示例假设在栈上存在一个指针ptr我们可以通过栈溢出覆盖ptr的值然后程序会执行free(ptr)。// 假设我们想控制返回地址 ret_addr // 我们在栈上构造如下结构 // [ padding ] (8 bytes) // [ size: 0x41 ] (8 bytes) - 伪造 chunk 的 size0x40 大小PREV_INUSE1 // [ ... fake user data ... ] (0x38 bytes) // [ next_size: 0x1 ] (8 bytes) - 绕过下一个 chunk 检查 // 栈布局 // 0x7fffffff0100: 0x0000000000000000 -- 伪造 chunk 的 prev_size (ptr 指向这里) // 0x7fffffff0108: 0x0000000000000041 -- 伪造 chunk 的 size // 0x7fffffff0110: ... (user data) // 0x7fffffff0148: 0x0000000000000001 -- 下一个 chunk 的 size // 漏洞触发 // 1. 通过栈溢出覆盖指针 ptr 0x7fffffff0100 // 2. 程序执行 free(ptr) // 3. 程序执行 malloc(0x30) - 返回 0x7fffffff0110 // 4. 向 0x7fffffff0110 写入 ROP 链覆盖原本的返回地址3. House of Force以力破巧的 Top Chunk 劫持3.1 漏洞原理House of Force 是一种针对Top Chunk顶部 chunk的利用技术。ptmalloc2 在管理堆时所有的 chunk 都是从 Top Chunk 中切割下来的。当用户请求内存时如果 bins 中没有合适的 chunkglibc 会从 Top Chunk 的顶部划分出请求大小的空间。如果我们能通过堆溢出覆盖Top Chunk 的 size 字段将其修改为一个极大的值如0xffffffffffffffff那么无论我们申请多大的内存Top Chunk 都被认为“足够大”不会触发sysmalloc向操作系统申请新内存。此时如果我们精心计算一个巨大的malloc参数size使得Top Chunk 的指针 size正好溢出并等于目标地址如 GOT 表、Hook 函数地址那么下一次malloc就会直接在目标地址分配内存实现任意地址写。3.2 数学推导假设当前 Top Chunk 的地址为top_addr。目标地址为target_addr如__malloc_hook - 0x10。我们想要让malloc(size)后Top Chunk 移动到target_addr。移动公式new_top_addr old_top_addr size 0x10(包含 header)。我们需要让new_top_addr target_addr即size target_addr - old_top_addr - 0x10因为target_addr通常比old_top_addr小GOT 表在低地址堆在高地址所以size实际上是一个负数。但在无符号整数中负数表现为一个极大的正数。只要 Top Chunk 的 size 被我们改成了0xffffffffffffffff这个巨大的size就能通过size old_top_size的检查。3.3 利用条件与限制堆溢出能够覆盖 Top Chunk 的 size 字段。可控的 malloc能够以任意大小调用malloc。高版本限制在 glibc 2.29 中引入了对 Top Chunk 大小的严格检查assert((old_top initial_top(av) old_size 0) || ...)House of Force 在高版本中基本失效但理解其原理对于掌握堆分配机制至关重要。3.4 利用步骤利用堆溢出将 Top Chunk 的 size 覆盖为0xffffffffffffffff。计算偏移evil_size target_addr - current_top_ptr - 0x10。调用malloc(evil_size)消耗掉中间的堆空间Top Chunk 指针移动到target_addr。再次调用malloc(任意大小)此时返回的地址即为target_addr 0x10跳过 header。向该地址写入 payload如修改 GOT 表。4. House of Lore暗度陈仓的 Smallbin 劫持4.1 漏洞原理House of Lore 针对的是Smallbin的分配机制。当请求的 chunk 大小在 smallbin 范围内时glibc 会优先从 smallbin 中取出 chunk。smallbin 是一个双向链表。在 glibc 2.28 及以前从 smallbin 中取出 chunk 时的检查相对较弱if ((victim last(bin)) ! bin) { bck victim-bk; // 检查 bck-fd 是否等于 victim (双向链表完整性) if (__glibc_unlikely(bck-fd ! victim)) malloc_printerr(malloc(): smallbin double linked list corrupted); // 设置 inuse 位 set_inuse_bit_at_offset(victim, nb); bin-bk bck; bck-fd bin; // ... return victim; }如果我们能控制 smallbin 中最后一个 chunk 的bk指针使其指向target_addr - 0x10那么当这个 chunk 被分配出去时glibc 会执行bck-fd bin即向target_addr写入bin的地址一个 libc 地址。更重要的是在 glibc 2.30 引入了 smallbin 到 tcache 的 stash 机制后House of Lore 演变出了更强大的变体Tcache Stashing Unlink Attack可以直接将目标地址加入 Tcache 链表从而在下次malloc时直接返回目标地址。4.2 经典利用流程glibc 2.23-2.28泄露 libc 基址。将目标 chunk 释放进入 smallbin通过先进入 unsorted bin 然后触发 sort 来实现。利用 UAF 或堆溢出修改 smallbin 链表中该 chunk 的bk指针为target_addr - 0x10。触发malloc请求从该 smallbin 中分配 chunk。检查通过后target_addr处被写入 libc 地址且如果利用 stash 机制target_addr会被当作下一个 chunk 放入 bin/tcache再次malloc即可获得该地址。4.3 现代变体Tcache Stashing Unlink Attack在 glibc 2.30 中当从 smallbin 取出一个 chunk 且对应大小的 Tcache 未满时glibc 会把 smallbin 中的剩余 chunk 全部放入 Tcachewhile (tcache-counts[tc_idx] mp_.tcache_count (tc_victim last(bin)) ! bin) { bck tc_victim-bk; set_inuse_bit_at_offset(tc_victim, nb); bin-bk bck; bck-fd bin; // 漏洞点 tcache_put(tc_victim); }如果我们篡改了tc_victim的bk指针那么不仅会执行bck-fd bin任意地址写 libc还会在下一轮循环中将bck即我们伪造的目标地址当作 chunk 放入 Tcache这样后续连续的malloc操作就能直接返回目标地址。5. 总结与下篇预告5.1 核心知识点总结House of Spirit通过伪造 chunk header 并 free将任意地址“洗白”为合法堆块再通过 malloc 取回。House of Force通过覆盖 Top Chunk size 为极大值利用整数溢出使得 malloc 直接在任意地址分配内存。注高版本已修补House of Lore通过篡改 smallbin 的bk指针在 chunk 分配或 stash 过程中将伪造地址写入 libc 地址或加入 Tcache 链表。5.2 下篇预告下一篇我们将迎来 House of 系列中难度更高、也更经典的技术House of Einherjar 与 House of Orange。House of Einherjar利用 off-by-one/null 清除 PREV_INUSE 位强行触发 chunk 合并制造堆重叠。House of Orange在没有free()的情况下利用 Top Chunk 溢出触发sysmalloc将 chunk 送入 Unsorted Bin并结合 IO_FILE 劫持控制流。最终结论House of 系列技术展示了攻击者如何利用堆管理器中最细微的机制特性。即使是看似只读或受限的操作如 free 一个指针、申请大内存在精心构造的布局下都能转化为致命的攻击原语。理解这些技术是迈向高阶 PWN 选手的必经之路。参考文献The Malloc Maleficarum - Phantasmal PhantasmagoriaCTF Wiki - Heap Exploitation: House of Seriesglibc malloc.c 源码分析