使用winafl进行fuzzing

0x00 背景

前阵子看到了这篇讲winafl的文章http://www.tuicool.com/articles/j2eqym6,也想按这篇文章试一试,但结果无法重现这篇文章的方法,所以我做了一些调查,总结了一下winafl的正确使用方法。

0x01 使用方法

首先,下载winafl和dynamoRIO,这两个直接在github上下载好就行了,而且有已经编译好的文件,都不需要自己编译。然后下载VUPlayer这款播放器作为我们要fuzzing的目标软件。

然后我们得找到需要fuzzing的函数的偏移。一般选取main函数就行了。VUPlayer这个软件是用MFC做的,我们找到WinMain这个函数的偏移0xF85B0即可(注意,IDA自动加了一个基址0x400000,要把它减掉)。我们不能使用WinMain函数作为入口,因为这样会导致需要用户交互,必须找到一个不涉及用户交互的函数,我目前还未找到一个可行的函数。

现在假设我们找到一个符合这些条件的偏移,我们就可以使用dynamoRIO配合winafl.dll模块来测试一下winafl能不能跑成功。命令如下:

C:\Users\Administrator\Desktop\DynamoRIO-Windows-6.2.0-2\bin32\drrun.exe -c winafl.dll -debug -target_module VUPlayer.exe -target_offset 0xF85B0 -fuzz_iterations 2  --  VUPlayer.exe test.m3u

注意要把winafl.dll拷贝到VUPlayer.exe所在目录下,而且要自己创建test.m3u文件,内容如下:

C:\Users\Administrator\Downloads\Developers.mp3
C:\Users\Administrator\Downloads\Developers.mp3
以下省略

这是一个标准的m3u格式的文件。

运行了上述命令后,会在当前目录产生一个log文件,我们看一下这个log文件,内容大概是这样的:

Module loaded, VUPlayer.exe
Module loaded, BASS.dll
Module loaded, BASSWMA.dll
Module loaded, BASSMIDI.dll
Module loaded, dynamorio.dll
Module loaded, ODBC32.dll
Module loaded, MFC42.dll
...
Module loaded, ADVAPI32.dll
Module loaded, IMM32.dll
Module loaded, USER32.dll
Module loaded, SHLWAPI.dll
Module loaded, SECHOST.dll
Module loaded, combase.dll
Module loaded, ntdll.dll
In pre_fuzz_handler
In post_fuzz_handler
In pre_fuzz_handler
In post_fuzz_handler
Everything appears to be running normally.
Coverage map follows:
...

我们可以看到,一开始加载了一些dll,包括dynamorio.dll,然后分别执行了两次pre_fuzz_handlerpost_fuzz_handler。我是在windows 8下进行的测试,如果在windows7以下的版本中还可以发现文件在pre_fuzz_handlerpost_fuzz_handler中间被打开。如果没有crash等出现,可以说明我们的目标函数选对了。接下来我们就可以进行正式fuzz了。

先在当前目录下创建in和out两个目录,然后在in目录里新建test_case.m3u文件,内容跟上面的test.m3u一样即可。之后使用如下命令:

afl-fuzz.exe -i in -o out -f test.m3u -D C:\Users\Administrator\Desktop\DynamoRIO-Windows-6.2.0-2\bin32 -t 20000 -- -fuzz_iterations 5000 -coverage_module VUPlayer.exe -target_module VUPlayer.exe -target_offset 0xF85B0 -nargs 4 -- VUPlayer.exe @@

注意此处我将afl-fuzz.exe也拷贝到了当前目录。然后fuzz开始了,截图是这样的:

%e3%82%ad%e3%83%a3%e3%83%97%e3%83%81%e3%83%a3

如果你看到这个画面,就说明你的fuzzer成功运行了。下面来说一说原理和一些原文中有问题的地方。

0x02 原理

所谓fuzzer,就是将目标软件一遍遍执行,每次执行都给定一个不同的输入,看看有没有输入会造成软件崩溃。但是这里有个问题,如果只是单纯得一遍遍运行程序再结束程序,fuzzing的效率将非常低,所以我们可以将函数进程启动,然后找到一个会打开并完全关闭输入文件的函数,通过操作pc寄存器,使得程序反复执行该函数,当然每次执行前需要将我们的输入文件mutate以下,这样我们就节省了创建进程,杀死进程的时间了,每秒可以测试上百个输入文件。而操作pc寄存器是得靠PIN工具(也就是instrumentation工具)来完成,这就是dynamoRIO的作用。另外,如果强行改变pc寄存器,而不去管内存呀寄存器的状态,是很可能造成程序崩溃的,所以必须要在我们的目标函数被调用之前进行现场的保存和恢复,这就是pre_fuzz_handlerpost_fuzz_handler的作用,这两个函数其实就是使用dynamoRIO注册了这两个hook函数,分别在目标函数执行前和执行后执行。以上就是winafl的基本原理。

0x03 原文中的坑

之前提到了,原文中其实有很多坑,使得按照原文操作并不能正确地fuzz,我来一一道来。

首先,想要用winafl fuzz软件,目标软件必须支持命令行,能从命令行读取输入文件。而VUPlayer是支持命令行的,比如使用VUPlayer.exe test.m3u可以打开test.m3u文件,但是有个前提,文件的后缀名必须是m3u,否则不会打开文件。所以在我们fuzz的时候,要使用-f test.m3u这个选项,这是确保文件名的后缀为m3u。如果不使用这个选项,文件名将默认为out目录下的.cur_input。

其次,in目录中的test_case不能为使得程序crash的输入,也就是不能用poc作为test_case,不然fuzzer在第一次迭代就crash,根本不会继续往下执行。这一点在实际fuzzing的过程中当然不会出现,但是原文中却使用了poc作为test_case,未免误导人。

再次,作为target函数的函数有几点要求,一是必须进行一次完整的文件打开和关闭操作,如果没有文件关闭操作,winafl的mutator无法生成新的输入文件(覆盖旧的输入文件),会造成winafl强行kill进程,这样fuzzing效率非常低,而且并不是我们期待的做法。原文中提供的函数0x532a0仅有打开文件操作,没有关闭文件操作,是无法进行正确地fuzzing的。二是目标函数的参数如果有太复杂的指针,fuzzing有可能无法进行,这是因为在pre_fuzz_handler中无法聪明地恢复指针所指向的数据造成的(只能恢复指针本身的值),这种情况下有可能在第二次迭代中(也就是强行修改了pc寄存器后)造成崩溃。

0x04 总结

知道原理才能成功地使用fuzzer,不能盲目乱试。