使用DeepState与Eclipser进行单元测试模糊测试:突破性技术解析

本文介绍了Eclipser这一创新模糊测试工具如何与DeepState集成,通过轻量级插桩和近似路径约束技术显著提升漏洞检测效率,在真实场景中 outperforms 传统工具并发现多个CVE漏洞。

使用DeepState与Eclipser进行单元测试模糊测试

动态分析、模糊测试、符号执行

如果单元测试对您很重要,那么现在有另一个理由使用DeepState——我们类似于Google Test的基于属性的C/C++测试工具。它叫做Eclipser,是最近在ICSE 2019论文中提出的一种强大新型模糊测试工具。我们自豪地宣布,Eclipser现已完全集成到DeepState中。

Eclipser在模糊测试中提供了符号执行的许多好处,而没有通常与符号执行相关的高计算和内存开销。它结合了“白盒和灰盒模糊测试的最佳特性”,仅使用轻量级插桩,最关键的是,从不调用昂贵的SMT或SAT求解器。Eclipser是我们希望(也许在您的帮助下)打造的一系列一键式前端中的第一个,这些前端面向比AFL或libFuzzer需要更多工作才能应用的有前途的工具。Eclipser使DeepState能够快速检测更多难以触发的错误。

是什么让Eclipser特别?

传统的符号执行(DeepState通过Manticore和angr等工具支持)跟踪路径约束:程序输入的条件,使得程序在给定满足约束的输入时会采取特定路径。不幸的是,解决这些条件既困难又昂贵,尤其是因为许多约束是不可行的:它们无法解决。

已经提出了许多解决路径约束高成本的变通方法,但大多数基于符号执行的工具在可扩展性方面仍然有限,并且在要求生成长路径或处理复杂代码时容易失败。Eclipser基于KLEE和MAYHEM中开发的思想,用近似路径约束替代路径约束。这些条件(顾名思义)不太精确,但更容易解决。关键的是,它们不需要缓慢的求解器。Eclipser仍然必须解决这些近似的“简单”约束,但它可以假设它们要么是简单且线性的(在这种情况下,廉价的技术就足够了),要么至少是单调的,在这种情况下Eclipser使用二分搜索而不是求解器调用。如果真实约束既不是线性的也不是单调的,Eclipser将无法生成相关输入,但模糊测试可能会让它在此失败的情况下取得进展。在实践中,符号执行也经常因为此类约束而失败,但会在浪费大量计算努力后出现求解器超时。Eclipser将更快地产生一些输入(尽管不一定是满足太难解决条件的输入)。

为什么您应该关心?

Eclipser之所以有趣,主要是因为作者报告称,在coreutils的代码覆盖率方面,它的表现优于KLEE;在LAVA-M基准测试中检测到的错误方面,优于AFLFast、LAF-intel、VUzzer和Steelix;最引人注目的是,在真实Debian包中检测到的错误方面,优于AFLFast和LAF-intel。Debian实验产生了八个新的CVE。

鉴于这种有希望的性能,我们决定将Eclipser集成到DeepState中,使您可以轻松地将Eclipser方法应用于单元测试。开箱即用,DeepState已经可以与Eclipser一起使用。该模糊测试工具适用于任何将文件作为输入的程序。DeepState适用于我们尝试过的所有基于文件的模糊测试工具。然而,重要的是使用正确的参数与Eclipser一起运行DeepState,否则Eclipser基于QEMU的插桩将无法工作。由于Eclipser以其他工具无法使用的自定义格式存储测试,因此还需要一些手动努力来为DeepState生成独立的测试用例和崩溃输入。因此,我们添加了一个简单的前端,使您(和我们)的生活更轻松。

Eclipser论文示例

DeepState示例目录包含了Eclipser论文中使用的主要示例的DeepState化版本代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <deepstate/DeepState.hpp>
using namespace deepstate;
#include <assert.h>

int vulnfunc(int32_t intInput, char * strInput) {
   if (2 * intInput + 1 == 31337)
      if (strcmp(strInput, "Bad!") == 0)
         assert(0);
   return 0;
}

TEST(FromEclipser, CrashIt) {
   char *buf = (char*)DeepState_Malloc(9);
   buf[8] = 0;
   vulnfunc(*((int32_t*) &buf[0]), &buf[4]);
}

尝试此示例的最简单方法是构建DeepState docker镜像(是的,DeepState现在使创建功能齐全的docker镜像变得容易):

1
2
3
4
$ git clone https://github.com/trailofbits/deepstate
$ cd deepstate
$ docker build -t deepstate . -f docker/Dockerfile
$ docker run -it deepstate bash

构建docker镜像需要一段时间:DeepState、AFL和libFuzzer很快,但构建Eclipser是一个相当复杂的过程。

一旦进入DeepState docker镜像:

1
2
$ cd deepstate/build/examples
$ deepstate-eclipser ./FromEclipser --timeout 30 --output_test_dir eclipser-FromEclipser

