Rewind 是一款基于快照的覆盖率引导模糊测试器,针对 Windows 内核组件。
其思路是从一个正在运行的系统的快照开始。该快照由物理内存页面以及 CPU 状态组成。
该状态用于设置虚拟 CPU 的初始状态。通过利用按需分页,只有执行目标函数所需的页面才会从快照中读取。
由于我们使用一个专用的虚拟机,其中仅包含对执行目标函数有用的物理内存页面,因此恢复快照的速度很快。
目前有 2 个后端可用:
WHVP 后端利用 WHVP(Windows 虚拟机监控程序平台)API 来提供对 Hyper-V 分区的访问。详见
https://docs.microsoft.com/en-us/virtualization/api/hypervisor-platform/hypervisor-platform
以获取更多详细信息。Bochs 后端利用 Bochs 模拟器
(https://bochs.sourceforge.io/)一个 KVM 后端正在开发中,应该很快就会可用。
Rewind 提供 2 个主要功能:
它还提供了一个基本的 TUI(终端用户界面),用于报告有关模糊测试的有用信息。
它已在 Windows 和 Linux 上进行了测试(目前 Linux 仅支持 bochs 后端)。
我一直很喜欢做内核漏洞研究,尤其是 Windows 内核。这个过程总是涉及静态分析和动态分析的结合。动态分析很快就会变得乏味。调试 / 崩溃 / 重启 / 重置所有断点的循环既缓慢又痛苦。当你想进行模糊测试时,通常需要你设置一台或多台虚拟机,外加一个内核调试器,并编写一些简陋的脚本来处理崩溃检测……
使用虚拟机做快照有帮助,但速度很慢。
2018 年,微软推出了一组名为 Windows 虚拟机监控程序平台(WHVP)的新 API。这些 API 允许设置一个带有若干虚拟处理器的分区(Hyper-V 术语中的 VM),并可以对虚拟机中发生的 VM exit 进行控制。这几乎就像在用户态拥有你自己的 VM-exit 处理器。做有用的事情非常方便,例如 Simpleator 或 applepie。
于是我开始摆弄 WHVP,并制作了第一个 PoC,让我能够在 Hyper-V 分区中执行一些 shellcode。它是用 Python 编写的,速度相当慢。这个最初的 PoC 很快演变成了一种基于快照的追踪器。我希望有一个能够引导虚拟 CPU 并且相当容易设置的工具。由于我已经在使用内核调试器来处理我的目标,我决定使用 WinDbg 生成的内核转储作为我的快照。有了这个,我只需要设置一个带虚拟 CPU 的分区。虚拟 CPU 的上下文使用从转储中获取的上下文来设置。每当虚拟 CPU 需要物理页面时,我就使用转储中的页面。
有了这个,我能够将转储的状态 fork 到一个分区中,然后恢复执行。这让我可以轻松地追踪目标函数的执行。通过修改参数并还原分区的内存状态,对目标进行模糊测试也非常容易。
这项工作已在 2020 年的 SSTIC 会议上展示,并发布在 github 上。
该工具实现了 2 种获取覆盖率的方式。第一种利用经典的 TF(陷阱标志)来在每条指令上产生 INT1 中断。这需要修改目标,而且速度很慢。我更倾向于使用 MONITOR 陷阱标志。但 WHVP 不提供这种可能性。
为了获得合适的性能(模糊测试所需),我决定降低覆盖率的精度,并添加一种模式,在这种模式下你只知道指令首次执行的时间。
为此,我用 0xcc 字节修补从快照中获取的页面(仅针对可执行页面)。当 CPU 执行这些被修补的指令时,虚拟机监控程序将捕获该异常,并用原始代码重写这些指令。
这就像在每条指令上都设置了一个唯一的软件断点。它在 95% 的情况下都能正常工作,但在特定的代码片段中(例如带有跳转表的代码)会失败,因为数据会被替换。
为了克服这个问题,一种选择是在映射代码之前先对其进行反汇编,并且只修补必要的内容(也许下次再做)。
在我的实验中,我遇到了使用 WHVP 时的几个限制。它很慢,真的非常慢。 VirtualBox 源代码中有一些有趣的评论 :)
因此,要获得合适的性能,你确实需要限制 VM exit,并且如果你想将 Hyper-V 用作追踪虚拟机监控程序,这是不兼容的(因为它需要大量的 VM exit)。
与此同时,我开始使用 bochs(特别是 插桩部分)来检查该工具获得的 trace(追踪记录)是否正确。Bochs 就像是某种预言机,用来查看我的 trace 是否存在分歧。
在进行完整 trace 时,Bochs 比 WHVP 更快,而且你还能获得内存访问以及其他有用的功能。
我决定将 bochs 添加为另一个后端。whvp 不再是一个合适的名称,于是我定下了 rewind。

