https://www.usenix.org/system/files/sec21-nagy.pdf
覆盖率导向的fuzz是最有效的fuzz安全测试方法,有两种形式:基于编译器的和只有二进制文件的。基于编译器的性能已经被提升很多,只有二进制文件的方法就稍显落后,由于语义和插桩的局限性。大多数fuzz都是只有二进制文件的,因此把二进制文件转化为fuzz增强版本上是一个挑战。
本文测试了只有二进制文件fuzz插桩所需的二进制文件属性,并基于此设计了ZAFL:可以在二进制程序上进行fuzz插桩,并且保留编译器级别的性能。ZAFL和AFL完全兼容,并且在性能和crash找寻能力方面超过目前的工具:AFL-QEMU, AFL-Dyninst
对于大型应用的漏洞,手动分析和重量级分析工具像符号执行都是不现实的,所以研究人员更倾向于使用fuzz这种测试方法。
目前效果最成功的方法是覆盖率导向的fuzz,只生成能够覆盖更多代码的种子,因为这样更可能发现漏洞。覆盖率通过基本块插桩进行计算。广泛使用的fuzz包括AFL,libFuzzer,honggFuzz。
大多数现代的fuzzer都需要通过编译进行插桩,来获取覆盖率或跟踪行为。但是编译器插桩无法应用在闭源软件上,这就需要二进制插桩。二进制插桩在其它领域很成功,但是无法既转换得好又保持性能。
为了解决这个问题本文观察二进制插桩,找到保持性能和实现fuzz插桩的关键属性,并基于此设计了ZAFL。
通过静态重写插桩:动态转换需要繁重的工作解释目标架构的主机指令,增加了更多的运行时开销
通过内联方式调用桩:基于跳转的插桩需要计算相对偏移,这种计算量随着桩的增多会逐步加大,最终会导致fuzz出问题
必须支持存活跟踪:由于插桩可能对条件寄存器造成影响,保存/恢复条件寄存器会大大影响性能。所以应该跟踪条件寄存器,如果不受影响则不再执行保存/恢复动作
支持多数的格式和平台
包含静态重写组件和ZAX:四个IR修改阶段,用于集成编译器质量的插桩和fuzz增强
给定一个二进制文件,处理步骤如下:
提取IR:从二进制重写器中,获取一个目标程序的中间表示(IR)。二进制重写器使用一个扩展的Zipr来达到目标要求
IR被传输到ZAX的四个转换中,用于插桩
优化
分析:分析各种元属性(例如继承和前置,数据流,支配关系)
选点:找到哪个点适合插桩,穷举所有基本块并且剔除对于未来插桩没用的基本块
应用:根据配置应用插桩
二进制重构:在中间表示中插桩之后,将IR转换成可用于fuzz的二进制文件
在LAVA-M数据集上测试
和静态重写器AFL-Dyninst相比,找到26-96%独特的漏洞
和动态转换器AFL-QEMU相比,找到37-131%独特的漏洞
和AFL-Dyninst/AFL-QEMU相比,只有27%的编译器开销,但是fuzz吞吐量提高了48-78%/131-203%
成功插桩56个不同类型的二进制文件,(33开源23闭源),大小10K-100MB,复杂度100-1000000个基本块,30个linux平台12个windows平台
评估了实现编译器级别效果的二进制插桩所需的属性,开发了一套成功标准,指出了适用于这套标准的二进制插桩方法
应用这套方法实现了ZAFL,具有编译器质量的二进制插桩器。专注于在复杂fuzz增强转换的细粒度的插桩方案上实现高性能
展示了实现高性能的fuzz转换二进制插桩是可能的,通过在ZAFL中从已有的基于编译器实现五个这样的转换,测试了运行时开销
描述了ZAFL的性能,并且和AFL-Dyninst/AFL-QEMU做了对比测试
开源了ZAFL和测试集 https://git.zephyr-software.com/opensrc/zafl