Fuzzing 基础
在软件开发的领域,测试是必要的一环。我们需要通过动态运行程序,来检测其是否能正常工作、其行为是否能符合我们的预期。在漏洞挖掘领域,尤其是内存漏洞的挖掘领域,动态测试的方法也是漏洞猎手们的主力工具。
手动进行测试是一种灵活的测试方法,但是根据著名的不知出自哪里的自动化原则“如果你重复做了某件事三遍及以上,说明你需要将它自动化”,想要更好地进行测试就必须要将测试的各个步骤全都进行自动化,包括测试样例生成、测试执行以及测试结果检查这整个流程。
所谓 Fuzzing 技术,其核心在与生成随机的测试样例,以测试程序的健壮性(robustness)。通过测试程序在随机输入下的行为,我们可以捕捉到各种非预期的行为,比如抛出未捕获的异常、内存访问错误、直接崩溃等等,只要我们能够识别出相应的表现。对于内存漏洞,Address Sanitizer
就是一个非常关键的、帮助我们识别漏洞的工具。
Coverage
为了判断某个测试的有效程度,以及指导测试样例的生成,我们往往会测量测试中程序的哪一部分被实际执行了,这就是代码覆盖率的概念。测量代码覆盖率在多数情况下是一种简单而完全自动化的方法,而且在指导测试样例生成中扮演着至关重要的地位。
有许多种不同的覆盖率,比如最常用的 statement coverage 以及 branch coverage。前者即跑的代码越多越好,属于是最直观的覆盖率。后者则是面向这样一种场景:
1 if cond:
2 do_something_A()
3 do_something_B()
在计算 statement coverage 时,如果在样例集中有一个满足 cond == TRUE
的样例,这组样例集就可以达到最高的覆盖率;然而这样一来我们就忽略了 cond == FALSE
,也就是没有执行 do_something_A()
而直接执行了 do_something_B()
的情况。
在这种情况下,我们需要 branch coverage,以控制流向量(一条语句指向另一条语句)而不是语句本身来计算测试的覆盖率。
在上面的例子中,statement coverage 以及 branch coverage 的全覆盖率分别为:[1, 2, 3]
和 [(1, 2), (1, 3), (2, 3)]
,尽管数量一样,但后者包含的信息更多。