Eclipser不需要完整的30秒;它几乎立即产生一个崩溃输入,将其保存在eclipser-FromEclipser/crash-0中。我们尝试的其他模糊测试工具AFL和libFuzzer,即使给它们四个小时生成测试,也无法找到崩溃输入。它们分别生成和执行数千万和数亿的输入,但没有一个满足产生崩溃的条件。即使使用libFuzzer的值配置文件也没有帮助。

自己运行实验很容易:

1
2
$ mkdir foo; echo foo > foo/foo
$ afl-fuzz -i foo -o afl-FromEclipser -- ./FromEclipser_AFL --input_test_file @@ --no_fork --abort_on_fail

1
2
3
$ mkdir libFuzzer-FromEclipser
$ export LIBFUZZER_EXIT_ON_FAIL=TRUE
$ ./FromEclipser_LF libFuzzer-FromEclipser -use_value_profile=1

当您厌倦等待时,您会想要中断这两个运行。

angr和Manticore都在几秒钟内找到了这个崩溃输入。区别在于,虽然Eclipser能够像二进制分析工具一样处理这个玩具示例,但二进制分析工具无法扩展到复杂问题,如测试类似ext3的文件系统、测试Google的leveldb,或需要更长测试才能触及有趣行为的代码,如红黑树实现。Eclipser令人兴奋,因为它在文件系统和红黑树方面都优于libFuzzer,但仍然可以解决“你需要符号执行”的问题,如FromEclipser.cpp。

幕后:向DeepState添加Eclipser支持

如上所述,原则上添加对Eclipser或大多数基于文件的模糊测试工具的支持“毫无难度”。DeepState使使用文件作为向程序传递输入方式的模糊测试工具轻松为参数化单元测试生成值。然而,找出与给定模糊测试工具一起使用的正确DeepState参数可能很困难。起初我们认为Eclipser不起作用,因为如果DeepState分叉运行测试,它就不起作用。一旦我们使用no_fork运行DeepState,一切都很顺利。我们生成像deepstate-eclipser这样的前端的部分目标是确保您永远不必处理此类神秘故障。设置Eclipser运行、解析命令行选项(将DeepState工具参数约定转换为Eclipser的参数)以及让Eclipser从结果中生成独立测试文件的完整代码仅需57行代码。我们希望看到用户提交更多简单的“前端”到其他有前途的模糊测试工具,这些工具需要一点额外设置才能与DeepState一起使用!

那么,这是最好的模糊测试工具吗?

像Eclipser这样的测试生成技术的进步是否会淘汰DeepState支持许多不同后端的目标?答案是“不太可能”。虽然Eclipser令人兴奋,但我们的初步测试表明,在文件系统和红黑树方面,它的表现略逊于每个人最喜欢的驮马模糊测试工具AFL。事实上,即使在我们使用DeepState深入探索的一小部分测试问题中,我们也看到了Eclipser表现最好的实例、libFuzzer表现最好的实例以及AFL表现最好的实例。红黑树中的一些错误需要专门的符号执行测试工具才能找到(我们发现Eclipser没有帮助)。此外,即使一个模糊测试工具在某个示例中总体表现最好,它也可能不是在找到该示例的某个特定错误方面最好。

研究文献和模糊测试工具使用的实践智慧反复表明,即使一个模糊测试工具足够好“击败”其他模糊测试工具(从而在ICSE上发表论文),它也总是有表现比“老”、“过时”模糊测试工具差的实例。在模糊测试中,多样性不仅有帮助,而且至关重要,如果您真的想找到每一个错误的最佳机会。没有模糊测试工具会对所有被测程序或给定真实世界程序中的所有错误都是最好的。

Eclipser论文的作者认识到了这一点,并指出他们的技术是对Angora模糊测试工具中使用的技术的补充。Angora分享了Eclipser的一些目标,但依赖于关于分支距离的元启发式方法,而不是近似路径条件,并使用细粒度污点分析来渗透一些Eclipser无法处理的分支。Angora还需要源代码。Eclipser的一个大优势是,与AFL(在非QEMU模式下)或libFuzzer不同,它不需要您使用额外的插桩重新构建任何要使用DeepState测试的库。在Eclipser论文撰写时,Angora还无法进行比较,但它最近发布,是另一个与DeepState完全集成的良好候选。

Eclipser是添加到您的模糊测试工具库中的一匹好马,但它不会赢得每一场比赛。随着新的令人兴奋的模糊测试工具的出现,DeepState支持许多模糊测试工具的能力只会变得更加重要。如果只是改变变量并执行FUZZER=FOO make; deepstate-foo ./myprogram,使用多样化的模糊测试工具阵列很容易,而如果需要为每个工具重写测试,则几乎不可能。在不久的将来,我们计划使生活更轻松,并支持自动集成模式,其中DeepState利用多个模糊测试工具更积极地测试您的代码,而您除了决定要使用多少核心外无需任何努力。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计