智能汽车安全新媒体
模糊测试是一种发现软件缺陷的方法,它通过向程序提供随机输入来查找导致崩溃的测试用例。对程序进行模糊测试可以让我们快速了解程序的整体健壮性,并帮助发现和修复关键漏洞。
模糊测试归根结底是一种黑盒技术,不需要访问源代码,但仍可用于有源代码的软件,因为它有可能更快地发现错误,并避免审查大量代码的需要。一旦检测到崩溃,如果有源代码,修复起来就会容易得多。
01
模糊测试的优缺点
优点:
几乎不费吹灰之力就能提供结果:模糊器一旦启动并运行,就可以让它在没有交互的情况下查找错误,时间可长达数小时、数天或数月。
可发现人工审核中遗漏的漏洞。
全面了解目标软件的健壮性。
缺点:
无法发现所有漏洞:模糊测试可能会漏掉那些不会引发程序全面崩溃的漏洞,也不太可能触发那些只在非常特殊的情况下才会触发的漏洞。
产生的崩溃测试用例可能难以分析,因为模糊行为并不能让你了解软件的内部运行方式。
输入复杂的程序可能需要更多的工作才能产生足够智能的模糊器,以获得足够的代码覆盖率。
02
智能和傻瓜模糊测试
模糊器向软件提供随机输入。输入的形式可以是网络协议、某种格式的文件或用户直接输入。模糊输入可以是完全随机的,不知道预期输入应该是什么样子,也可以是经过一些修改后看起来像有效输入的。
生成完全随机输入的模糊器被称为 "哑 "模糊器,因为它对所模糊的程序没有内置智能。傻瓜式模糊器只需最少量的工作即可生成(可以简单到在程序中加入/dev/random管道)。这种少量工作可以以极小的成本产生结果,这也是模糊测试的一大优势。
不过,有时程序只有在输入的特定方面出现时才会执行某些处理。
例如,程序可能会接受输入中的 "姓名 "字段,而该字段可能会与 "姓名长度"相关联。如果这些字段的存在形式不足以让程序识别,程序可能永远不会尝试读取名称。但是,如果这些字段以有效的形式存在,但长度值被设置为不正确的值,程序可能会读取超出包含名称的缓冲区的内容,并引发崩溃。
如果没有至少部分有效的输入,这种情况就不太可能发生。在这种情况下,可以使用"智能"模糊器。这些模糊器在编程时需要了解输入格式(即协议定义或文件格式规则)。然后,模糊器可以构建大部分有效输入,并只对基本格式内的部分输入进行模糊处理。
模糊器的智能程度越高,对协议或文件格式的处理就越深入,但自己的工作量也就越大。需要在这两个极端之间找到平衡。一开始可以使用更笨的模糊器,随着测试软件代码质量的提高,再提高模糊器的智能。如果使用简单的模糊器会导致大量崩溃,那么在代码质量提高到需要智能模糊器的程度之前,就没有必要花费很长时间来提高它的智能。
03
模糊器的类型
从广义上讲,模糊器可根据其创建程序输入的方式分为两类–基于突变的模糊器和基于生成的模糊器。本文将详细介绍这两类模糊器,并简要介绍一种名为 "进化模糊 "的高级技术。
突变
基于突变的模糊器可以说是比较容易创建的模糊器类型之一。这种技术适合傻瓜式模糊器,但也可用于更智能的模糊器。通过突变,有效输入样本会被随机突变,从而产生畸形输入。
傻瓜式突变模糊器只需选择一个有效的输入样本,然后随机改变其中的部分内容即可。对于许多程序来说,这可以提供令人惊喜的结果,因为输入往往与有效输入有足够大的相似性,因此无需进一步的智能就可以实现良好的代码覆盖。
可以让模糊器对样本进行一定程度的解析,以确保它只修改特定部分,或者不会破坏输入的整体结构,从而立即被程序剔除。某些协议或文件格式会包含校验和,如果任意修改这些校验和,它们就会失效。基于突变的模糊器通常应修复这些校验和,以便接受输入进行处理,或者只测试校验和验证代码,而不测试其他代码。
下文介绍了基于突变的模糊器可以使用的两种有用技术。
①重放
模糊器可以获取已保存的样本输入,并在对其进行变异后进行简单的重放。这在文件格式模糊测试中效果很好,可以保存大量样本文件并对其进行模糊处理,然后提供给目标程序。
简单或无状态的网络协议也能通过重放进行有效的模糊测试,因为模糊测试器不需要发出大量合法请求就能深入了解协议。对于更复杂的协议,重放可能会更加困难,因为模糊器可能需要对程序做出动态响应,以便继续深入协议进行处理,或者该协议本身就是不可重放的。
②中间人或代理
中间人(MITM)是渗透测试人员和黑客使用的一种技术,但它也可用于基于突变的网络协议模糊测试。MITM指的是将自己置于客户端和服务器(或点对点网络中的两个客户端)中间,拦截并可能修改它们之间传递的信息。这样,你就像两者之间的代理。
通过将模糊器设置为代理,它可以根据我们是在模糊服务器还是在模糊客户端来改变请求或响应。同样,模糊器可以不具备协议智能,只是随机改变一些请求而不改变其他请求,也可以智能地针对我们感兴趣的协议的特定层级发出请求。
基于代理的模糊测试可以让我们利用现有的网络程序部署,快速插入模糊测试层,而无需让模糊测试器本身像客户端或服务器一样运行。
生成
基于生成的模糊器实际上是从头开始生成输入,而不是改变现有输入。尽管生成完全随机的数据在技术上也属于生成,但基于生成的模糊器通常需要一定程度的智能,才能构建出至少对程序有一定意义的输入。
生成型模糊器通常会将协议或文件格式分割成若干小块,然后按照有效顺序构建起来,并随机对其中一些小块进行独立模糊处理。这样就能生成保持整体结构的输入,但在该结构中包含不一致的数据。这些块的粒度和构建它们的智能程度决定了模糊器的智能水平。
基于突变的模糊处理可以产生与生成模糊处理类似的效果(因为随着时间的推移,突变会被随机应用,而不会完全破坏输入的结构),而生成输入则确保了这种效果。生成模糊测试还能更容易地深入协议,因为它可以构建有效的输入序列,对通信的特定部分进行模糊测试。它还允许模糊器充当真正的客户端/服务器,在无法盲目重放的情况下生成正确的动态响应。
进化
进化模糊测试是一种高级技术,我们在此仅作简要介绍。它允许模糊器利用来自每个测试用例的反馈,逐渐学习输入的格式。例如,通过测量每个测试用例的代码覆盖范围,模糊器可以启发式地计算出测试用例的哪些属性会对特定区域的代码产生影响,从而逐步演化出一套能够覆盖大部分程序代码的测试用例。
进化模糊法通常依赖于与遗传算法类似的其他技术,可能需要某种形式的二进制仪器才能正确运行。
04
到底在模糊什么?
即使是相对笨拙的模糊器,也必须牢记测试用例实际上可能触及代码的哪一部分。举个简单的例子,如果正在模糊一个使用TCP/IP的应用协议,而模糊器随机改变了原始数据包捕获,那么很可能破坏了TCP/IP数据包本身,输入根本不可能被应用处理。
或者,如果正在测试一个将文本图像解析为真实文本的 OCR 程序,但却对整个图像文件进行了变异,那么最终测试图像解析代码的次数可能会多于实际的 OCR 代码。如果想专门针对 OCR 处理,可能希望保持图像文件头的有效性。
同样,可能会生成随机性很强的输入,以至于无法通过程序中的初始正确性检查,或者代码中包含没有纠正的校验和。这样就只能测试程序中的第一个分支,而无法深入程序代码。
05
模糊器剖析
为了有效运行,模糊器需要执行一系列重要任务:
生成测试用例
记录测试用例或重现测试用例所需的任何信息
与目标程序连接,提供测试用例作为输入
检测崩溃
Fuzzer通常会将其中的许多任务分拆成不同的模块,例如,有一个库可以更改数据或根据定义生成数据,另一个库则向目标程序提供测试用例,等等。
下面是关于这些任务的一些说明:
①生成测试用例
测试用例的生成会因采用基于突变的模糊处理还是基于生成的模糊处理而有所不同。无论采用哪种方法,都会有需要随机变换的内容,无论是特定类型的字段还是任意数据块
这些变换可以是完全随机的,但值得注意的是,边缘和角落情况往往是程序错误的根源。因此,可能会倾向于使用这些情况,并包含以下值:
②重现性
重现测试用例的最简单方法是记录检测到崩溃时使用的准确输入。不过,在某些情况下,还有其他更方便的方法来确保重现性。
其中一种方法是存储用于生成测试用例随机部分的初始种子,并确保所有后续随机行为都遵循可追溯到该种子的路径。使用相同的种子重新运行模糊器,其行为应该是可重现的。例如,您可以只记录测试用例编号和初始种子,然后使用该种子快速重新执行生成,直至达到给定的测试用例。
当目标程序可能会根据过去的输入积累依赖性时,这种技术就很有用。以前的输入可能会导致程序在内存中初始化各种项目,而这些项目必须存在才能触发错误。在这种情况下,仅仅记录崩溃测试用例不足以重现错误。
③与目标程序交互
与目标程序进行交互以提供模糊输入通常很简单。对于网络协议,可能只需通过网络发送测试用例,或响应客户端请求即可;对于文件格式,可能只需使用指向测试用例的命令行参数执行程序即可。
不过,有时输入的形式并不容易自动生成,或者执行每个测试用例的程序脚本开销很大,速度很慢。在这种情况下,创造性思维可以揭示使用正确数据执行相关代码的方法。例如,可以在内存中人为地设置一个程序来执行解析函数,而输入作为参数完全在内存中提供。这样,程序就无需在每个测试用例之前进行冗长的加载过程,而完全在内存中生成和提供测试用例,而不是通过硬盘驱动器,也可进一步提高速度。
④碰撞检测
崩溃检测对于模糊测试至关重要。如果不能准确判断程序何时崩溃,就无法确定测试用例是否触发了错误。有几种常见的方法可以解决这个问题:
安装调试器
可以编写调试器脚本,以便在检测到程序崩溃时立即提供崩溃跟踪。不过,附加调试器会大大降低程序的运行速度,而且会造成相当大的开销。在给定时间内生成的测试用例越少,发现崩溃的机会就越小。
查看进程是否消失
与安装调试器相比,只需在执行测试用例后查看目标进程的进程 ID 是否仍存在于系统中即可。如果进程消失了,那么它很可能崩溃了。如果想了解更多有关崩溃的信息,可以稍后在调试器中重新运行测试用例,甚至可以在每次崩溃时自动执行此操作,同时还能避免每次都附加调试器所带来的速度减慢。
⑤超时
如果程序对测试用例做出正常响应,可以设置一个超时时间,在此时间后假定程序崩溃或冻结。这也可以检测出导致程序无响应但不一定终止的错误。
无论使用哪种方法,只要程序崩溃或无响应,就应重新启动程序,以便继续进行模糊测试。
06
模糊测试质量
速度
速度可能是模糊测试中最重要的因素之一。每秒/每分钟可以运行多少个测试用例?合理的数值当然取决于目标,但执行的测试用例越多,在给定时间内发现崩溃的可能性就越大。模糊测试是随机的,因此每个测试用例就像一张彩票,需要尽可能多的测试用例。
有很多方法可以提高测试用例的速度,例如提高生成或突变例程的效率、并行化测试用例、减少超时或在不显示图形用户界面的 "无头"模式下运行程序。
对崩溃进行分类
当然,发现崩溃只是整个过程的开始。找到崩溃测试用例后,需要对其进行分析,找出错误所在,并对其进行修复或编写漏洞利用程序。
通过对崩溃案例进行分类,可以根据您最感兴趣的案例来确定它们的优先级。这还可以帮助识别一个测试用例何时触发了与另一个测试用例相同的错误,因此只需保留与独特崩溃相关的案例。为此,需要一些有关崩溃的自动化信息,以便做出决定。将目标连接到调试器上运行测试用例可以提供崩溃跟踪,可以通过解析该跟踪来查找异常类型、寄存器值、堆栈内容等值。
微软提供的一个工具可以帮助实现这一点,它被称为 !exploitable (读作 “bang exploitable”),它与Windbg 调试器配合使用,可以根据它认为错误的可利用程度对崩溃进行分类。
减少测试用例
由于模糊测试会随机更改输入,因此崩溃测试用例通常会有多个与触发错误无关的更改。测试用例缩减就是将测试用例缩小到触发错误所需的有效输入的最小改动集,这样在分析时只需关注输入的这一部分。
这种缩小可以手动进行,也可以由模糊器自动完成。当遇到崩溃测试用例时,模糊器可以多次重新执行测试用例,逐步减少对输入的修改,直到剩下最小的修改集,同时仍能触发错误。这可以简化分析,还有助于对崩溃测试用例进行分类,因为您可以准确地知道输入的哪些部分受到了影响。
代码覆盖率
代码覆盖率是衡量模糊器执行了多少程序代码的指标。其原理是,覆盖率越高,实际测试的程序就越多。代码覆盖率的测量可能比较棘手,通常需要二进制仪器来跟踪代码的哪些部分被执行。你也可以用不同的方法来衡量代码覆盖率,比如按行、按基本块、按分支或按代码路径。
就模糊测试而言,代码覆盖率并不是一个完美的衡量标准,因为有可能在执行代码时并没有发现其中的错误,而且经常会有一些代码区域几乎从未被执行过,例如安全错误检查,这些检查不太可能真正需要,而且我们也不太可能对其感兴趣。不过,某种形式的代码覆盖率测量可以让我们深入了解模糊器在程序中触发了什么,尤其是当模糊测试完全是黑盒测试,而且我们可能还不太了解程序的内部运作时。对代码覆盖率有帮助的工具和技术包括 Pai Mei、Valgrind、DynamoRIO 和 DTrace。
07
模糊处理框架
有许多现有框架可以创建模糊器,而无需从头开始。其中一些框架比较复杂,可能仍需要一段时间才能为目标创建一个有效的模糊器;相比之下,另一些框架则采用非常简单的方法。这里列出了部分这些框架和模糊器,供参考:
Radamsa
Radamsa 设计得简单易用、灵活多变。它尝试 "只适用于 "各种输入类型,并包含多种用于突变的模糊算法。
Sulley Sulley
提供了一个全面的生成框架,允许表示结构化数据以进行基于生成的模糊处理。它还包含帮助记录测试用例和检测崩溃的组件。
Peach
Peach 框架可对文件格式和网络协议进行智能模糊测试。它既能执行基于生成的模糊测试,也能执行基于突变的模糊测试。
SPIKE
SPIKE 是一款网络协议模糊器。它需要良好的 C 语言知识才能使用,设计在 Linux 上运行。
Grinder
Grinder 是一款网络浏览器模糊器,还具有帮助管理大量崩溃的功能。NodeFuzz
NodeFuzz
NodeFuzz 是一款基于 nodejs 的网络浏览器工具包,其中包括从客户端获取更多信息的仪器模块。
内容来源:
https://blog.csdn.net/qq_61346813/article/details/134691772
- THE END -
专业社群
精品活动推荐
因文章部分文字及图片涉及到引用,如有侵权,请及时联系17316577586,我们将删除内容以保证您的权益。