构建新一代快照模糊测试工具与IDA实战
引言
2020年1月,我决定开发自定义模糊测试工具。在与yrp604讨论后,我们决定构建一个可用于模糊测试任何Windows目标(用户/内核模式、应用/服务、内核/驱动)的工具。计划开发基于Windows快照的模糊测试器,在VM或模拟器中运行目标代码,允许用户通过断点进行插桩,并提供现代模糊测试器应有的基本功能:代码覆盖率、崩溃检测、通用变异器、跨平台支持、快速恢复等。
为了验证工具实用性,我选择IDA Pro作为目标,原因包括:它是复杂的Windows用户模式应用、解析多种二进制文件、启动缓慢(快照方法可加速测试)、且存在漏洞赏金计划。
架构设计
用户使用流程:
- 在目标中寻找接近处理攻击者控制数据的位置,使用Windows内核调试器中断并置目标于所需状态
- 生成内核转储并提取CPU状态
- 编写模块告知wtf如何插入测试用例,定义停止条件
核心组件:
- kdmp-parser: C++库解析Windows内核转储
- bdump.js: Windbg扩展用于提取CPU状态和MSR
- bochscpu: Bochs的CPU组件,通过Rust库提供C绑定
Bochscpu基础
内存管理
Bochscpu采用惰性内存加载机制,通过回调处理未映射的物理内存访问:
|
|
CPU状态加载
通过bochscpu_cpu_set_state设置完整的CPU状态,包括寄存器、MSR、段描述符等。
执行钩子
注册多种钩子函数获取执行信息:
|
|
基础功能构建
内存访问设施
提供物理和虚拟内存读写能力。虚拟内存访问需要模拟MMU和解析页表。
执行流插桩
通过指令执行通知实现断点功能:
|
|
无限循环处理
通过指令计数限制测试用例执行:
|
|
代码覆盖率追踪
每次指令执行时记录地址:
|
|
脏内存追踪
通过内存访问钩子跟踪写入的内存:
|
|
通用变异器
集成libfuzzer和honggfuzz的变异策略:
|
|
IDA实战测试
测试用例插入
通过文件系统钩子层处理IDA的文件I/O操作,将实际文件读入缓冲区并通过钩子传递字节。
遇到的问题与解决方案
问题1: 预加载DLL 解决方案:在生成快照前使用inject工具将DLL注入IDA进程。
问题2: 换出内存 解决方案:使用lockmem工具锁定进程工作集中的所有虚拟内存范围。
问题3: 手动触发页错误 通过虚拟到物理地址转换检查页表状态,必要时插入页错误。
问题4: KVA Shadow 通过注册表编辑禁用KVA Shadow缓解措施。
问题5: 性能瓶颈识别 使用Intel V-Tune Profiler分析性能问题,发现内存访问错误处理耗时过长。
WHV后端实现
代码覆盖率
使用基本块起始处的一次性软件断点,需要预先生成断点地址JSON文件。
脏内存追踪
使用WHVQueryGpaRangeDirtyBitmap接口跟踪脏内存。
确定性执行
通过断点钩子使nt!ExGenRandom的rdrand指令行为确定化。
性能优化
实现Ram_t类通过内存换CPU时间的方式优化断点恢复性能。
KVM后端实现
共享内存GPR访问
通过mmap映射共享内存区域访问Guest寄存器。
按需分页
使用userfaultfd实现按需分页:
|
|
超时机制
通过PMU(性能监控单元)在指定指令数后触发中断。
分布式架构
将wtf重构为客户端-服务器架构,服务器维护覆盖率、语料库和变异器,客户端作为运行器返回结果,充分利用硬件资源进行语料库最小化。
成果总结
通过wtf发现了IDA多个组件(libdwarf64.dll、dwarf64.dll、elf64.dll、pdb64.dll)中的数十个独特崩溃,包括空指针解引用、栈溢出、除零错误、无限循环、释放后使用和越界访问等问题。
根据lighthouse数据,实现了elf64.dll约80%、dwarf64.dll约50%、libdwarf64.dll约26%的代码覆盖率,使用约2.4k个文件(总计17MB)的最小语料集。
工具开发过程中还创建并开源了多个辅助项目:lockmem、inject、kdmp-parser和symbolizer。