Winafl中基于插桩的覆盖率反馈原理

最近winafl增加支持对Intel PT的支持的,但是只支持x64,且覆盖率计算不全,比如条件跳转等,所以它现在还是不如直接用插桩去hook的方式来得准确完整,这里主要想分析也是基于 DynamoRIO插桩的覆盖率反馈原理。

之前曾有人在《初识 Fuzzing 工具 WinAFL》(https://paper.seebug.org/323/#32)中“3.2.2 插桩模块”一节中简单分析过其插桩原理,但没有找到我想要的答案,因此只好自动动手分析下源码。

比如,我想知道:

  1. 通过循环调用fuzzing的目标函数来提高速度,但DynamoRIO的覆盖率信息是如何同步给fuzzer主进程的?

  2. 具体是如何实现寄存器环境的记录与恢复,从而实现目标函数的不断循环?

  3. 覆盖率信息是如何记录与分析的?

覆盖率信息记录与分析原理

第3个问题发现已经有人分析过afl,可以参见这里《AFL内部实现细节小记》(http://rk700.github.io/2017/12/28/afl-internals/),简单总结下:

  1. AFL在编译源码时,为每个代码生成一个随机数,代表位置地址;

  2. 在二元组中记录分支跳转的源地址与目标地址,将两者异或的结果为该分支的key,保存每个分支的执行次数,用1字节来储存;

  3. 保存分支的执行次数实际上是一张大小为64K的哈希表,位于共享内存中,方便target进程与fuzzer进程之间共享,对应的伪代码如下:

    image

  4. fuzzer进程通过buckets哈希桶来归类这些分支执行次数,如下结构定义,左边为执行次数,右边为记录值trace_bits:

    image

  5. 对于是否触发新路径,主要通过计算各分支的trace_bits的hash值(算法:u32 cksum **=** hash32(trace_bits, MAP_SIZE常量, HASH_CONST常量);)是否发生变化来实现的

覆盖信息的传递原理

  1. 先在fuzzer进程中先创建命名管道,其中fuzzer_id为随机值:

image.png

2.创建drrun进程去运行目标程序并Hook,在childpid_(%fuzzer_id%).txt的文件中记录子进程id,即目标进程ID,然后等待管道连接,并通过读取上述txt文件以获取目标进程id,主要用来后面超时中断进程的:

image.png

3. 在插桩模块winafl.dll中打开前面创建的命名管道,然后通过管道与fuzzer主进程进行交互:

image

4. 当插桩模块winafl.dll监测到程序首次运行至目标函数入口时,pre_fuzz_handler函数会被执行,然后通过管道写入'P'命令,代表开始进入目标函数,afl-fuzz.exe进程收到命令后,会向目标进程写入管道命令'F',并监测超时时间和循环调用次数。afl-fuzz.exe与目标进程正是通过读写管道命令来交互的,主要有'F'(退出目标函数)、'P'(进入目标函数)、'K'(超时中断进程)、'C'(崩溃)、'Q'(退出进程)。覆盖信息通过文件映射方法(内存共享)写入winafl_data.afl_area:

image.png

image

篡改目标函数循环调用的原理

此步的关键就在于进入目标函数前调用的pre_fuzz_handler函数,以及函数退出后调用的post_fuzz_handler函数。

进入pre_fuzz_handler函数时,winafl.dll会先获取以下信息

image

其中内存上下文信息支持各平台的寄存器记录:

接下来就是获取和设置fuzzed的目标函数参数:

image

当目标函数退出后,执行post_fuzz_handler函数,会恢复栈顶指针和pc地址,以此实现目标函数的循环调用:

image

总结

总结下整个winafl执行流程:

  1. afl-fuzz.exe通过创建命名管道与内存映射来实现与目标进程交互,其中管道用来发送和接收命令相互操作对方进程,内存映射主要用来记录覆盖率信息;

  2. 覆盖率记录主要通过drmgr_register_bb_instrumentation_event去设置BB执行的回调函数,通过instrument_bb_coverage或者instrument_edge_coverage来记录覆盖率情况,如果发现新的执行路径,就将样本放入队列目录中,用于后续文件变异,以提高代码覆盖率;

  3. 目标进程执行到目标函数后,会调用pre_fuzz_handler来存储上下文信息,包括寄存器和运行参数;

  4. 目标函数退出后,会调用post_fuzz_handler函数,记录恢复上下文信息,以执行回原目标函数,又回到第2步;

  5. 目录函数运行次数达到指定循环调用次数时,会中断进程退出。


为您推荐了相关的技术文章:

  1. 利用 Python 特性在 Jinja2 模板中执行任意代码
  2. PHP代码审计学习 | 零の杂货铺
  3. CVE-2017-6920:Drupal远程代码执行漏洞分析及POC构造
  4. CI框架下column注入 – Wupco's Blog
  5. Python Waf黑名单过滤下的一些Bypass思路 - Mosuan's Blog

原文链接: www.secpulse.com