最近winafl
增加支持对Intel PT
的支持的,但是只支持x64
,且覆盖率计算不全,比如条件跳转等,所以它现在还是不如直接用插桩去hook的方式来得准确完整,这里主要想分析也是基于 DynamoRIO
插桩的覆盖率反馈原理。
之前曾有人在《初识 Fuzzing 工具 WinAFL》(https://paper.seebug.org/323/#32)中“3.2.2 插桩模块”一节中简单分析过其插桩原理,但没有找到我想要的答案,因此只好自动动手分析下源码。
比如,我想知道:
-
通过循环调用fuzzing的目标函数来提高速度,但
DynamoRIO
的覆盖率信息是如何同步给fuzzer主进程的? -
具体是如何实现寄存器环境的记录与恢复,从而实现目标函数的不断循环?
-
覆盖率信息是如何记录与分析的?
覆盖率信息记录与分析原理
第3个问题发现已经有人分析过afl
,可以参见这里《AFL内部实现细节小记》(http://rk700.github.io/2017/12/28/afl-internals/),简单总结下:
-
AFL在编译源码时,为每个代码生成一个随机数,代表位置地址;
-
在二元组中记录分支跳转的源地址与目标地址,将两者异或的结果为该分支的key,保存每个分支的执行次数,用1字节来储存;
-
保存分支的执行次数实际上是一张大小为64K的哈希表,位于共享内存中,方便target进程与fuzzer进程之间共享,对应的伪代码如下:
-
fuzzer进程通过buckets哈希桶来归类这些分支执行次数,如下结构定义,左边为执行次数,右边为记录值trace_bits:
-
对于是否触发新路径,主要通过计算各分支的trace_bits的hash值(算法:
u32 cksum **=** hash32(trace_bits, MAP_SIZE常量, HASH_CONST常量);
)是否发生变化来实现的
覆盖信息的传递原理
-
先在fuzzer进程中先创建命名管道,其中fuzzer_id为随机值:
-
创建drrun进程去运行目标程序并Hook,在childpid_(%fuzzer_id%).txt的文件中记录子进程id,即目标进程ID,然后等待管道连接,并通过读取上述txt文件以获取目标进程id,主要用来后面超时中断进程的:
3. 在插桩模块winafl.dll中打开前面创建的命名管道,然后通过管道与fuzzer主进程进行交互:
4. 当插桩模块winafl.dll监测到程序首次运行至目标函数入口时,pre_fuzz_handler
函数会被执行,然后通过管道写入'P'命令,代表开始进入目标函数,afl-fuzz.exe进程收到命令后,会向目标进程写入管道命令'F',并监测超时时间和循环调用次数。afl-fuzz.exe与目标进程正是通过读写管道命令来交互的,主要有'F'(退出目标函数)、'P'(进入目标函数)、'K'(超时中断进程)、'C'(崩溃)、'Q'(退出进程)。覆盖信息通过文件映射方法(内存共享)写入winafl_data.afl_area
:
篡改目标函数循环调用的原理
此步的关键就在于进入目标函数前调用的pre_fuzz_handler
函数,以及函数退出后调用的post_fuzz_handler
函数。
进入pre_fuzz_handler
函数时,winafl.dll会先获取以下信息
其中内存上下文信息支持各平台的寄存器记录:
接下来就是获取和设置fuzzed的目标函数参数:
当目标函数退出后,执行post_fuzz_handler
函数,会恢复栈顶指针和pc地址,以此实现目标函数的循环调用:
总结
总结下整个winafl
-
afl-fuzz.exe通过创建命名管道与内存映射来实现与目标进程交互,其中管道用来发送和接收命令相互操作对方进程,内存映射主要用来记录覆盖率信息;
-
覆盖率记录主要通过
drmgr_register_bb_instrumentation_event
去设置BB执行的回调函数,通过instrument_bb_coverage
或者instrument_edge_coverage
来记录覆盖率情况,如果发现新的执行路径,就将样本放入队列目录中,用于后续文件变异,以提高代码覆盖率; -
目标进程执行到目标函数后,会调用
pre_fuzz_handler
来存储上下文信息,包括寄存器和运行参数; -
目标函数退出后,会调用
post_fuzz_handler
函数,记录恢复上下文信息,以执行回原目标函数,又回到第2步; -
目录函数运行次数达到指定循环调用次数时,会中断进程退出。
本文始发于微信公众号(漏洞战争):Winafl中基于插桩的覆盖率反馈原理
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论