模糊测试器开发1:新机器的灵魂
引言与致谢
长期以来,我一直想在博客上利用周末和空闲时间开发一个模糊测试器,但由于种种原因,始终未能构思出一个既有教育价值又能为模糊测试社区提供实用工具的项目。最近,出于Linux内核漏洞利用的需求,我对Nyx产生了浓厚兴趣。Nyx是一款基于KVM的虚拟机模糊测试器,可用于对传统上难以测试的目标进行快照模糊测试。
面对模糊测试中的目标复杂性(暂时抛开输入生成和细节问题),通常有两种方法:
- 目标简化:将目标的一部分隔离出来进行测试,例如将内核子系统的一小部分提取出来编译成用户态应用程序。但这种方法限制了可探索的目标状态。
- 全系统沙盒:将目标完全沙盒化,以便控制其执行环境并测试整个目标。这是Nyx等模糊测试器采用的方法。
在了解Nyx工作原理的过程中,我重新观看了Gamozolabs(Brandon Falk)对Nyx论文的流媒体评审。这次流媒体不仅展示了Nyx的强大功能,还提到了Gamozo之前构建的一个利用Bochs模拟器进行快照模糊测试的架构。这个架构与我正在设计的一个沙盒工具有很多共同之处,因此我决定在此基础上开发自己的模糊测试器。
项目选择标准
这个模糊测试架构满足了我对博客项目的多个标准:
- 设计相对简单
- 允许添加几乎无限的内省工具
- 适合迭代开发周期
- 可扩展并可用于我购买的服务器
- 可测试Linux内核和其他操作系统(Windows、macOS)的用户态和内核组件
- 设计与现有开源模糊测试工具相比非常独特
- 可从零开始设计,与LibAFL等现有灵活工具良好配合
- 没有公开源代码,可自由实现
- 可移植到Windows等其他平台
- 允许进行大量学习和底层计算研究
Bochs简介
Bochs是一款x86全系统模拟器,能够在软件模拟的硬件设备上运行整个操作系统。与QEMU相比,Bochs更小、更简单,但性能较差且用例较少。Bochs采用完全软件模拟的方式,不关心性能,这使得它成为运行x86系统的理想工具。我们将使用Bochs作为目标执行引擎,目标代码将在Bochs内部运行。
模糊测试器架构
根据Gamozo在流媒体中描述的信息,我们的模糊测试器设计如下:
- 创建一个“模糊测试器”进程,该进程将执行Bochs,而Bochs又执行我们的测试目标。
- 通过快照和恢复Bochs(包含目标及其所有模拟状态)来实现目标的快照和恢复。
- 将Bochs沙盒化,使其无法与操作系统交互,从而确保其确定性。
内存加载ELF
为了将Bochs加载到内存中,我选择将其编译为-static-pie
ELF文件。这种ELF文件没有加载位置的期望,其_start
例程包含执行自身重定位所需的逻辑。
ELF头解析
我解析了ELF头和相关程序头,提取了加载段的信息。关键数据结构包括:
|
|
内存映射与段加载
使用mmap
分配足够的内存来容纳可加载段,然后遍历程序头,将段数据从文件复制到分配的内存中,并使用mprotect
设置适当的内存权限。
设置Bochs的堆栈
设置堆栈是中最复杂的部分。我需要构建一个包含参数向量(argv)、环境变量(envp)和辅助向量(auxv)的堆栈。辅助向量包含程序的关键信息,如入口点(AT_ENTRY)、程序头地址(AT_PHDR)和程序头数量(AT_PHNUM)。
堆栈构建逻辑
- 分配堆栈内存:使用
mmap
分配1MB的堆栈空间。 - 构建堆栈数据:从堆栈底部开始,依次放置结束标记、参数字符串、辅助向量、环境变量终止符、参数向量终止符和参数指针。
- 计算绝对地址:根据堆栈长度和字符串偏移量计算参数字符串的绝对地址。
辅助向量成员
我包含了以下辅助向量成员:
- AT_ENTRY:程序入口点
- AT_PHDR:程序头数据指针
- AT_PHNUM:程序头数量
- AT_RANDOM:随机种子指针(用于栈金丝雀值)
- AT_NULL:辅助向量终止符
执行加载的程序
一切就绪后,通过汇编代码跳转到程序入口点并开始执行:
|
|
测试结果
测试程序成功运行,解析了命令行参数并正常退出。随后,我编译了Bochs作为-static-pie
ELF并成功加载执行:
|
|
下一步
接下来的步骤包括:
- 开发上下文切换例程,用于在模糊测试器和Bochs执行之间切换。
- 熟悉Bochs,尝试在 vanilla Bochs 中运行目标。
- 在模糊测试器中运行目标。
资源与致谢
- 感谢Faster Than Lime的博客文章,帮助我了解如何在内