构建新一代快照模糊测试工具与IDA逆向工程实战

本文详细介绍了从零开发wtf快照模糊测试工具的过程,涵盖bochscpu、whv和KVM后端实现,以及如何针对IDA Pro进行逆向工程与漏洞挖掘,涉及代码覆盖率、内存管理和性能优化等核心技术。

构建新的快照模糊测试工具与模糊测试IDA

引言

2020年1月,我设定了开发自定义模糊测试工具的目标。在与yrp604讨论后,决定开发一款可用于模糊测试任何Windows目标(用户或内核模式、应用程序或服务、内核或驱动程序)的工具。计划开发基于Windows快照的模糊测试工具,在虚拟机或模拟器中运行目标代码,允许用户通过断点进行插桩,并提供现代模糊测试工具的基本功能:代码覆盖率、崩溃检测、通用变异器、跨平台支持、快速恢复等。

为了验证工具的有效性,选择IDA Pro作为目标,原因包括:它是复杂的Windows用户模式应用程序,解析多种二进制文件,启动缓慢(快照方法可加速模糊测试),且提供漏洞赏金。

架构

用户使用流程:

  1. 在目标中找到接近处理攻击者控制数据的位置,使用Windows内核调试器中断并置目标于所需状态。
  2. 生成内核转储并提取CPU状态。
  3. 编写模块告知wtf如何插入测试用例,定义退出条件。

wtf运行目标代码,跟踪代码覆盖率,检测崩溃,跟踪脏内存,并从内核转储恢复脏物理内存和重置CPU状态,生成新测试用例并重复。

Bochscpu基础

bochscpu是Bochs的CPU部分,作为Rust库提供C绑定,是软件CPU,能运行Intel 64位代码,了解分段、环、MSR等,不使用Bochs设备,更轻量。

内存懒加载:启动时无物理内存,bochs通过回调通知客户机访问未映射的物理内存。回调分配页,用转储内容填充,若转储中无该页则填零。

创建上下文:调用bochscpu_cpu_new创建虚拟CPU,bochscpu_cpu_set_state设置状态。

注册钩子:通过bochscpu_hooks_t结构注册执行前后、内存访问、中断、异常等钩子。例如,代码覆盖率通过执行前钩子跟踪。

构建基础功能

内存访问设施

  • 写入物理内存:通过bochscpu_mem_phy_translate查询GPA到HVA页的映射。
  • 写入虚拟内存:模拟MMU解析页表获取GPA,再写入。

插桩执行流

通过断点实现崩溃检测和用户功能。在执行前钩子检查地址是否有注册断点并调用回调。

处理无限循环

使用执行后钩子计数指令,限制测试用例执行。

跟踪代码覆盖率

执行前钩子将地址加入集合。

跟踪脏内存

通过内存访问钩子跟踪写入的内存页,快速恢复时仅恢复脏页。

通用变异器

采用libfuzzer和honggfuzz的变异器。

语料库存储

代码覆盖率作为适应度函数,生成新覆盖的测试用例加入语料库。

检测上下文切换

通过TlbControlHook回调检测cr3变化,停止执行以避免无关代码。

调试符号

使用IDebugClient/IDebugControl COM对象避免硬编码地址。

跟踪生成

生成执行跟踪用于调试和验证后端执行正确性。

崩溃检测

挂钩ntdll!RtlDispatchExceptionnt!KiRaiseSecurityCheckFailure检测用户模式崩溃。

配置IDA:赤脚走入沙漠

针对加载器插件,选择ELF作为目标。初始尝试带GUI的IDA,处理Qt和win32k相关问题后,发现IDA有简化选项:

  • IDA_NO_HISTORY=1减少注册表访问。
  • -B选项允许命令行批处理模式。
  • TVHEADLESS=1避免GUI/Qt问题。

插入测试用例

IDA通过freadfseek等惰性读取输入文件。实现文件I/O钩子层,将客户机文件I/O传递到主机,读取许可证、配置文件等,并拦截IDA生成的.id0、.id1等文件写入。

