peafl64 是一款用于 Windows 中 x64 PE 的静态插桩工具。
静态插桩是指编辑可执行文件并在其中特定位置添加代码的做法。
该插桩会在二进制文件的每个基本块开头添加代码,以 AFL 兼容的方式记录执行流程。
它使我们能够在无需访问源代码的情况下,对用户模式(使用 WinAFL)和内核模式(使用 kAFL)下的二进制文件进行模糊测试。
还有其他模糊测试 Windows 二进制文件的方法;在本项目中,我们选择专注于静态插桩,因为它是最快的方法。
本项目基于 wmliang 的 pe-afl 工具构建,并增加了 x64 支持。
插桩脚本需要 IDA 的分析输出。
要生成该输出,请在 IDA 中运行提供的 ida_dumper.py 脚本。
该脚本需要 IDA 7+ 和 python3.8+。
usage: pe_afl.py [-h] [-n] [-cb] [-tf] [-te THREAD_ENTRY] [-nt NTOSKRNL] [-e ENTRY] [-l ENLARGE] [-v] [-lf] pefile ida_dump
positional arguments:
pefile Target PE file for instrumentation
ida_dump dump.json from IDA (created by ida_dumper.py)
optional arguments:
-h, --help show this help message and exit
-n, --nop Instrument with NOPs for testing
-cb, --callback Instrument with a callback, which is in the helper driver that's written in C
-tf, --thread-filter Driver instrumentation that filters only on thread ID (must use "-te" with this option)
-te THREAD_ENTRY, --thread-entry THREAD_ENTRY
The address (RVA) of the thread's initialization function
-nt NTOSKRNL, --ntoskrnl NTOSKRNL
ntoskrnl.exe path for offset extraction (non-optional if instrumenting a driver)
-e ENTRY, --entry ENTRY
Inject code on entry point, ie. -e9090
-l ENLARGE, --enlarge ENLARGE
Enlarge factor for sections, default=4
-v, --verbose Print debug log
-lf, --logfile Print log to pe-afl64.log rather than stream
使用 NOP 对用户模式二进制文件进行插桩
PS pe-afl-64> python .\pe_afl.py -n C:\Work\cmd.exe C:\Work\cmd.exe.dump.json
[*] User-mode binary is being instrumented
[*] Single-thread instrument is on
[*] Preparing new sections
[*] Added section .text^
[*] Added section .cov
[*] Expanding relative jumps
[*] Expanded 3874 of 14353 branches
[*] Building address map
[*] Updating relative instructions
[*] Updating relocations...
[*] Updating Export table
[*] Updating load config
[*] Updating exception records
[*] Updating the PE headers
[*] Finalizing...
[*] Creating instrumented code
[*] Writing address mapping to C:\Work\cmd.exe.mapping.txt
[*] Updating jump tables
[*] Updated .text
...
[*] Removing temporary PE files
[*] Fixing PE checksum
[*] Instrumented binary saved to: C:\Work\cmd.instrumented.exe
使用进程 ID 过滤对内核模式二进制文件进行插桩
python .\pe_afl.py -l 6 -nt "C:\Work\ntoskrnl.exe" "C:\Work\driver.sys" "C:\Work\driver.sys.dump.json"
使用线程 ID 过滤并输出详细日志对内核模式二进制文件进行插桩
python .\pe_afl.py -v -tf -te 0x40000 -l 6 -nt "C:\Work\ntoskrnl.exe" "C:\Work\ntoskrnl.exe" "C:\Work\ntoskrnl.exe.dump.json"
首先,你需要检查你的机器是通过 BIOS 还是 UEFI 启动。
对于 Hyper-V 虚拟机:第 1 代虚拟机基于 BIOS,第 2 代基于 UEFI。
如果你的机器基于 BIOS,则需要修补 winload.exe:
ImgpValidateImageHashmov eax, edi 替换为 xor eax, eaxbcdedit /set path \Windows\system32\winload2.exe如果你的机器使用 UEFI 启动,则使用 EfiGuard 工具来修补 winload.efi。
如果使用 Hyper-V 管理器,命令速查表如下:
FS1:
cd EFI/Boot
Load EfiGuard.Dxe.efi
FS0:
\EFI\Boot\bootx64.efi
然后对你选择的驱动程序进行插桩。要在 Windows 机器上加载已插桩的驱动程序,必须对其进行签名, 而自签名证书足以满足操作系统的要求:
# In elevated powershell terminal
$c = New-SelfSignedCertificate -Type CodeSigningCert -KeyUsage DigitalSignature -Subject 'CN=Microsoft Windows, O=Microsoft Corporation, L=Redmond, S=Washington, C=US'
Set-AuthenticodeSignature .\driver.instrumented.sys -Certificate $c -Force
如果你插桩的驱动程序已被系统使用,请使用以下命令(以管理员身份)替换驱动文件:
set NAME=mydriver.sys
icacls %NAME% /save C:\windows\temp\%NAME%.icacls
takeown /F %NAME%
icacls %NAME% /grant Everyone:F
move %NAME% %NAME%.bak
move instrumented_driver.sys %NAME%
icacls . /restore C:\windows\temp\%NAME%.icacls
然后运行:
bcdedit /set recoveryenabled no
bcdedit /set nointegritychecks on
shutdown -t 0 -r
与 WinAFL 的集成方式是通过使用提供的头文件编译一个 harness。
在头文件旁边还有 example.c,这是一个示例程序,演示了如何使用它们。
所提供的头文件是对 WinAFL 自带头文件的一处轻微修改,目的是与另一个名为 Syzygy 的静态二进制插桩工具集成。
我们与 kAFL 集成的方式非常简单。
通常,kAFL harness 运行在虚拟机上,并通过特殊的“hypercalls”与模糊测试器的前端通信。
这些 hypercalls 让模糊测试器执行许多操作,其中包括从 IntelPT 加载覆盖率数据并将其解析为 AFL 位图。
由于 peafl64 使 IntelPT 跟踪变得不再必要,我们必须准备一种方式将覆盖率数据传输给模糊测试器。
因此,我们为 qemu 和 kvm 扩展了“hypercalls”,使运行在虚拟机中的(用户模式)harness 能够发送它通过辅助驱动程序收集到的覆盖率数据。
这里具体介绍 ESXi 上的设置,但同样适用于 AWS 等其他虚拟化平台。
设置非常简单:
install.sh qemu 步骤,而是运行 install.sh qemu_sbi要使用 kAFL 和 peafl64 进行模糊测试,我们需要搭建一台模糊测试机器:
除此之外,使用我们的 kAFL fork 进行模糊测试与使用 kAFL 进行常规模糊测试相同。
步骤概览:
首先,我们使用 IDA 来查找并分析感兴趣的指令和位置。
分析会生成一个 dump.json 文件,其中包含所有这些信息的 json 格式。
instrument.py 包含插桩逻辑,其流程在 process_pe 函数中概述。
要对二进制文件进行插桩,我们会复制其可执行节,所有插桩代码都会放在这些复制的节中。
接下来,我们处理所有相对寻址指令,并确定是否需要以及如何处理它们。
假设从地址 X 到地址 Y 有一个短跳转(short jmp)。我们在 X 和 Y 之间插入插桩代码,现在目标位于地址 Z(Z = Y + len(instrumentation))。
这意味着我们必须修改这条跳转指令。
如果地址 Z 现在位于短跳转(short jmps)的范围之外,我们需要将跳转从 short 转换为 far。(instrument.py:expand_relative_instructions)
例如:
short jmp 0x57 (eb 55) -> near jmp 0x604 (e9 ff 05 00 00)对于 call、loop 和条件跳转等指令也是如此。
另一个需要处理的常见情况是 x64 汇编中引入的 rip 相对寻址指令:
call [rip+0x1000]我们会更新以下内容,使其指向插桩后的代码:重定位表、导出表、加载配置、TLS 目录和异常记录。
所有地址都会从原始节更新到对应的插桩节。(instrument.py:update_addr)
PE 头也会被更新,以反映 PE 结构的更改——新增的节、更改的入口点和 PE 大小。(instrument.py:update_pe_headers)
此外,peafl64 能够对 Windows 内核进行插桩。为此,它解析并更新了动态值重定位表(DVRT)和 SSDT,并处理 PatchGuard 的特殊细节。
DVRT 是一个编译器生成的表,描述了加载 PE 时需要更改的地址位置。
它用于增强 KASLR,并有助于缓解 Spectre 漏洞(1, 2, 3)。
DVRT 通过一组模仿该表结构的类来解析(drt.py; instrument.py:get_updated_dynamic_relocs)
处理和更新 SSDT 并不那么简单。
它的地址没有被导出,因此我们只能依赖符号或启发式方法来定位它。
我们的解决方案采用启发式方法,使用 NtWaitForSingleObject 作为所有 Windows 10 版本的常量标记,并相对它来查找 SSDT 的地址。(instrument.py:ntoskrnl_update_KiServiceTable)
这种方法适用于所有 Windows 10 内核,但对于其他内核版本则需要调整。