一. WinAFL 原理简单介绍
由于AFL的设计是偏Unix风格的,没法直接用来测试windows程序,因此便有了在AFL基础上进行改造的WinAFL。
AFL对于源码程序是在编译时插桩,对于二进制程序是利用qemu插桩实现的。而WinAFL则使用DynamoRIO(http://dynamorio.org)来进行动态插桩获取运行时覆盖率的,这种插桩方法带来了原始执行的2倍开销,但仍比AFL的二进制插桩模式开销小。
二. WinAFL安装
(1) 下载安装DynamoRio源码,或者直接下载DynamoRio Windows版的二进制包(https://github.com/DynamoRIO/dynamorio/wiki/Downloads)
(2) 打开Visual Studio命令提示工具,如果要安装成64位版本的则打开Visual Studio x64命令提示工具(一般在【开始—所有程序—Visual Stdio—Visual Studio Tools】中可找到)。因为在对64位程序进行fuzz时,需要有64-bit的winafl.dll,所以安装时要选择好版本。
(3) 下载WinAFL源码,并在命令提示工具中进入WinAFL的目录下
(4) 在Visual Studio命令提示工具中输入如下命令进行WinAFL编译安装(需将-DDynamoRIO_DIR参数设置为你的DynamoRIO cmake文件所在位置)
32-bit build:
mkdir build32
cd build32
cmake .. -DDynamoRIO_DIR="\path\to\DynamoRIO\cmake"
cmake --build . --config Release
64-bit build:
mkdir build64
cd build64
cmake -G"Visual Studio 11 2012 Win64" .. -DDynamoRIO_DIR="\path\to\DynamoRIO\cmake"
cmake --build . --config Release
提示:
cmake可以直接从官网上下载, cmake安装过程中会默认将bin加入环境变量中;
cmake -G后面的要根据你机器上安装的Visual Studio版本来确定,可以通过cmake -G命令来查看有哪些形式;
DynamoRIO下载后也需要将bin32或bin64文件夹的路径添加到环境变量中;
-DDynamoRIO_DIR后面的目录名最好是加引号的绝对地址;
三. WinAFL使用
先说明:根据官方的介绍,winafl是可以用于测试dll和GUI程序的,但必须保证你要测的目标函数能在不需用户交互的情况下被执行到且能返回,同时该目标函数还能打开输入文件并关闭输入文件。在windows下大部分程序都是有图形界面的,而且我们想测的也是这种类型程序(如:pdf解析器、视频播放器、图片显示器等)。
然而对于这类程序,输入文件一般都是通过用户点击菜单栏打开的,而关闭也是要通过手动关闭标签页,甚至是关闭应用程序才可以,所以GUI程序操作文件非常依赖于用户交互。但幸运的是,有些GUI程序也是支持命令行方式打开的,因此对于这类程序我们认为可以找到一个函数会去解析命令行参数,并自动打开及处理该文件(在IDA中通过找调用OpenFile的函数,再结合OD调试,应该可以找到的),但是关闭输入文件的函数就不那么好找了,比如说pdf解析器命令行打开了一个文件,它并不会说一打开解析完了就把这个文件关闭了,而是只有等用户手动关了这个标签页才会关闭,所以我个人觉得对于GUI程序winafl还是不那么好用。。。因此,下面只是介绍大体流程,并没有实际操作成功。
这里我们以VLC这款视频播放器为测试目标,该程序可以用命令行方式打开输入。我们先通过IDA来找需要fuzzing的函数的偏移。一般以start函数或者main函数作为入口即可,但对于不支持命令行的程序的话就要通过OpenFile等API来找打开文件的函数了。为了验证这里直接使用start就可以,我们还是通过IDA来分析一下。具体分析就不叙述了,这里经过三层调用就能到关键的函数sub_401B40,在该函数中会读取命令行参数,且会调用其他dll中的函数来打开并播放输入文件。
再通过OD验证:
因此这里我觉得也可以选择偏移0x1B40(函数地址-基址)作为fuzz目标。根据上面的分析这个目标函数只能自动打开输入,并不能关闭输入文件,所以下面的操作是基于假设这个函数就是目标fuzz函数来说的。
找到目标函数偏移后,可以使用DynamoRIO和winafl.dll来验证并测试目标程序是否能正常插桩运行:
- 先将winafl.dll和afl-fuzz.exe放到同一个目录(这一步是在执行afl-fuzz.exe时要求的,这里提前做了);
- 然后切换到afl-fuzz.exe所在的目录;
- 执行命令:
"D:\Program Files\DynamoRIO-Windows-7.0.0-RC1\bin32\drrun.exe" -c winafl.dll -debug -target_module VLC.exe -target_offset 0x1B40 -fuzz_iterations 2 -- D:\IIE\crash\VLC\VLC\vlc.exe "C:\Users\Public\Videos\Sample Videos\Wildlife.wmv"
运行之后便会在当前目录生成一个log文件,记录了加载的dll、打开的文件、是否正常运行等信息:
如果没出什么crash信息,显示running normally的话,那说明目标函数选对了,可以去测了。(我这里尽管这样显示,但后面afl-fuzz时还是不行 zzz)
具体afl-fuzz测试命令和linux下类似:
cd D:\Program Files\winafl-master\bin32 //切换到afl-fuzz.exe所在目录
afl-fuzz.exe -i D:\IIE\crash\VLC\input_wmv -o D:\IIE\crash\VLC\output -D "D:\Program Files\DynamoRIO-Windows-7.0.0-RC1\bin32" -t 20000 -- -coverage_module VLC.exe -fuzz_iterations 5000 -target_module VLC.exe -target_offset 0x1B40 -nargs 4 -- D:\IIE\crash\VLC\VLC\vlc.exe @@
说明:
- -i 输入文件目录;-o 输出文件目录;-D DynamoRIO插桩工具所在目录;-t 每次运行的时间限制(这四个选项是必须的)
- coverage_module 希望记录覆盖率的模块;-fuzz_iterations 目标函数运行迭代数;-target_module 目标函数所在的模块;-target_offset 即上面所说的函数偏移;-nargs 官方说是目标函数接收的参数个数
- 最后就是目标程序,加执行时参数,如果接收输入文件目录中的文件为参数就用@@占位
然后就开始运行了,然后就发生悲剧了。。。
由于目标程序是播放器,播放需要时长,这里-t设置的时间太短了,因此可以设置的更大或者直接用-t 20000+来表示可以更长时间。然而,结局还是一样的悲伤。。。
还是之前说的情况,目标函数内不会自动关闭输入文件,因此导致运行超时;若手动关闭可执行程序,还是会显示超时(skipping)。所以此时winafl之旅并没有成功,但是基本方法就是上面说的那些,主要还是要看目标程序是如何处理输入文件的。 当然,你也可以手动改winafl或者测试的目标程序,能让它在一个函数内打开并关闭文件,如果哪个小伙伴弄成功麻烦分享分享,万分感谢啦~~