rewind 是围绕我自己的工作流程设计的,用于在 Windows 平台上对内核驱动程序进行安全评估时使用。
第一步是在虚拟机内安装目标软件。由于我混合使用静态和动态分析,我还会设置一个内核调试器。
在 IDA 中打开一些随机的驱动程序后,我会很快开始瞄准某些函数。为此,我通常会用 windbg 设置一些断点,并结合 ret-sync,我就可以开始把玩了。
这正是 rewind 发挥作用的地方。与其编辑内存中的随机缓冲区、单步执行并注释 IDB 来大致了解发生了什么,我会用 windbg 拍摄快照,然后改用 rewind。
这将大大简化流程。拥有快照提供了很多优势。一切都是确定性的。你可以反复重放一个函数调用。如果目标函数看起来很有趣,你可以启动模糊测试器。你甚至可以关闭虚拟机,因为不再需要它了。
显然你需要 Rust(已在 Windows 和 Linux 上使用 Rust 1.50 测试过安装)。某些依赖项还需要 CMake。
首先克隆仓库:
$ git clone [email protected]:quarkslab/rewind.git
继续安装 bochs 后端
在 vendor 目录中克隆 bochscpu (https://github.com/yrp604/bochscpu) 仓库:
$ cd vendor
$ git clone https://github.com/yrp604/bochscpu
从 bochscpu-build (https://github.com/yrp604/bochscpu-build) 下载预构建的 bochs 构件
$ curl.exe -L --output bochs-x64-win.zip [artifact_url]
将 lib 和 bochs 文件夹解压到 bochscpu 检出目录中。
$ Expand-Archive -Path .\bochs-x64-win.zip -DestinationPath .\
$ copy -Recurse .\bochs-x64-win\msvc\* .\bochscpu\
在 Windows 上,WHVP 也将作为后端构建。
在提权的 PowerShell 会话中,使用以下命令检查 WHVP 是否已启用:
Get-WindowsOptionalFeature -FeatureName HypervisorPlatform -Online
FeatureName : HypervisorPlatform
DisplayName : Windows Hypervisor Platform
Description : Enables virtualization software to run on the Windows hypervisor
RestartRequired : Possible
State : Enabled
CustomProperties :
如果未启用,你可以使用 Set-WindowsOptionalFeature cmdlet 来启用。你还需要启用 Hyper-V。
你还需要安装 Windows SDK (10.0.19041.0)。你可以从 https://developer.microsoft.com/fr-fr/windows/downloads/windows-10-sdk/ 下载。
你需要安装 LLVM 并设置 LIBCLANG_PATH 环境变量(bindgen 需要)。详细说明请参阅 https://rust-lang.github.io/rust-bindgen/requirements.html。
$ $env:LIBCLANG_PATH="C:\Program Files\LLVM\bin"
在那里你应该能够构建 rewind(由于 bochscpu crate 中的 unwind_attributes,需要 nightly 版本):
$ cd rewind_cli
$ cargo +nightly build --release
rewind 二进制文件将位于 target/release 目录中。
你也可以使用 cargo 在本地安装:
$ cd rewind_cli
$ cargo +nightly install --path .
> error: failed to run custom build command for `zydis v3.1.1`
whvp-sys 将无法构建examples 目录中提供了一个利用 CVE-2020-17087 的基础教程
参见 TODO.md
hit 覆盖率模式时,追踪器会在某些函数上出现异常行为(某些 switch 表就是这种情况)。原因是每个字节都被软件断点替换(包括可执行页面中的数据,如果存在的话)。更好的做法是例如从反汇编器获取所有基本块的列表。该工具目前由 Quarkslab 在 Apache 2.0 许可证下开发和赞助。
向 @yrp604、@0vercl0k、Alexandre Gazet 致敬,感谢他们的帮助、反馈和想法。也感谢我在 Quarkslab 的所有同事!