WinAFL
因为winafl需要的测试时间比较长,所以目前采取的策略:
本地的win10虚拟机环境配置,命令配置,样本收集,成功跑起环境的时候推送到服务器上进行长时间测试(操作相同配置、或者直接推送到服务器上)
环境
- win10专业版,但是认证过期了
- 安装WinAFL
- 安装DynamoRIO(新+旧,注意选对仓库,右边的那个仓库)
- cmake 3.190,高版本问题也不大,别太低就行
- Visual Studio 2019(c++环境)
过程
需要提前安装好Visual Studio 2019(c++环境)和perl环境还有doxygen。最好提前安装好,配置好环境变量(或者在make里面当时添加)。
编译DynamoRIO
这个是插桩库,关于fuzz中在Windows上用来插桩的关键工具,如果没有这个东西,winAFL就不能编译出winafl.dll
这个关键文件。两种方式,我建议直接安装编译好的版本,关键就是里面的cmake
目录。
编译AFL
不同的位数需要分开编译,用到cmake-gui,为什么呢,因为Windows下的命令行不会讲错误高亮出来,你也不知道对错,给自己埋地雷。
64
利用cmake的gui填入相关目录
这里第二个输出目录随便,没有她会自己新建。然后Configure一下,这里需要注意的是在File里有一个清理缓存的选项
Configure里面第一个就是要选择一下编译环境
第二行可以选择是32还是64位,默认是64,一般就这样就直接finish,然后下面可能会报错,缺少message compiler或者是什么其他的就everything找一下,然后把目录添加进去就行,参考这里就行。
完成之后,还需要再点击Generate,完事就去目标目录里,用vs打开sln文件
这个用vs打开,然后用vs编译就行,这里网上的教程基本都是让你用命令行,这种做法相对比较傻逼一些,给自己找麻烦。
直接生成解决方案就行,这里注意会有一个报错,但是好像不影响文件的生成。
注意项目里有没有winafl
这个文件,没有的话就说明cmake失败了。
32
注意在cmake里的Configure
的时候选择一下,其他步骤一致。vs编译的时候不会报错
最后
看到这样基本就成了。也可以看到目录下生成的文件。
红框中的是关键文件,其他文件都是一些测试用例和我自己写的案例。
使用例子
为了验证程序是不是能跑,先找一个命令测试一下
下面的命令需要在cmd中使用,Windows terminal里面无法执行这个命令
afl-fuzz.exe
-i in
-o out
-D "D:\dynamorio-master\dynamorio-master\Project\bin32"
-t 20000
--
-coverage_module gdiplus.dll
-coverage_module WindowsCodecs.dll
-fuzz_iterations 5000
-target_module test_gdiplus.exe
-target_offset 0x1680
-nargs 1
--
test_gdiplus.exe @@
需要注意的是里面的--
是分隔符
-o、-i
是输出输入目录
-t
是超时时间,单位毫秒
coverage_module
这是测试的模块,允许有多个
fuzz_iterations
是循环的次数,这个会影响exec speed
, 一般速度在几百上千是正常的,过慢就说明前处理过程不行
target_module
和target_offset
是测试的程序,就是要执行的程序,要和最后的相匹配,而且offset一定要是module里的offset,ida可以直接看
-nargs
是参数的个数,不包括本身
@@
代表参数是自动生成的in里的
最后的结果如下所示
AFL++
afl已经很久没有更新了,但是推出的afl++还在更新,所以可以采用afl++来替代afl。
配置这个环境很简单,直接使用docker就行了,注意设置一个共享目录用来交流文件。
docker pull aflplusplus/aflplusplus
docker run -ti -v $HOME:/home aflplusplus/aflplusplus
export $HOME="/home"
IrfanView案例
首先在官网下载最新版本,然后下载一些他的插件
在这里可以下载插件,可以看到这里还是有不少CVE版本的。
首先先找一下样本库,从github仓库中可以下载,下载之后需要对样本进行一个精简,让 afl自己去根据特征进行删减来提高效率。
寻找偏移
不论干啥都需要先找到处理文件的对应的偏移,可以使用ProcMon来查找
找到处理偏移之后,就可以用winafl自带的工具进行样本筛选
python winafl-cmin.py
--working-dir C:\Users\moshe\Desktop\fuzz\winafl\build32\bin\Release
-w 3
-i C:\Users\moshe\Desktop\fuzz\samples
-o C:\Users\moshe\Desktop\fuzz\irfanview_cmin
-t 4000
-D C:\Users\moshe\Desktop\fuzz\dynamorio\build\bin32
-covtype edge
-target_module "i_view32.exe"
-coverage_module "i_view32.exe"
-target_offset 0xa54fd
-nargs 3
--
C:\Users\moshe\Desktop\fuzz\iview457\i_view32.exe @@ /convert="NUL" /silent
- 第一个参数是winafl.dll所在的目录
- -w是CPU的核心数,提高筛选速度
- i/o就是输入和输出目录
- -t就是超时时间
- -D是DynamoRIO 目录。这与之前执行 drrun 的位置相同.exe
- covtype:
默认覆盖跟踪器仅跟踪遍历了哪些基本块。Edge还跟踪基本块被命中的顺序。因此,如果您的程序从具有两个独立输入的基本块 A->B->C 和 A->C->B 开始,则基本块跟踪器只会看到一个感兴趣的输入,而边缘跟踪器将同时看到两个输入。
- 后面就是目标模块和测试模块了
- 程序参数的个数
- 后面就是执行的时候的参数
经过筛选完之后就是这个结果
使用drrun验证
这是关键的一步,需要注意的是:
测试程序和winafl一定要在同一个目录下,不然会出现定位不到偏移的问题。比如我测试的是aaa.exe但是主要目标是他的插件bbb.dll这里bbb的位置无所谓,但是exe一定要同目录
一定要注意一定要注意一定要注意一定要注意
可以使用如下命令进行测试,这个文档不是一天写的,所以测试的程序不一样,懒得改了。
D:\DynamoRIO9.92.19461\bin32\drrun.exe
-c winafl.dll
-debug
-target_module calldll.exe
- coverage_module test_call.dll
-fuzz_iterations 10
-target_offset 0x1590
--
calldll.exe "123123123456asdf"
查看覆盖率
利用命令,生成一个log文件,然后利用ida安装的lighthouse插件,在
load生成的log file,就可以明显的看到覆盖率了,但是这里需要注意的是lighthouse已经很久没更新了,所以需要一个旧版本的drrun来测试,查看的结果其实应该基本一样的。如果报错了,就降低drrun的版本,但是编译afl的时候还是用新版本的drrun去编译。然后生成的对应的文件要load进对应文件的ida中,用错了也会报错。
D:\DynamoRIO-Windows-8.0.18712\bin32\drrun.exe
-t drcov
--
calldll.exe adsfadfadfdasfassdf 12345609876llllllllllllllllll
总结
利用procmon实际上就是进行一个大体的定位,后续用到的drrun就是对具体模块中的代码块进行的定位,可以看到覆盖百分率这些东西,看一下目标模块怎么样就可以了,或者有没有陷入奇怪的代码中。
开始模糊测试
使用经典的winafl命令就可以直接进行测试了,因为这是利用 DynamoRIO 进行的动态插桩,所以不需要编译啥的。
afl-fuzz.exe
-i C:\Users\moshe\Desktop\fuzz\irfanview_cmin
-o C:\Users\moshe\Desktop\fuzz\winafl_output
-t 1000+
-D C:\Users\moshe\Desktop\fuzz\dynamorio\build\bin32
--
-coverage_module "i_view32.exe"
-target_module "i_view32.exe"
-target_offset 0x082550
--
C:\Users\moshe\Desktop\fuzz\iview457\i_view32.exe @@ /convert="NUL" /silent
iotD
这些参数都是固定的含义- -- 作为分隔符
- target_offset:目标的相对于文件头的偏移
开始测试:
不难发现,全是一堆time out,这显然是不成功的。但是基本的流程清楚了。这是因为他找不到要测试的dll库了,需要手动patch一下目标程序让他可以在本目录中寻找插件dll,否则winafl会报错,winafl不能跨目录
修改一下这里,然后将webp托到同目录就可以了就可以同目录测试了。
-f
这个测试可以指定输入文件的名称,如下所示:
afl-fuzz.exe -i in -o out -S s1 -D "D:\DynamoRIO9.92.19461\bin32" -f test.webp -t 7000 -- -c overage_module WebP.dll -target_module call_webp.exe -target_offset 0x0155c -fuzz_iterations 8000 -nargs 1 -- call_webp.exe @@
这个输入会根据in目录中的文件变异得到。
提高效率
直接通过原始程序测试会有许多其他路径,降低fuzz的速度,实测峰值在40次每s左右,这里我们可以手动写一个calldll程序来进行fuzz
首先就是要搞清楚dll里某个导出函数的参数,ida可能会出错,所以这里我们要x64dbg和ida联合调试,下面是我写的calldll文件,可以将速度提升到1000次每秒左右,速度非常快,仅供参考:利用下面的程序我们可以只fuzz下面的那个fuzz函数,防止fuzz程序陷入其他无用模块。
#include<stdio.h>
#include<windows.h>
// L"\x65E0\x0000\x215B\x54C4" 2
wchar_t iarg3[520] = { 0 };
wchar_t iarg4[520] = { 0 };
int iarg5[17] = {0};
// typedef void (WINAPI *Readwebp_W)(char *D, wchar_t a2[], wchar_t arg2[] , wchar_t *ini_path, wchar_t* file_path);
typedef void (WINAPI *Readwebp_W)(LPCWSTR file_path, LPCWSTR ini_path, wchar_t *arg3, wchar_t *arg4, int *arg5);
void fuzz(Readwebp_W readwebp_W, LPCWSTR file_path)
{
LPCWSTR lpFileName = TEXT(L"C:\\Users\\Rootkit\\AppData\\Roaming\\IrfanView\\i_view32.ini");
readwebp_W(file_path, lpFileName, iarg3, iarg4, iarg5);
}
int main(int argc, char **argv)
{
HMODULE PDFDLL = LoadLibraryA("C:\\Users\\Rootkit\\Desktop\\winafl-TEST\\IrfanView\\webp.dll");
if(PDFDLL == NULL)
{
printf("call pdf.dll wrong , error code : %d\n", GetLastError());
return 0;
}
Readwebp_W readWebp = (Readwebp_W)GetProcAddress(PDFDLL, "ReadWebP_W");
if (readWebp == NULL)
{
printf("GetProcAddress readWebp failed! error code: %d\n", GetLastError());
FreeLibrary(PDFDLL);
return 1;
}
int len = strlen(argv[1]); // length of input string
// calculate required length of wide byte string (add 1 for null terminator)
int wideLen = MultiByteToWideChar(CP_UTF8, 0, argv[1], len, NULL, 0) + 1;
printf("%d\n", wideLen);
wideLen = len + 1; // UTF-8 to wide char conversion, assuming all chars are the same (no surrogates
printf("%d\n", wideLen);
// allocate memory for wide byte string
LPWSTR wideStr = (LPWSTR) malloc(wideLen * sizeof(wchar_t));
// convert narrow byte string to wide byte string
MultiByteToWideChar(CP_UTF8, 0, argv[1], len, wideStr, wideLen);
// null-terminate the wide byte string
wideStr[wideLen - 1] = 0;
printf("i will call the fuzz func\n");
fuzz(readWebp, wideStr);
FreeLibrary(PDFDLL);
return 0;
}
然后通过-M maste和-S s1这些参数来多开进程来提高效率,影响到内存和cpu,每一个进程会独占一个cpu核心,内存也会成波浪式使用,所以要懂得取舍。
实际效果
afl-fuzz.exe -i in -o out -M master -D "D:\DynamoRIO9.92.19461\bin32" -t 7000 -- -c overage_module WebP.dll -target_module call_webp.exe -target_offset 0x0155c -fuzz_iterations 8000 -nargs 1 -- call_webp.exe @@
afl-fuzz.exe -i in -o out -S s1 -D "D:\DynamoRIO9.92.19461\bin32" -t 7000 -- -c overage_module WebP.dll -target_module call_webp.exe -target_offset 0x0155c -fuzz_iterations 8000 -nargs 1 -- call_webp.exe @@
afl-fuzz.exe -i in -o out -S s2 -D "D:\DynamoRIO9.92.19461\bin32" -t 7000 -- -c overage_module WebP.dll -target_module call_webp.exe -target_offset 0x0155c -fuzz_iterations 8000 -nargs 1 -- call_webp.exe @@
afl-fuzz.exe -i in -o out -S s3 -D "D:\DynamoRIO9.92.19461\bin32" -t 7000 -- -c overage_module WebP.dll -target_module call_webp.exe -target_offset 0x0155c -fuzz_iterations 8000 -nargs 1 -- call_webp.exe @@
速度相当可观,大概是测试了33小时,当last new path的值长时间不变的时候,就差不多可以停了,我停止的时候他已经4小时没有新路径了,所以就停了,也没有fuzz出crash。