- Author:ZERO-A-ONE
- Date:2021-03-28
一、简介
Fuzz本意是“羽毛、细小的毛发、使模糊、变得模糊”,后来用在软件测试领域,中文一般指“模糊测试”。模糊测试(fuzzing)是一种通过向程序提供非预期的输入并监控输出中的异常来发现软件中的故障的方法。
随着互联网基础设施的完善,网络协议广泛应用于各个应用中,与其相关的安全问题也在近年来被一直发掘出来。网络协议中最关键的问题在于网络协议的实现缺陷,这些缺陷产生的许多漏洞可以使得恶意攻击者发动诸如DDos,远程代码执行等攻击。人工地对数量巨大的网络协议进行审计是不现实的,如何利用Fuzz技术减轻人工操作,提高发现漏洞的效率变成了一个备受研究者关心的问题
传统测试技术面临以下几个问题:
- 无法穷举所有的输入作为测试用例
- 我们无法想到所有可能的异常场景
而模糊测试主要有以下优点:
- Fuzzing技术首先是一种自动化技术,即软件自动执行相对随机的测试用例。因为是依靠计算机软件自动执行,所以测试效率相对人来讲远远高出几个数量级
- Fuzzing技术本质是依赖随机函数生成随机测试用例,随机性意味着不重复、不可预测,可能有意想不到的输入和结果
- 根据概率论里面的“大数定律”,只要我们重复的次数够多、随机性够强,那些概率极低的偶然事件就必然会出现。Fuzzing技术就是大数定律的典范应用,足够多的测试用例和随机性,就可以让那些隐藏的很深很难出现的Bug成为必然现象
这个报告我们将要主要讨论一下问题:
- 什么是模糊测试
- 模糊测试目前的实现方式
- 模糊测试存在的框架与其改进
- 在网络环境中模糊测试的应用与发展
- 针对DDoS环境模糊测试的讨论
- 目前已经完成的工作
二、概念
2.1 基本原理
模糊测试(fuzzing)是一种通过向程序提供非预期的输入并监控输出中的异常来发现软件中的故障的方法,用于模糊测试的模糊测试器(fuzzer)分为两类:
- 基于变异的模糊测试器,它通过对已有的数据样本进行变异来创建测试用例
- 例如早年间IE和Flash的fuzzer很多依托于通过爬虫搜集的大量样本集合,与现在流行的AFL
- 基于生成的模糊测试器,它为被测试系统使用的协议或文件格式建模,基于模型生成输入并据此创建测试用例
- peach为代表的模版流派:研究人员通过XML等方法定义输入数据的格式,这种常见于文件格式和协议的fuzzer
2.2 模糊测试流程
模糊测试通常包含下面几个基本阶段:
- 确定测试目标:确定目标程序的性质、功能、运行条件和环境、编写程序的语言、软件过去所发现的漏洞信息以及与外部进行交互的接口等
- 确定输入向量:例如文件数据、网络数据和环境变量等
- 生成模糊测试数据:在确定输入向量之后设计要模糊测试的方法和测试数据生成算法等
- 执行模糊测试数据:自动完成向测试目标发送大量测试数据的过程,包括启动目标进程、发送测试数据和打开文件等
- 监视异常:监视目标程序是否产生异常,记录使程序产生异常的测试数据和异常相关信息
- 判定发现的漏洞是否可被利用:通过将产生异常的数据重新发送给目标程序,跟踪异常产生前后程序相关的处理流程,分析异常产生的原因,从而判断是否可利用
2.3 基本要求
要实现高效的模糊测试,通常需要满足下面几个方面的要求:
- 可重现性:测试者必须能够知道使目标程序状态变化所对应的测试数据是什么,如果不具备重现测试结果的能力,那么整个过程就失去了意义。实现可重现性的一个方法是在发送测试数据的同时记录下测试数据和目标程序的状态
- 可重用性:进行模块化开发,这样就不需要为一个新的目标程序重新开发一个模糊测试器
- 代码覆盖:指模糊测试器能够使目标程序达到或执行的所有代码及过程状态的数量
- 异常监视:能够精确地判定目标程序是否发生异常非常的关键
2.4 存在的问题
人们通常以monkey testing来指代最原始的fuzz,就像著名的无限猴子定理一样。但是人类的算力是有限的,随机化的输入虽然终究能覆盖所有的输入空间,在人类未来可预见的算力水平下近乎天方夜谭
所以就目前来说模糊测试中存在以下几个问题:
- 具有较强的盲目性:即使熟悉协议格式,依然没有解决测试用例路径重复的问题,导致效率较低
- 测试用例冗余度大:由于很多测试用例通过随机策略产生,导致会产生重复或相似的测试用例
- 对关联字段的针对性不强:大多数时候只是对多个元素进行数据的随机生成或变异,缺乏对协议关联字段的针对性
三、实现方式
就像开头说的一样,模糊测试主要有两种流派,一种是基于种子的变异流派,一种是基于模型生成的流派
3.1 生成
通常情况下,应用程序都会对输入的数据对象进行格式检查。通过分析输入到程序的数据对象的结构以及其组成元素之间的依赖关系,构造符合格式要求的测试用例从而绕过程序格式检查,是提高模糊测试成功率的重要步骤
应用程序的输入数据通常都遵循一定的规范,并具有固定的结构。例如:网络数据包通常遵守某种特定的网络协议规范,文件数据通常遵守特定的文件格式规范。输入数据结构化分析就是对这些网络数据包或文件格式的结构进行分析,识别出特定的可能引起应用程序解析错误的字段,有针对性地通过变异或生成的方式构建测试用例。通常关注下面几种字段:
- 表示长度的字段
- 表示偏移的字段
- 可能引起应用程序执行不同逻辑的字段
- 可变长度的数据等
应用程序所能处理的数据对象是非常复杂的。例如 MS Office 文件是一种基于对象嵌入和链接方式存储的复合文件,不仅可以在文件中嵌入其他格式的文件,还可以包含多种不同类型的元数据。这种复杂性导致在对其进行模糊测试的过程中产生的绝大多数测试数据都不能被应用程序所接受。数据块关联模型是解决这一问题的有效途径。该模型以数据块为基本元素,以数据块之间的关联性为纽带生成畸形测试数据。其中,数据块是数据块关联模型的基础。通常一个数据对象可以分为几个数据块,数据块之间的依赖关系称为数据关联
数据块的划分通常遵循三个基本原则:
- 使数据块之间的关联性尽可能的小
- 将具有特定意义的数据划分为一个数据块
- 将一段连续且固定不变的数据划分为同一个数据块
数据块关联模型的划分:
- 关联方式
- 内关联:指同一数据对象内不同数据块之间的关联性
- 长度关联:数据对象内某一个或几个数据块表示另一数据块的长度。是文件格式、网络协议和ActiveX控件模糊测试中最常见的一种数据关联方式
- 外关联:指属于多个不同数据对象的多个不同数据块之间存在的关联性
- 内容关联:某个数据对象的某个数据块表示另一个(或同一个)数据对象的另一个数据块的值。在需要用户验证的网络协议应用中经常出现
- 内关联:指同一数据对象内不同数据块之间的关联性
- 关联强度
- 强关联:关联数据块的数量大于等于非关联数据块的数量
- 弱关联:关联数据块的数量小于非关联数据块的数量
- 评价标准
- 有效数据对象效率:构造的畸形数据对象个数与能够被应用程序所接受处理的数据对象个数的比率
3.2 变异
传统的变异方式主要是有两种:
- 随机方法:简单地产生大量伪随机数据给目标程序
- 模糊启发式:将模糊字串或模糊数值列表中包含的特定潜在危险值称作模糊启发式
- 边界整型值:整型值上溢、下溢、符号溢出等
- 字符串重复:堆栈溢出等
- 字段分隔符:将非字母数字字符如空格、制表符等随机地包含到模糊测试字符串中
- 格式化字符串:最好选择
%s
、%n
等包含到字符串中 - 字符转换和翻译:特别关注对扩展字符的处理
- 目录遍历:在URL中附加
../
之类的符号将导致攻击者访问未授权的目录 - 命令注入:向
exec()
、system()
之类的 API 调用传递未经过滤的用户数据
3.2.1 遗传算法
随着LLVM工具链的成熟和计算资源的大发展,基于编译器插桩的coverage-feedback driven fuzzer渐渐成为了答案之一。传统的Blackbox Fuzzing无法捕捉程序运行时信息,测试效率低下。基于此,**覆盖率引导的灰盒模糊测试技术(Coverage-based Greybox Fuzzing)**开始得到重视与发展。CGF通过插桩等技术捕捉程序控制流信息,通过控制流信息变化来引导Fuzzer选取优异种子进行变异。AFL的诞生则宣告着这个永恒的问题出现了一个高分答案。
AFL为代表的CGF的核心算法其实是遗传算法,将测试用例的生成过程转化为一个利用遗传算法进行数值优化的问题,算法的搜索空间即为待测软件的输入域,其中最优解即为满足测试目标的测试用例。首先,使用初始数据和种子生成数据,然后对数据进行测试和评估,并监控测试过程,如果满足测试终止的条件,就输出测试结果,否则通过选择、杂交、变异生成新的数据
AFL为代表的CGF的主要流程大致如下:
AFL的变异类型主要有:
- bitflip:按位翻转,1变为0,0变为1
- arithmetic:整数加/减算术运算
- interest:把一些特殊内容替换到原文件中
- dictionary:把自动生成或用户提供的token替换/插入到原文件中
- havoc:中文意思是“大破坏”,此阶段会对原文件进行大量变异,具体见下文
- splice:中文意思是“绞接”,此阶段会将两个文件拼接起来得到一个新的文件
总结以下AFL的优势主要在于:
-
基于编译时插桩和自定义bitmap的方法维护coverage信息,以简洁的方式巧妙地解决了以往获取coverage效率太低的问题
-
基于fork/exec的方式极大提高了fuzz效率
-
coverage导向的遗传进化算法简单但惊人地有效
-
在设计上即支持多进程甚至跨集群并联
四、框架与工具
4.1 常用的工具
-
AFL:非常常用的模糊测试工具,Greybox fuzzing 的潮流引领者
-
Libfuzzer:为LLVM的内置fuzzer,和AFL大体上相似,但实现细节上有所不同,如:AFL为每个测试用例都开一个新进程, 而Libfuzzer在单进程内执行所有测试用例,没有进程反复启动的开销;Libfuzzer通过Sanitizer-Coverage Instrumentation来收集block coverage而AFL通过静态插桩收集edge coverage
-
Honggfuzz:也是google开发的fuzzing引擎,和AFL大体上相似。主要特征如下
- 可通过Intel BTS (Branch Trace Store) 或Intel PT (Processor Tracing)硬件方式去计算代码覆盖率。在闭源软件中不进行插桩也能获取覆盖率
- Persistent Fuzzing:在一个进程内针对某个API持续进行fuzzing,不需要每次喂数据都生成一个新的进程
-
AFL plusplus:因为原版AFL在17年以后就没有更新了,各路fuzzing爱好者将各种基于AFL的改进版本的特征融合起来,成为一个++版本。特征如下
- AFLfast的power schudule(seed被fuzz过的次数越少被选中的概率越高)
- MOPT: Optimized Mutation Scheduling for Fuzzers: 粒子群优化算法在线找出当前最合适的mutation operator
- Radamsa and honggfuzz的mutation operator
-
AFLSmart:针对高度结构化输入(PPT/mp3/PDF)的模糊测试工具。AFL针对结构化的输入提供了基于字典的方法,然而仍处于bit级别的变异,无法对一个chunk进行操作。AFLSmart增加了chunk级别的addition, deletion和splicing变异
4.2 CGF的改进
CGF效率的主要影响因素有初始种子、调度算法、变异策略、测试速度和测试精度,为此,提升CGF效率的主要途径为:
- 优化初始种子:Skyfire、Learn&Fuzz
- 聚焦优质种子:AFLFast、AFLGo、EcoFuzz
- 优化变异操作:FairFuzz、MOPT、VUzzer、Angora、Greyone、Driller、PANGOLIN
- 提高测试速率:INSTRIM、UnTracer、Zeror
- 提高测试精度:CollAFL、PTFuzz
但是其实这些本质都是AFL的改型,目前仍然没有跳出AFL开创的CGF模式,DARPA组织的Cyber Grand Challenge尝试将漏洞挖掘与AI进行结合,而最终的结果证明了CGF及其改进版仍是距离自动化漏洞挖掘和利用这一伟大目标最为实际的路径
几乎所有改进版本可以在 hub.docker.com 找到,AFL改进的目的可以认为主要