问题与解决

  1. 预加载DLL:IDA加载所有加载器以识别文件类型。通过inject工具在生成快照前注入DLL,避免文件I/O。
  2. 换出内存:Windows内存可换出到页面文件,访问触发#PF,需从页面文件加载。使用lockmem工具锁定进程工作集中的虚拟内存范围。
  3. 手动软页错误:有时虚拟内存写入失败因PTE无效。尝试虚拟到物理转换,若失败则插入页错误到客户机,让页错误处理程序修复PTE。
  4. KVA影子:用户@cr3不包含内核模式页表部分,无法设置内核模式断点。通过注册表禁用KVA影子。
  5. 识别瓶颈:使用Intel V-Tune Profiler分析性能,发现内存访问错误处理耗时过长。

希望的诞生

经过大量工作,能执行ELF加载器并看到输出窗口消息。IDA加载elf64.dll,初始化数据库和B树,加载处理器模块,创建段,处理重定位,最后加载dwarf模块解析调试信息。

速度需求:whv后端

IDA模糊测试速度极慢(约0.01测试用例/秒),决定使用whv实现执行后端。

代码覆盖率

使用基本块起始处一次性软件断点。用户需生成断点地址JSON文件,wtf初始化时使用。虽需为每个模块生成JSON文件,但是足够好的折衷。

脏内存

whv提供WHvQueryGpaRangeDirtyBitmap跟踪脏内存。

跟踪

尝试使用陷阱标志生成执行跟踪,但发现缺失页面错误等问题,最终仅生成代码覆盖率跟踪。

超时

使用定时器限制执行时间,不如bochscpu精确但是唯一解决方案。

插入页错误

使用WHvRegisterPendingEventWHvX64PendingEventException事件类型插入页错误。

确定性

nt!ExGenRandom使用rdrand导致非确定性。通过断点模拟其行为为确定性实现。时间戳计数器也是非确定性源,但未导致重大问题。

问题与优化

  1. 代码覆盖率断点不免费:恢复断点效率低,使用Ram_t类复制页并设置断点,恢复时从复制或转储恢复内容,以内存换CPU时间。
  2. IDA代码覆盖率:IDA将切换表标记为代码而非数据,导致wtf破坏切换表。最新版IDA未遇此问题。
  3. 优化轮次WHvQueryGpaRangeDirtyBitmap慢,模拟该功能通过EPT映射内存为读/执行,在写入内存错误时跟踪脏污。WHvTranslateGva也慢,通过自行走页表模拟。

收益

whv性能比bochscpu提高约15倍,虽低于预期的100倍,但显著提升。

速度与激情:KVM后端

为在合适硬件上模糊测试IDA,开发KVM后端。KVM API与whv相似,通过/dev/kvm暴露用户模式API。

通过共享内存的GPR

KVM允许映射共享内存区域访问客户机GPR,避免每次IOCTL。

按需分页

使用userfaultfd实现按需分页,线程轮询并处理请求。

超时

KVM向客户机暴露性能监控单元(PMU),编程PMU在任意指令数后触发中断,通过挂钩hal!HalPerfInterrupt捕获。

问题与解决

  1. 在云中运行:Amazon不支持嵌套虚拟化,Azure的SKU不支持unrestricted_guest功能。租用vultr裸机服务器,支持PMU,性能比whv快约10倍。
  2. 最小化1.6m文件语料库:将wtf重构为客户端-服务器架构,服务器拥有覆盖率、语料库和变异器,分发测试用例并接收覆盖率报告,充分利用硬件最小化语料库。

总结

本文介绍了wtf的诞生,这是一款分布式、代码覆盖率引导、可定制、跨平台的快照模糊测试工具,用于攻击Windows用户和/或内核模式目标。并开源了多个小项目:lockmem、inject、kdmp-parser和symbolizer。

从零开始,在IDA多个组件(libdwarf64.dll、dwarf64.dll、elf64.dll、pdb64.dll)中发现数十个独特崩溃,类型包括空解引用、栈溢出、除零、无限循环、释放后使用和越界访问。所有发现编译在Github仓库fuzzing-ida75中。

模糊测试约一个月,大部分崩溃在前两周出现。根据lighthouse,覆盖了elf64.dll的80%、dwarf64.dll的50%和libdwarf64.dll的26%,最小语料库约2.4k文件,共17MB。

感谢IDA Hex-Rays团队快速处理和修复报告,推荐尝试他们的漏洞赏金计划。感谢yrp604和__x86校对文章。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计