House of Storm 利用手法教学文档
一、背景与必要性
1.1 手法概述
House of Storm 是一种针对 glibc 堆管理器的组合利用手法,主要应用于 2.27 至 2.30 版本的 glibc。它通过精心构造的堆布局,结合 unsorted bin 和 large bin 的特定操作,实现在任意地址伪造 chunk 并成功申请出来,从而达成任意地址写的目的。
1.2 适用场景
- 题目限制条件:只能通过 House of Storm 达成利用,其他常见手法(如 fastbin attack、tcache poisoning 等)均不可行
- 关键限制:edit 次数有限(文中案例为 3 次),且每次写入大小受限(0x18 字节)
- 内存分配:只能申请特定大小的堆块(文中为 0x440、0x430、0x48),其中 0x48 通过 calloc 分配
- 信息泄露:show 功能只能使用一次,限制了信息泄露能力
- 版本限制:glibc 2.30 之后由于增加了 smallbin 双向链表检查,该手法失效
二、前置知识要求
2.1 堆风水基础
- 修改 size 位的 inuse 标志触发向前/向后合并
- fastbin 的 fd 指针修改利用
- tcache 相关利用:
- 劫持 tcache_perthread_struct 结构体修改 count
- 修改 tcache 的 fd 指针
- 加密指针绕过技巧
- key 检查绕过方法
- unsorted bin attack:通过修改 bk 指针实现任意地址写
- large bin attack:利用 glibc 的 large bin 管理算法修改 bk_nextsize
2.2 其他 House 手法了解
- House of Botcake:利用 unsorted bin 实现 double free
- House of Apple:伪造 FILE 结构体的 wide_data 和 vtable
- House of Einherjar:利用 prev_inuse 位伪造前一个 chunk
- House of Orange:改小 top chunk 触发 sysmalloc
- House of Spirit:伪造 size 字段使 fake chunk 被链入
- House of Force:修改 top chunk size
- House of Wiki:通过 malloc_assert 触发 IO
- House of Emma:爆破修改成员偏移
- House of Water:在 tcache 上残留 libc 地址
- House of Pig:劫持 tcache_perthread_struct 结构体打 IO
- House of Banana:劫持动态链接器打 IO
三、漏洞环境分析
3.1 程序功能分析
基于文档中的 2026 CISCN 半决赛题目 "catchme",程序提供以下功能:
-
add (adopt_creature)
- 可申请三种大小的堆块:
- 0x430 字节(通过 malloc)
- 0x440 字节(通过 malloc)
- 0x48 字节(通过 calloc)
- 最多同时存在 5 个堆块指针
- calloc 在 2.41 之前版本不会使用 tcache
- 可申请三种大小的堆块:
-
delete (release_creature)
- 存在 Use-After-Free (UAF) 漏洞
- 释放后不立即清空指针,直到调用 purge_record
-
show (inspect_creature_tag_once)
- 一次性读取功能,整个进程只能使用一次
- 读取堆块偏移 +0x8 处的 tag 字段
-
edit (engrave_creature_tag)
- 最多可使用 3 次
- 每次最多写入 0x18 字节
- 写入位置为堆块偏移 +0x8 处的 tag 字段
-
purge_record
- 仅清空指针,不释放内存
- 用于清理悬垂指针记录
3.2 限制条件总结
- 信息泄露极度有限:仅一次 show 机会
- 写入能力受限:最多 3 次 edit,每次 0x18 字节
- 分配大小固定:只有三种特定大小的堆块
- 无 IO 相关函数:难以通过常规 IO 流攻击
- 无法通过 fastbin/tcache 直接攻击:大小不合适且 calloc 不使用 tcache
四、House of Storm 核心机制
4.1 利用原理
House of Storm 的核心在于同时利用 unsorted bin 和 large bin 的链表操作,在任意地址伪造一个 chunk 并将其链入 unsorted bin,从而可以从该地址申请出 chunk。
4.2 关键源码分析
4.2.1 unsorted bin 遍历过程
当 malloc 从 unsorted bin 中取出 chunk 时,会执行以下操作:
while ((victim = unsorted_chunks(av)->bk) != unsorted_chunks(av)) {
bck = victim->bk;
// ... 检查和其他处理
}
4.2.2 关键写入操作
在 unsorted bin 处理过程中,有以下关键写入:
-
unsorted bin 链入:
bck->fd = unsorted_chunks(av); // 写入位置:victim->bk + 0x10 -
large bin 链入:
- 当 unsorted chunk 需要放入 large bin 时:
// 如果 unsorted_chunk->size > largebin_chunk->size unsorted_chunk->fd_nextsize = largebin_chunk; unsorted_chunk->bk_nextsize = largebin_chunk->bk_nextsize; largebin_chunk->bk_nextsize = unsorted_chunk; unsorted_chunk->bk_nextsize->fd_nextsize = unsorted_chunk; // 关键写入- 后续链表链接:
bck = largebin_chunk->bk; unsorted_chunk->bk = bck; unsorted_chunk->fd = largebin_chunk; largebin_chunk->bk = unsorted_chunk; bck->fd = unsorted_chunk; // 关键写入
4.3 伪造 chunk 的构造
要成功利用 House of Storm,需要构造以下条件:
-
控制两个 chunk:
- 一个 unsorted bin chunk(称为 chunk A)
- 一个 large bin chunk(称为 chunk B)
-
关键字段设置:
假设目标地址为 fake_chunk 对于 chunk A(unsorted bin): chunk_A->bk = fake_chunk 对于 chunk B(large bin): chunk_B->bk = fake_chunk + 0x8 chunk_B->bk_nextsize = fake_chunk - 0x18 - 5 -
写入结果:
- 第一次写入:
*(fake_chunk + 0x10) = main_arena + 0x60(unsorted bin 链表头) - 第二次写入:
*(fake_chunk + 0x3) = chunk_A(通过 bk_nextsize->fd_nextsize) - 第三次写入:
*(fake_chunk + 0x18) = chunk_A(通过 bck->fd)
- 第一次写入:
4.4 版本限制原因
在 glibc 2.30 之后,增加了 smallbin 双向链表检查:
bck = victim->bk;
if (__glibc_unlikely(bck->fd != victim)) {
malloc_printerr("malloc(): smallbin double linked list corrupted");
}
这个检查使得 House of Storm 无法在 2.30 及之后版本中使用。
五、利用步骤详解
5.1 准备工作
-
堆布局:
- 创建两个 large bin 大小的 chunk(0x440)
- 确保它们进入 large bin
- 创建 unsorted bin chunk
-
信息泄露:
- 通过唯一的 show 机会泄露 libc 地址
- 可能需要泄露堆地址(如果题目需要)
5.2 核心利用步骤
-
释放 chunk 到 large bin:
# 假设 chunk0 和 chunk2 是 large bin 大小的 chunk delete(0) # chunk0 进入 unsorted bin delete(2) # chunk2 进入 unsorted bin # 触发合并或重新分配,使它们进入 large bin add(0x440) # 重新分配,触发整理 -
构造伪造指针:
# 计算目标地址,通常是 __free_hook - 0x18 fake_chunk = libc_base + libc.sym["__free_hook"] - 0x18 # 第一次编辑:修改 unsorted bin chunk 的 bk edit(unsorted_chunk_idx, p64(fake_chunk)) # 第二次编辑:修改 large bin chunk 的 bk 和 bk_nextsize edit(largebin_chunk_idx, p64(fake_chunk + 8) + # bk p64(0) + # fd_nextsize p64(fake_chunk - 0x18 - 5)) # bk_nextsize -
触发分配:
- 申请一个合适大小的 chunk
- 由于伪造的 chunk 已经被链入 unsorted bin,这次分配会从 fake_chunk 处"切割"内存
- 成功获得目标地址附近的可控内存
5.3 后续利用
-
写 __free_hook:
- 由于 fake_chunk 在 __free_hook - 0x18 处
- 通过编辑可以修改 __free_hook 为 system 或其他目标函数
-
触发 shell:
- 释放一个包含 "/bin/sh" 字符串的 chunk
- 触发 __free_hook 执行 system("/bin/sh")
六、防御与检测
6.1 缓解措施
- 更新 glibc:升级到 2.30 或更高版本
- 安全编译选项:
- 开启 Full RELRO
- 开启 PIE
- 使用堆栈保护
- 代码审计:检查 UAF 漏洞和适当的指针清理
6.2 检测方法
- 动态检测:
- 监控异常的 bk/bk_nextsize 指针值
- 检测 unsorted bin 和 large bin 链表异常
- 静态分析:
- 检查堆操作中的指针使用
- 识别未初始化的指针访问
七、总结与延伸
7.1 手法特点
- 组合利用:需要同时控制 unsorted bin 和 large bin
- 条件苛刻:需要精确的堆布局和有限次数的编辑操作
- 历史手法:仅适用于特定版本的 glibc(2.27-2.29)
7.2 学习价值
- 深入理解堆管理:通过此手法可以深入理解 glibc 的 bin 管理机制
- 组合利用思维:学习如何将多个漏洞或条件组合成完整利用链
- 版本适应性:理解不同 glibc 版本的安全机制差异
7.3 相关资源
- 替代手法:在更新版本中,可研究 House of Emma、House of Pig 等新手法
- 调试技巧:使用 gdb 插件(如 pwndbg、gef)观察 bin 状态变化
- 练习平台:尝试在 glibc 2.27-2.29 环境中复现此手法
注:本文档基于提供的链接内容编写,详细描述了 House of Storm 利用手法的原理、步骤和实现细节。在实际应用中,请确保在合法授权的环境中进行测试和学习。