what the fuzz一个分布式的、代码覆盖率引导的、跨平台的基于快照的模糊测试器,专为攻击运行在Microsoft Windows和Linux用户模式(实验性!)上的用户模式或内核模式目标而设计。
what the fuzz(简称 wtf)是一个分布式的、代码覆盖率引导的、可定制的、跨平台的基于快照的模糊测试器,专为攻击运行在Microsoft Windows或Linux(实验性,参见 linux_mode)上的用户模式或内核模式目标而设计。目标的执行可以在带有 bochscpu 的模拟器内进行(最慢,最精确),在使用 Windows Hypervisor Platform APIs 的 Windows VM 内,或在使用 KVM APIs 的 Linux VM 内(最快)。
它在广泛的软件中发现了内存损坏漏洞:IDA Pro,一款流行的 AAA游戏,Windows内核,Microsoft RDP客户端,NVIDIA GPU显示驱动程序 等。
编译后的二进制文件可以从 CI artifacts 或 Releases 部分获取,适用于Windows和Linux。
如果你想了解更多关于它的历史或如何在真实目标上使用它,建议阅读以下文章以入门 🔥
尝试这些功能的最佳方式是使用 fuzzer_hevd / fuzzer_tlv_server 模块。你可以下载 target-hevd.7z / target-tlv_server.7z 存档并将其解压到 targets/ 目录中。这些存档包含每个目标预期的目录树:
inputs 是存放输入测试用例的文件夹,outputs 是存放当前最小集文件的文件夹,coverage 是存放 .cov 文件的文件夹,crashes 是存放崩溃文件的文件夹,state 是存放内存转储(mem.dmp)、CPU状态(regs.json)以及符号存储(symbol-store.json)的文件夹。符号存储是一个简单的JSON文件,在Linux系统上用于指定断点位置,因为这些平台不支持符号/dbgeng。wtf 在每次在Windows上运行目标时会在运行时生成此文件。以下假设你已经下载了最新版本附带的 target-hevd.7z 文件,并将其解压到你的 wtf 克隆的 targets 目录中。你应该有 wtf/targets/hevd,其中包含 inputs / outputs 等目录。
服务器本质上是大脑,负责跟踪所有状态:聚合的代码覆盖率、语料库,它生成测试用例并将其分发给客户端。
以下是启动本地服务器节点的方法:```text wtf.exe master --name hevd --max_len=1028 --runs=10000000
`max_len` 选项用于限制生成的测试用例的大小,`runs` 指定生成的测试用例数量,`address` 定义 **wtf** 需要监听的地址,`target` 是包含我们上述目录树结构的目录(用户也可以选择使用 `--input` / `--output` / `--crashes` 覆盖这些目录),`name` 指定你的模糊测试模块名称,以便主控节点在你定义了生成器函数时调用它。
<p align='center'>
<img src="https://assets.kitploit.com/production/public/readmes/4699/4a03fc75eed3ed5a92f7f10def697dbf220ae36b0701f05b37759eb432fc0fd9.webp">
</p>
### 模糊测试节点
客户端节点运行由服务器生成并分发的测试用例,并将结果(代码覆盖率、结果等)反馈给服务器。
以下是启动一个使用 *bochscpu* 后端的客户端节点的方法:```text
wtf.exe fuzz --name hevd --limit 10000000
fuzz子命令需要与name选项一起使用,以指定要使用的fuzzer模块,backend用于指定执行后端,limit用于指定每个测试用例最多执行的指令数(根据后端的不同,该选项的含义有所不同)。
如果您想要运行一个测试用例(或包含多个测试用例的文件夹),可以使用run子命令。
以下是运行crash-0xfffff764b91c0000-0x0-0xffffbf84fb10e780-0x2-0x0测试用例的方法:```
wtf.exe run --name hevd --limit 10000000 --input crashes\crash-0xfffff764b91c0000-0x0-0xffffbf84fb10e780-0x2-0x0
<p align='center'>
<img src="https://assets.kitploit.com/production/public/readmes/4699/49b3ca8582d6314724e5615c687472499d5f8f41d04c9543c0a6a070c51f56f8.webp">
</p>
### 精简语料库
要精简语料库,你需要使用一个服务器节点和尽可能多的客户端节点,就像进行模糊测试任务一样。只需将 `runs` 选项设置为 0 即可。
以下是将 `outputs` 中的语料库精简到 `minset` 目录的方法(同时展示了如何覆盖 `inputs` 和 `outputs` 目录):```
wtf.exe master --name hevd --max_len=1028 --runs=0 --inputs=outputs --outputs=minset
在执行后端中进行内省的主要机制是生成执行跟踪。bochscpu 是执行此操作最快的后端,因为在其他后端退出VMX模式的代价非常高。
以下是针对测试用例 crash-0xfffff764b91c0000-0x0-0xffffbf84fb10e780-0x2-0x0 生成执行跟踪的方法:```
wtf.exe run --name hevd --limit 10000000 --input crashes\crash-0xfffff764b91c0000-0x0-0xffffbf84fb10e780-0x2-0x0 --trace-type=rip
<p align='center'>
<img src="https://assets.kitploit.com/production/public/readmes/4699/340b4b251e378eaf21c6f5ffdd4f013bd2cef89f23a604cffd0ebf897d27c71f.webp">
</p>
要符号化执行轨迹,应使用 [symbolizer-rs](https://github.com/0vercl0k/symbolizer-rs)。以下是如何符号化上述生成的 `crash-0xfffff764b91c0000-0x0-0xffffbf84fb10e780-0x2-0x0.trace` 执行轨迹:```
symbolizer-rs.exe --trace crash-0xfffff764b91c0000-0x0-0xffffbf84fb10e780-0x2-0x0.rip.trace
如果你发现自己需要更多的上下文感知能力,bochscpu 后端允许你生成可加载到 Tenet 追踪探索器中的执行追踪。下文中,我从 memmove 中的一个崩溃开始,回溯以找出源指针的来源(用户模式!):```
wtf.exe run --name hevd --limit 10000000 --input crashes\crash-0xfffff764b91c0000-0x0-0xffffbf84fb10e780-0x2-0x0 --trace-type=tenet
<p align='center'>
<img src="https://assets.kitploit.com/production/public/readmes/4699/34df2f5ca8f378db664d3f01bcbefdd43409e300d256d50e3f4f630eb06cc7be.webp">
</p>
### 生成代码覆盖率追踪
要生成代码覆盖率追踪,您只需使用 `run` 子命令并带上 `--trace-type=cov` 选项。
以下是您如何为 `minset` 文件夹内的所有文件生成代码覆盖率追踪,并将它们存储在 `coverage-traces` 文件夹中的方法:```
wtf.exe run --name hevd --input minset --trace-path=coverage-traces --trace-type=cov
这些追踪结果无法直接加载到 lighthouse 中,因为它们尚未被符号化。
以下是你如何对 coverage-traces 文件夹内的所有文件进行符号化,并将结果写入 coverage-traces-symbolized:```
symbolizer-rs.exe --trace coverage-traces -o coverage-traces-symbolized --style modoff
<p align='center'>
<img src="https://assets.kitploit.com/production/public/readmes/4699/8a3c3ca18571abef16a9f87f736fe4d52ac102f41095eab4db32533cb5b198b4.webp">
</p>
最后,你可以在 [lighthouse](https://github.com/gaasedelen/lighthouse) 中加载这些文件:
<p align='center'>
<img src="https://assets.kitploit.com/production/public/readmes/4699/35d9c25c3d26abf5761fec9e7848ba09d090c7aab32322a740732d82e85964e8.webp">
</p>
此外,如果你不关心单独的代码覆盖率,master 分支维护了一个 `coverage.cov` 文件,其中包含了已被执行的唯一聚合代码覆盖率。这使得在模糊测试期间快速检查全局代码覆盖率变得非常容易。
## 它是如何工作的?
**wtf** 通过 *执行后端* 运行用户态和内核态,并依赖用户在目标中插入测试用例。与其他传统的模糊测试工具不同,**wtf** 并不承担大部分繁重的工作;用户需要自己完成。用户需要非常了解被测试目标,并且接入一个新目标是一个需要时间的迭代过程。不过,如果你准备好进行黑客操作,它提供了很大的灵活性 :)
接入目标的通常工作流程如下:
1. 将你的目标运行在一个运行 Windows 的 Hyper-V 虚拟机中,配置一个虚拟 CPU 和 4GB RAM。
1. 使用 [KD](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/) 将目标置于所需状态。例如,要针对 [HEVD](https://github.com/hacksysteam/HackSysExtremeVulnerableDriver) 的 IOCTL 处理程序,我选择在客户端调用 [DeviceIoControl](https://docs.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-deviceiocontrol) 之前,在用户态停止目标。这取决于你的目标,但你可能希望它接近你想模糊测试的代码。
```
kd> r
rax=000000dfd98ff3d0 rbx=0000000000000088 rcx=0000000000000088
rdx=00000000deadbeef rsi=0000000000000000 rdi=0000000000000000
rip=00007ff6f5bb111e rsp=000000dfd98ff380 rbp=0000000000000000
r8=000000dfd98ff3d0 r9=0000000000000400 r10=000002263e823055
r11=00007ff6f5bcb54d r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei pl nz na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
hevd_client!main+0xae:
00007ff6`f5bb111e ff15dc1e0100 call qword ptr [hevd_client!_imp_DeviceIoControl (00007ff6`f5bc3000)] ds:002b:00007ff6`f5bc3000={KERNEL32!DeviceIoControlImplementation (00007ff8`3e2e6360)}
```
1. 使用 [snapshot](https://github.com/0vercl0k/snapshot) 生成内核崩溃转储以及包含 CPU 状态的 `regs.json` 文件。我建议将这些文件转储到你的 `target` 目录下的一个 `state` 目录中(例如 `targets/hevd/state`):
```
kd> .load c:\work\codes\snapshot\target\release\snapshot.dll
kd> !snapshot -h
[snapshot] Usage: snapshot [OPTIONS] [STATE_PATH]
Arguments:
[STATE_PATH] The path to save the snapshot to
Options:
-k, --kind <KIND> The kind of snapshot to take [default: full] [possible values: active-kernel, full]
-h, --help Print help
kd> !snapshot c:\work\codes\wtf\targets\hevd\state
[snapshot] Dumping the CPU state into c:\work\codes\wtf\targets\hevd\state\regs.json..
[snapshot] Dumping the memory state into c:\work\codes\wtf\targets\hevd\state\mem.dmp..
Creating c:\\work\\codes\\wtf\\targets\\hevd\\state\\mem.dmp - Full memory range dump
0% written.
5% written. 1 min 50 sec remaining.
10% written. 1 min 17 sec remaining.
15% written. 1 min 30 sec remaining.
[...]
Wrote 4.0 GB in 1 min 32 sec.
The average transfer rate was 44.5 MB/s.
Dump successfully written
[snapshot] Done!
```
1. 创建一个 [fuzzer 模块](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_hevd.cc),编写将 [测试用例](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_hevd.cc#L20) 插入目标的代码,并定义 [各种](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_hevd.cc#L81) [条件](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_hevd.cc#L104) 来 [检测崩溃](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_hevd.cc#L115) 或 [测试用例的结束](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_hevd.cc#L69)。
1. 你还可以通过继承 [Mutator_t](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/mutator.h) 接口来创建自己的变异器/生成器。[fuzzer_tlv_server.cc](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_tlv_server.cc) 是一个很好的例子,可以帮助你理解如何实现自己的变异器/生成器。
此时,你应该开始迭代并验证模糊测试模块是否按预期工作。执行后端是一个黑盒,因此你应该生成执行轨迹以确保它经过正确的路径,执行正确的操作。在此阶段,我主要使用 [bochscpu](https://github.com/yrp604/bochscpu) 后端,因为它完全确定、启动快、可以生成执行轨迹、代码覆盖率自动获取等。总的来说,这是一个更好的开发和原型设计环境。
一旦你对模块满意,你可以开始考虑让它与 [winhv](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/whv_backend.h) / [kvm](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/kvm_backend.h) 后端一起工作,如果你需要在这些后端下运行的话。*bochscpu* 后端与其他后端之间的一个主要区别是,其他后端使用软件断点来提供代码覆盖率信息。因此,你需要将你想要覆盖的模块加载到 [IDA](https://hex-rays.com/IDA-pro/) 中,并使用 [gen_coveragefile_ida.py](https://github.com/0vercl0k/wtf/blob/HEAD/scripts/gen_coveragefile_ida.py) 脚本生成一个简单的 JSON 文件,该文件会被 wtf 加载。你也可以使用任何你喜欢的工具自己生成这个 JSON 文件:它基本上是一个基本块虚拟地址的列表。
你还可以通过使用 `!wow64exts.sw` Windbg 命令在创建快照之前切换到 64 位上下文来针对 [WoW64](https://docs.microsoft.com/en-us/windows/win32/winprog64/wow64-implementation-details) 应用程序(感谢 [@cube0x8](https://twitter.com/cube0x8) 分享这个技巧!):```
32.kd:x86> !wow64exts.sw
The context is partially valid. Only x86 user-mode context is available.
Switched to Host mode
32.kd> !snapshot
复杂目标通常也携带复杂的状态,你可能需要在一次会话中发送多个测试用例才能触发复杂问题。tlv_server.cc 就是一个这样的服务器示例,仅用一个测试用例来执行解析函数不足以发现漏洞。
要处理这种情况,请查看 fuzzer_tlv_server.cc,它展示了如何解决此问题的示例。
wtf 附带两个流行的通用变异器:libfuzzer 和 honggfuzz。你可能希望提供自己的变异器或自行生成测试用例。
为此,你可以继承 Mutator_t 接口,并在定义模糊测试模块时注册实例化变异器的函数:```c++ class CustomMutator_t : public Mutator_t { public: static std::unique_ptr<Mutator_t> Create(std::mt19937_64 &Rng, const size_t TestcaseMaxSize) { return std::make_unique<CustomMutator_t>(Rng, TestcaseMaxSize); } // ... };
Target_t target("target", Init, InsertTestcase, Restore, CustomMutator_t::Create);
查看 [fuzzer_tlv_server.cc](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_tlv_server.cc) 模块中的 [CustomMutator_t](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_tlv_server.cc) 类,获取完整示例。
## 执行后端
在本节中,我简要介绍各个执行后端之间的差异。
### bochscpu
- ✅ 全系统代码覆盖率(可通过 `--edges` 获得边覆盖率),
- ✅ 按需分页,
- ✅ 超时是指令数,非常精确,
- ✅ 支持完整的执行跟踪,
- ✅ 完全确定性,
- ❌ 短执行时速度良好,但长执行时速度不佳(在我 fuzzing IDA 时,比 KVM 慢约 100 倍)。
### whv
- ✔ 通过软件断点实现代码覆盖率,
- ❌ 按需分页导致启动缓慢(因为它需要将完整的崩溃转储加载到内存中),
- ✔ 通过计时器实现超时,
- ✅ 支持完整的执行跟踪,但速度慢(退出 VMX 开销大),
- ✔ 如果手动处理非确定性的来源(例如,修补使用 `rdrand` 的 `nt!ExGenRamdom`),则是确定性的,
- ✔ 长执行时速度似乎还可以(但 whv 中存在大量瓶颈;在我 fuzzing IDA 时,比 kvm 慢约 10 倍)。
### KVM
- ✔ 通过软件断点实现代码覆盖率,
- ✅ 通过 UFDD 支持按需分页,
- ✔ 通过计时器实现超时。✅ 如果硬件支持 PMU 虚拟化,则在 X 条退役指令后用于生成 [PMI](https://forum.osdev.org/viewtopic.php?f=1&t=27040) (`MSR_IA32_FIXED_CTR0`),
- ✅ 支持完整的执行跟踪,但速度慢(退出 VMX 开销大),
- ✔ 如果手动处理非确定性的来源(例如,修补使用 `rdrand` 的 `nt!ExGenRamdom`),则是确定性的,
- ✅ 长执行时速度最快(约 5 亿 - 15 亿条指令;在我 fuzzing IDA 时,比 *bochscpu* 快约 100 倍,比 *whv* 快约 10 倍)。
## 构建
[CI](https://github.com/0vercl0k/wtf/actions/workflows/wtf.yml) 在 Ubuntu 上使用 [clang++](https://clang.llvm.org/) / [g++](https://gcc.gnu.org/gcc-11/) 构建 **wtf**,在 Windows 上使用 Microsoft 的 [Visual Studio](https://visualstudio.microsoft.com/vs/community/),在 OSX 上使用 [clang++](https://clang.llvm.org/)。
要自行构建,您需要启动 *Visual Studio 开发者命令提示符*,然后运行 [build-release.bat](https://github.com/0vercl0k/wtf/blob/HEAD/src/build/build-release.bat)(使用 [Ninja](https://ninja-build.org/) 生成器)或运行 [build-release-msvc.bat](https://github.com/0vercl0k/wtf/blob/HEAD/src/build/build-release-msvc.bat) 来生成 Visual Studio 解决方案文件:```
(base) wtf\src\build>build-release.bat
[...]
[2/2] Linking CXX executable wtf.exe
(base) wtf\src\build_msvc>..\build\build-release-msvc.bat
[...]
Finished generating code
wtf.vcxproj -> wtf\src\build_msvc\RelWithDebInfo\wtf.exe
Building Custom Rule wtf/src/CMakeLists.txt
特别感谢: