最近winafl增加支持对Intel PT的支持的,但是只支持x64,且覆盖率计算不全,比如条件跳转等,所以它现在还是不如直接用插桩去hook的方式来得准确完整,这里主要想分析也是基于 DynamoRIO插桩的覆盖率反馈原理。
之前曾有人在《初识 Fuzzing 工具 WinAFL》(https://paper.seebug.org/323/#32)中“3.2.2 插桩模块”一节中简单分析过其插桩原理,但没有找到我想要的答案,因此只好自动动手分析下源码。
比如,我想知道:
DynamoRIO的覆盖率信息是如何同步给fuzzer主进程的?第3个问题发现已经有人分析过afl,可以参见这里《AFL内部实现细节小记》(http://rk700.github.io/2017/12/28/afl-internals/),简单总结下:


u32 cksum **=** hash32(trace_bits, MAP_SIZE常量, HASH_CONST常量);)是否发生变化来实现的


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执行流程:
drmgr_register_bb_instrumentation_event去设置BB执行的回调函数,通过instrument_bb_coverage或者instrument_edge_coverage来记录覆盖率情况,如果发现新的执行路径,就将样本放入队列目录中,用于后续文件变异,以提高代码覆盖率;pre_fuzz_handler来存储上下文信息,包括寄存器和运行参数;post_fuzz_handler函数,记录恢复上下文信息,以执行回原目标函数,又回到第2步;