【翻译】The art of fuzzing Windows Binaries - Bushido Security
简介
今天我们将深入研究灰盒模糊测试,通过测试闭源 Windows 二进制文件。这种模糊测试允许在不访问源代码的情况下对目标进行测试。为什么要进行这种模糊测试?由于它需要更多的设置和高级技能,较少的人倾向于寻找漏洞/能够发现漏洞。因此,这为你这个漏洞研究员扩大了发现新的未被发现漏洞的可能性。
为了实现这一目标,我们需要克服以下几个挑战:
-
代码插桩 -
找到相关的函数进行模糊测试 -
修改/补丁二进制文件使其可以进行模糊测试
虽然有很多可用于对 Windows 二进制文件进行模糊测试的解决方案,但在本章中我们将只关注WinAFL。WinAFL提供三种插桩方式:
-
通过DynamoRIO进行动态插桩 – 动态插桩是在程序运行时修改程序的指令。 -
通过Syzygy进行静态插桩 – 静态插桩是在编译时修改程序的指令。 -
通过 Intel PTrace进行硬件跟踪 – 异步记录程序控制流的硬件功能。
虽然每种方法都有其优缺点,但今天我们将重点关注通过DynamoRIO进行动态插桩。下图展示了WinAFL + DynamoRIO在模糊测试目标二进制文件时将执行的工作流程。
编译 WinAFL
-
如果你要构建 DynamoRIO 支持,请下载并构建 DynamoRIO 源代码或从https://github.com/DynamoRIO/dynamorio/releases下载 DynamoRIO Windows 二进制包 -
如果你要构建 Intel PT 支持,请在 WinAFL 源目录运行 git submodule update --init --recursive
拉取第三方依赖 -
打开 Visual Studio 命令提示符(如果要 64 位构建,则打开 Visual Studio x64 Win64 命令提示符)。注意,如果你要模糊测试 64 位目标,则需要 64 位 winafl.dll 构建,反之亦然。 -
进入包含源代码的目录 -
输入以下命令。修改-DDynamoRIO_DIR 标志以指向 DynamoRIO cmake 文件的位置(可以是完整路径或相对于源目录的路径)。
32 位构建:
mkdir build32cd build32cmake -G"Visual Studio 16 2019" -A Win32 .. -DDynamoRIO_DIR=..pathtoDynamoRIOcmake -DINTELPT=1cmake --build . --config Release
对于 64 位构建:
mkdir build64cd build64cmake -G"Visual Studio 16 2019" -A x64 .. -DDynamoRIO_DIR=..pathtoDynamoRIOcmake -DINTELPT=1cmake --build . --config Release
构建配置选项支持以下 cmake 配置选项:
-
-DDynamoRIO_DIR=..pathtoDynamoRIOcmake
– 需要用于构建 winafl.dll DynamoRIO 客户端 -
-DINTELPT=1
– 启用 Intel PT 模式。更多信息请参见https://github.com/googleprojectzero/winafl/blob/master/readme_pt.md -
-DUSE_COLOR=1
– 颜色支持(Windows 10 周年版或更高版本) -
-DUSE_DRSYMS=1
– Drsyms 支持(在可用时使用符号从-target_method 获取-target_offset)。启用此功能在 Windows 10 v1809 上已知会导致问题,不过有解决方法,请参见#145
寻找目标
找到合适的模糊测试目标并不总是容易的。关键在于找到一个足够复杂值得测试,但又容易理解要测试什么以及哪些功能有趣的软件。
一个好的策略是针对那些已知包含漏洞且在披露计划中反应积极的软件,找到这类软件的好方法是查看Zero Day Initiative的网站。
在这个部分有之前披露的漏洞,可以让你对测试的程序及其响应性有一个很好的概览。这里我们可以看到Netgear和D-link产品的漏洞被披露,这个网站上有大量之前披露的漏洞,你可以搜索并找到最感兴趣的目标。
由于对复杂目标进行模糊测试需要一些高级技能,如逆向工程、理解大型代码库等,我们将重点关注一个我专门为本课程创建的二进制目标。这是一个有漏洞的文件读取器,它以文件作为输入,将其内容复制到缓冲区,然后关闭文件。
你可以在这里下载文件,密码:"bushido" https://drive.google.com/file/d/1c-cOuzYbC-gOFW91a2EHKNpZTiPrVdBP/view?usp=sharing
修补二进制文件以允许模糊测试
不幸的是,许多软件使用某种对话框控制流,在执行某个任务之前会提示用户回答问题,比如"此文件已存在。你想覆盖它吗?"等。
这使得模糊测试过程变得不可能,因为它需要用户与对话框交互,这将阻止模糊测试器正常运行。这就是为什么我们现在要研究如何改进/修补二进制文件以使其可以进行模糊测试!
下载并安装Ghidra,启动应用程序然后创建项目目录和项目。导入 vulnerable_reader.exe,点击"Options.."并启用"Load Local Libraries From Disk"
加载库后,你可以通过按回车键或双击文件名开始逆向工程过程,它会弹出一个"Analyze"对话框,你可以在其中进行配置。
对于这个练习,不需要更改它,但是,我邀请你探索可用的选项及其功能。点击 OK 后,你会看到显示的二进制文件的反汇编代码,你需要等待一会儿让 Ghidra 分析整个二进制文件,你可以在屏幕右下角找到进度条:
如果你在分析后保存程序,将来就不需要再次分析它。这个二进制文件相当小,但请记住情况并不总是如此。我建议你在执行分析后立即将分析后的程序保存为副本。
现在分析已经完成,我们可以查看软件。调查这个二进制文件会很容易,因为我们已经知道对话框中使用的一个字符串,让我们打开_Search > Program text_然后在"fields"中输入"You clicked Yes!",启用"all fields",在 block 中启用"all blocks",然后点击"Search All"并双击结果中的第一个发现。
我们可以看到有两个可能的选项,要么函数允许你选择 yes 并关闭,要么选择 no 并关闭。这个函数没有真正的目的,但是,它在你点击之前阻止程序继续其流程,因此阻止你对其进行模糊测试。
一个值得关注的信息是 XREFS,它对应于调用此函数(FUN_00401000)的位置。这里我们可以看到该函数被 FUN_00401130 调用,让我们双击看看这个函数是什么。
让我们用 NOP 指令替换"CALL FUN_00401000"指令
如你所见,现在我们的指令后面跟着一堆"??"。这是因为初始指令比 NOP 指令(十六进制:90)大,所以我们需要用 NOP 指令替换"??"以尊重填充。更多信息https://en.wikipedia.org/wiki/Data_structure_alignment
结果必须看起来像这样:
现在将程序导出为 PE 文件,点击 File > Export Program 然后选择 Original File 并设置正确的路径:
让我们运行程序看看对话框是否还会出现:
太棒了!请记住,大多数程序都有更复杂的交互要求,而且本课程不是关于逆向工程的。然而,成功运行模糊测试活动的一个重要方面在于移除使模糊测试器变慢的因素,而 GUI 是其中的一大部分。如果你想继续研究模糊测试,你肯定应该对 RE 有一些兴趣。
函数偏移
WinAFL 使用一种技术来优化模糊测试过程,通过减少与 exec syscall和典型进程启动程序相关的慢执行时间。它不是为每次模糊测试尝试重新初始化目标程序,而是采用了受 fork server 概念启发的策略。
基本思想是通过提供随机化输入来执行程序,直到达到所需的模糊测试点。通过采用这种方法,每个subprocess只处理一个输入,有效地绕过了与 exact syscall操作相关的开销。
因此,当使用WinAFL对程序进行模糊测试时,如果在第三次调用期间达到所需的模糊测试点,例如,性能保持不变。然而,重要的优势在于减少了整个程序模糊测试的开销,从而导致更高效和有效的模糊测试会话。
这是一个说明这个过程的图表。
如何选择目标函数
目标函数在其生命周期内应该做这些事情:
-
打开输入文件。这需要在目标函数内发生,这样你就可以为每次迭代读取新的输入文件,因为输入文件在目标函数运行之间被重写。 -
解析它(这样你就可以测量文件解析的覆盖率) -
关闭输入文件。这很重要,因为如果输入文件没有关闭,WinAFL将无法重写它。 -
正常返回(这样 WinAFL 就可以"捕获"这个返回并重定向执行。通过 ExitProcess() 等方式"返回"将不起作用)
如何找到函数的虚拟偏移
-
使用像Ghidra和radare2这样的工具进行静态分析 -
使用WinDBG或x64dbg调试代码(设置断点并在运行时分析函数的参数) -
使用辅助工具如 API 监视器、进程监视器和像ProcMon这样的覆盖率工具
通过 Ghidra 进行静态分析找到偏移
二进制文件包含一些字符串,其中一个是"Failed to open file",让我们点击Search菜单然后点击"Program Text"并寻找这个句子:
让我们点击 search all 并检查结果:
让我们双击命名空间 FUN_00401060 中的第一个出现:
记住我们要寻找的执行流程是:打开文件 > 读取 > 关闭文件 > 返回正常执行。让我们调查这个流程是否出现在函数的伪代码中。简化后给我们:
void __cdecl FUN_00401060(int argc, int argv){ uint openResult; uint readResult; WCHAR fileContentBuffer[6]; // Buffer to store file content uint localVariable; localVariable = DAT_0041c040 ^ (uint)&stack0xfffffffc;if (argc < 2) { FUN_00401130((int)s_Usage:_%s_<filename>_0041c000); // Print usage message }else { openResult = FID_conflict:__open(*(char **)(argv + 4), 0x8000); // Open file specified in argv[1]if ((int)openResult < 0) { FUN_00401130((int)s_Failed_to_open_file:_%s_0041c018); // Print error message if file opening fails }else {while (readResult = FUN_00406348(openResult, fileContentBuffer, 10), 0 < (int)readResult) { FUN_00401130((int)&DAT_0041c034); // Print file contents } FUN_00407b70(openResult); // Close the file } } FUN_0040116a(localVariable ^ (uint)&stack0xfffffffc); // Some additional function callreturn;}
听起来匹配!现在让我们找到这个函数的偏移量。这很简单,让我们右键点击函数并显示字节。我们可以看到函数的地址是0x00401060,基地址是0x0040000,所以函数偏移量是0x01060。
Ghidra 速查表: https://ghidra-sre.org/CheatSheet.html
准备模糊测试环境
对二进制文件进行模糊测试是一项相当消耗资源的任务,以下是一些你可以做的事情来准备环境以顺利运行模糊测试活动:
-
禁用自动调试 -
禁用杀毒软件扫描
优化
拥有一个良好的输入语料库是模糊测试的一个非常重要的方面。WinAFL 提供了两个选项来使用 c-min.py 优化你的语料库。使用示例:
-
典型用法winafl-cmin.py -D D:DRIObin32 -t 100000 -i in -o minset -covtype edge -coverage_module m.dll -target_module test.exe -target_method fuzz -nargs 2 -- test.exe @@ -
仅保留崩溃的试运行,使用 4 个工作进程和工作目录:winafl-cmin.py -C --dry-run -w 4 --working-dir D:dir -D D:DRIObin32 -t 10000 -i in -i C:fuzzin -o out_mini -covtype edge -coverage_module m.dll -target_module test.exe -target_method fuzz -nargs 2 -- test.exe @@ -
从特定文件读取winafl-cmin.py -D D:DRIObin32 -t 100000 -i in -o minset -f foo.ext -covtype edge -coverage_module m.dll -target_module test.exe -target_method fuzz -nargs 2 -- test.exe @@ -
使用模式从特定文件读取winafl-cmin.py -D D:DRIObin32 -t 100000 -i in -o minset -f prefix-@@-foo.ext -covtype edge -coverage_module m.dll -target_module test.exe -target_method fuzz -nargs 2 -- test.exe @@ -
使用静态插桩的典型用法winafl-cmin.py -Y -t 100000 -i in -o minset -- test.exe @@
winafl-cmin.py 运行可能需要一段时间,所以要有耐心。
运行测试活动
我们已经修补了二进制文件使其可以进行模糊测试,找到了我们想要测试的函数的偏移量,现在让我们开始有趣的部分,运行模糊测试器!WinAFL 提供了不同的选项,让我们列举一下:
-
t
– 每次模糊测试迭代的超时时间。如果未完成,WinAFL 会重启程序; -
D
– DynamoRIO 路径 -
coverage_module
– 记录覆盖率的模块。 -
target_module
– 目标函数的模块。 -
target_offset
– 要模糊测试的函数从模块开始的虚拟偏移量; -
fuzz_iterations
– 重启程序执行前的模糊测试迭代次数。 -
call_convention
– 指定调用约定:sdtcall
、cdecl
和thiscall
。 -
nargs
– 被模糊测试的函数接受的参数数量。this
指针(在thiscall
调用约定中使用)也被视为一个参数。
警告:我们构建了 2 个 WinAFL 对吧?记住,要使用正确版本的 AFL 来模糊测试你的目标!这里我们将使用 32 位版本!
由于我们的二进制文件旨在打开和读取文本文件,创建一个"in"文件夹并放入一个内容为简单短语的文本文件。
好的,现在让我们 cd 到 WinAFL_32 构建目录并运行以下命令:
afl-fuzz.exe -i in -o out -t 10000 -D C:WinAFLDynamoRIObin32 -- -fuzz_iterations 500 -coverage_module vulnerable_reader.exe -target_module vulnerable_reader.exe -target_offset 0x01060 -nargs 3 -call_convention thiscall -- vulnerable_reader.exe @@
如果一切顺利,你应该会看到这个漂亮的界面:
现在就是时间问题了。让模糊测试器运行几分钟,然后你就应该看到崩溃出现。
分析崩溃测试
这里WinAFL很快就发现了一个崩溃。为了让这个教程变得有趣,我特意设计了一个非常容易崩溃的二进制文件。如你所见,WinAFL 用状态和崩溃类型来命名崩溃文件。你可以在 out 目录 > crashes 中找到它们
这显然是一个栈缓冲区溢出,因为程序就是为此而设计的。不过,让我们在 WinDBG 中打开它,对崩溃进行根本原因分析。
启动 WinDBG 并点击 File > Launch Executable (advanced),然后将易受攻击的二进制文件的路径设为"Executable",将 crash_id 文件设为"Arguments",然后点击"Go"运行程序。
如你所见,WinDBG 立即报告检测到栈缓冲区溢出。如果你想了解更多关于使用 WinDBG 进行根本原因分析的信息,我推荐这个不错的视频:https://hardik05.wordpress.com/2021/11/23/analyzing-and-finding-root-cause-of-a-vulnerability-with-time-travel-debugging-with-windbg-preview/
参考资料
-
WinAFL – https://github.com/googleprojectzero/winafl -
DynamoRIO – https://dynamorio.org/ -
Syzygy – https://github.com/google/syzygy/wiki -
Intel PTrace – https://edc.intel.com/content/www/us/en/design/ipla/software-development-platforms/client/platforms/alder-lake-desktop/12th-generation-intel-core-processors-datasheet-volume-1-of-2/004/intel-processor-trace/ -
ProcMon – learn.microsoft.com/en-us/sysinternals/downloads/procmon -
x64dbg – x64dbg.com/#start -
WinDBG – https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-download-tools -
Ghidra CheatSheet – https://ghidra-sre.org/CheatSheet.html -
Windows Internals – scorpiosoftware.net/category/windows-internals/ -
根本原因分析 – https://hardik05.wordpress.com/2021/11/23/analyzing-and-finding-root-cause-of-a-vulnerability-with-time-travel-debugging-with-windbg-preview/
原文始发于微信公众号(securitainment):Windows 二进制文件模糊测试的艺术
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论