-
-
[翻译]模糊测试: 初学者入门指南
-
发表于: 2018-4-7 18:29 4884
-
模糊测试简介
模糊测试(Fuzzing), 简而言之, 就是为了触发新的或不可预见的代码执行路径或bug而在程序中插入异常的, 非预期的, 甚至是随机的输入. 因为模糊测试涉及到为目标提供大量的测试样例, 因此至少也会实现部分自动化. 模糊测试可以也应当用于测试每个需要接受某种形式输入的接口. 实际上, 模糊测试最起码就应该拿来用于测试每个从潜在恶意来源(比如互联网或用户提供的文件)获取输入的接口.
模糊测试是对其他测试技术的补充. 由模糊测试揭露出的问题往往是开发人员不太可能构建的输入(例如, 在处理一些边界情况, 数据正确性和错误处理例程时的输入)触发的. 在常规自动化测试过程中, 模糊测试扩大了代码覆盖范围, 提高了代码覆盖率测试程度. 通过模糊测试使用的非预期输入通常会触发一些平时不会触发的执行流.
很多地方都需要进行模糊测试. 它是你系统开发生命周期(SDLC)的一部分, 在这部分里, 你需要确保你完成了改善目标所要的系统性工作, 或是你只是想解决一些bug也行. 要如何费心于模糊测试取决于你的最终目标和相关资源, 本文只是帮你如何从模糊测试中获取更多的回报.
开始之前
也许到现在你已经跃跃欲试了. 很多组织和个体经常急于根据博客文章中的思路或会议上看到的酷炫演示来进行一次模糊测试, 这虽不一定是坏事, 但我们经常可以看到在模糊测试系统的背后有着大量的工作投入, 这些模糊测试系统仅在作者分配去完成其他任务之前有在使用, 稍加改动就会破坏兼容性. 更糟糕的是这些模糊测试系统长年消耗硬件资源, 却经常没能得出什么结果来. 就如同软件开发项目的其他任何部分一样, 测试自动化和模糊测试与否, 都需要一定的规划, 维护和提交.
你想拿fuzz干什么?
这是一个简单的问题, 但是答案却并不一定如你所想那样显而易见. 如果你已经有了一个目标, 那很不错. 如果没有, 那你就得去找一个接受输入的接口. 接口可以是对外的, 像是网络连接, 可以是一些文件. 当然也完全可以是对内的, 像是一个你代码正在使用的实用程序库(utility)里的函数调用约定. 模糊测试就是为你所选择的接口创建输入, 并观察这些接口如何处理这些极端的输入. 你可以通过威胁建模(Threat modelling)和回执数据流图来发现目标所拥有的潜在接口.
在每个接口背后可以有许多软件层, 选择对哪个层进行模糊测试就显得至关重要, 因为输入要到达那个层, 就需要通过前面各层的所有检查.
举个例子, 我们来看一个接收带签名二进制数据的HTTP服务器. 我们有一个含JSON字符串的二进制数据, 字符串里是我们应用程序要用到的值. 在这个例子里, 我们就有4个潜在的层需要进行模糊测试:
- 服务器接收的HTTP消息
- 二进制数据的签名校验
- JSON字符串解析
- 我们处理实际值的代码
暂且假定我们的HTTP, 签名和JSON库都是鲁棒的(我们并不想以这些库为目标). 要对我们自己的代码进行模糊测试, 我们就需要生成这些实际值, 然后将这些实际值包装为JSON字符串, 对二进制数据签名, 创建一个HTTP消息并将其发送给目标. 除非我们已经有了可以复用的自动化测试代码, 否则单独构建这些测试样例需要相当长的时间. 在堆栈中进行模糊测试也会不断带来开销, 并且在更改某些层时也更容易被破坏.
在模糊测试中, 测试样例的吞吐量也相当关键. 你应当考虑下目标是否有一些可禁用或绕过的功能, 以减少开销并扩大模糊测试覆盖范围. 通常我们实现一个直接使用模糊值调用目标代码的小程序可以带来不少好处. 在上面这个例子里, 写一个直接将值传递给我们处理代码的程序, 就可以绕过发送网络消息, 好几次哈希计算, 加密检查, JSON转字符串以及解析这些步骤. 在一些优化更好的模糊测试环境里, 诸如不必要的日志记录, CRC校验, 文件I/O以及远程资源调用等功能都会在一个更适合模糊测试的模式("fuzz-friendly mode")下禁用. 我们可以用一些ifdef
, 创建虚拟(Mock)函数或其他仅用于构建模糊测试的配置来实现一个对模糊测试友好的模式("fuzz-friendly mode"). 当然, 当你在进行一些会改变目标行为的模糊测试优化时, 你必须能确保这些修改不会创建或隐含任何的bug.
不过, 在刚开始时, 不要太担心想着要一个高效的每秒将数千个测试用例注入进优化的模糊测试环境中去的策略. 开始模糊测试的一个非常有效的办法就是将随机(或位翻转)的数据发送到你找到的任何接口去. 如果这能很快地找到问题, 那么你就算是找到了你第一个目标接口了!
你想找寻的是什么?
很多时候当你进行模糊测试, 目标可能会崩溃, 这是很难避免的. 然而, 为了能充分利用你的劳动, 你就还需要找到其他的错误情况. 目标都有它自己的功能需求, 需求里定义了程序应该干什么, 你可以从跟这个点找到它不应该做的事情. 除此之外, 所有程序都可能存在逻辑缺陷, 可能导致内存泄露或CPU及内存消耗过多等问题. 根据底层技术, 目标也可能容易发生内存腐败, 命令注入或其他应当注意的问题类别.
起初, 所有可能的潜在问题类型及其影响都应该记录下来. 现有的检测工具和技术可以适用于不同的问题类型, 但有些检测工具和技术使用起来相当复杂, 或是执行开销高昂. 影响评估有助于你判断使用工具或某技术是否值得. 例如, 图像压缩中颜色值的错误计算可能影响很小, 但却难以检测. 如果你只是想找到这些问题, 那么一些能使用模糊的和无效的身份绕过验证的地方十分致命, 也相当容易被检测到.
在研究不同的工具和技术时, 还要考虑其他的自动化测试方法. 例如, 在很多情况下, 你会发现你的单元测试(unit test)一次又一次触发了一些错误, 但你可能因为没有用到单元测试而无法发觉.
如何进行模糊测试?
模糊测试是一项一人一机器就能执行的技术. 中等规模的模糊测试可以作为持续集成(CI)系统的一部分来执行, 针对不同的项目每天运行几次模糊测试. 大规模的模糊测试可以通过使用数百上千台机器在云端并行自动地模糊测试. 所有这些环境都有着最终系统必须满足的不同需求. 因为最初基本不会考虑到与另一些部件的可用性, 所以通常情况下不会使用大型fuzzer
和所有的测试相同, 测试规模越大, 自动化就越重要. 使用单个实例来fuzz你的程序非常简单. 你可用不断地将模糊输入注入到目标程序中, 直到触发bug, 然后修复bug, 如此不断重复即可. 但当你同时处理成百上千个实例时, 你就会知道为什么重复筛选等功能相当重要了. 在一个在CI中针对不同构建版本并行运行模糊测试的大型组织中, 你也可能会忽视自动问题报告, 最小化测试用例和补丁验证这些需要注意的问题.
[招生]科锐逆向工程师培训(2025年3月11日实地,远程教学同时开班, 第52期)!