Breaking Through Binaries: Compiler-quality Instrumentation for Better Binary-only Fuzzing

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。

标准

  1. 通过静态重写插桩:动态转换需要繁重的工作解释目标架构的主机指令,增加了更多的运行时开销

  2. 通过内联方式调用桩:基于跳转的插桩需要计算相对偏移,这种计算量随着桩的增多会逐步加大,最终会导致fuzz出问题

  3. 必须支持存活跟踪:由于插桩可能对条件寄存器造成影响,保存/恢复条件寄存器会大大影响性能。所以应该跟踪条件寄存器,如果不受影响则不再执行保存/恢复动作

  4. 支持多数的格式和平台

方法

包含静态重写组件和ZAX:四个IR修改阶段,用于集成编译器质量的插桩和fuzz增强

给定一个二进制文件,处理步骤如下:

  1. 提取IR:从二进制重写器中,获取一个目标程序的中间表示(IR)。二进制重写器使用一个扩展的Zipr来达到目标要求

  2. IR被传输到ZAX的四个转换中,用于插桩

    1. 优化

    2. 分析:分析各种元属性(例如继承和前置,数据流,支配关系)

    3. 选点:找到哪个点适合插桩,穷举所有基本块并且剔除对于未来插桩没用的基本块

    4. 应用:根据配置应用插桩

  3. 二进制重构:在中间表示中插桩之后,将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