《The Art, Science, and Engineering of Fuzzing: A Survey》论文阅读
记录时间:2019.11.17,仅供帮助快速阅读与回忆使用,如有翻译或逻辑错误以原PDF为准。
链接:https://arxiv.org/abs/1812.00140
fuzzing的基本类别
- 动态符号执行 dynamic symbolic execution
- 基于语法的用例生成 grammar- based test case generation
- 访问允许 permission testing
- 行为测试 behavioral testing
- 复杂性测试 complexity testing
- 内核测试 kernel testing
- 表现依赖测试representation dependence testing
- 功能测试 function testing
- 鲁棒性测试 robustness
- 利用发展 exploit development
- GUI测试 GUI testing
- 签名生成 signal generation
- 穿透测试 penetration testing
fuzzing的基本术语
- PUT:测试的程序
- 获取内部信息方法:处理器跟踪,系统调用使用,检测(最有效)
- fuzz 输入:PUT所不期望的输入(无法正确处理的输入或无法预料的行为)
- fuzzing:使用输入空间(超出PUT的输入空间)采样的输入执行PUT的行为
- 特性:不一定必须包含预期输入,需多次迭代重复执行,且抽样不一定随机
- fuzz testing:测试PUT是否违反了安全策略
- fuzzer:对PUT执行fuzz testing的程序,根据语义粒度(收集到信息量)分组,详细介绍见下文。
- fuzz campaign:在具体安全策略上运行的一个fuzzer,寻找违反策略的bug
- bug oracle:bug程序或fuzzer部分,确定PUT执行是否违反了具体的安全策略
- fuzz algorithm:模糊算法,依赖于PUT外的一些参数且有通用模型,下文会详细介绍。
- fuzz configuration:模糊配置,由构成算法的参数组成,值类型取决于算法类型,为随机字节流: { (PUT)}或突变率绑定种子: {(PUT, s 1, r1), (PUT, s2, r2), . . .}可存储覆盖引导的fuzzer在配置中存储获得的覆盖。
- seed:PUT输入(通常结构良好),含文件,网络包或一系列UI事件或修改生成的测试用例
- seed pool:fuzzer维护的种子集合,不断扩展
Fuzzer分类
具体工具与分类见上链接pdf。
黑盒测试
- 仅观察输入输出信息,不检查PUT
- IO驱动/数据驱动测试
- 升级:考虑输入结构
白盒测试
- 分析PUT内结构与PUT执行时收集的信息
- 系统探索PUT状态空间
- 动态符号执行DSE(共域测试)
- 符号与具体转换同时执行,具体程序状态用于简化符号约束如具体化系统调用
- 输入符号值构建符号表达式而不计算
- 分支时一真一假,每条路径都建立一个路径谓词,若具体输入满足路径公式,查询SMT建立解决方案生成输入
- 污点分析
- 变量被一个表达式赋值给第二个变量,那么第二变量也是可疑的。污点检验工具在变量间传递运行,直到外部输入潜在地影响到所有的变量。
- 高开销解决方案:缩小范围
- 指定不感兴趣部分
- 灰盒估算路径概率,白盒关注挑战路径
灰盒测试
- PUT和/或执行的内部信息
- 不考虑PUT完整语义
- PUT的轻量级动态分析
- 收集执行的动态信息(如代码覆盖率)
- 近似的不完整信息获得速度与更多输入
Fuzz Algorithm详解
通用的算法模型
- 输入:配置集C,限定时间Tlimit
- 输出:bug集合B
1.process(C)->C
- fuzz campaign开始时执行, fuzz配置输入并返回修改后配置。
- 功能类型:是否支持内存fuzz,分析使用的推断模型,是否使用动态静态分析。
- 方法:代码静态插桩
- 编译时对源码或中间代码执行
- 对PUT依赖库进行单独插桩,并用相同插桩进行编译
- 执行反馈(灰盒输入)
- 检测分支指令计算分支覆盖率
- 路径敏感散列函数解决位向量存储的路径冲突
- 节点覆盖率执行反馈
- 用户自选
- 内存fuzz
- 大型程序只fuzz小部分且不为每个迭代重新生成进程以减少开销
- 初始化后获得PUT快照,新用例写入时恢复快照
- 客户端与服务端交互的大型web程序
- 内存API模糊化:内存中函数处理而不在迭代后恢复PUT状态
- 不可复现性
- 上下文调用需要有效目标
- 不捕获多个函数调用的副作用
- 入口点函数寻找困难
- 线程调度寻找竞态bug
- 显式控制线程调度方式
- 插桩触发不同非确定性行为
- 作用1:种子选择(测量PUT,剔除冗余配置)
- 最小种子集,最大限度扩展覆盖率,计算节点覆盖率(minset)
- 基于分支与计数器根据数量级覆盖
- 基于指令,分支,唯一基本块数量计算覆盖率
- 作用2:种子修剪
- 缩小种子尺寸以获得更高吞吐量
- 优先选择小而非随机种子
- 静态分析保留依赖关系时扩展系统调用减少种子大小
- 作用3:准备驱动程序
- 手工准备且仅运行一次方便间接模糊PUT
2.schedule(C,Telapsed,Tlimit)->conf
- 当前轮fuzz配置,当前时间,限定时间输入。
- 选择fuzz配置进入模糊迭代,内容取决于fuzzer类型。
- 模糊配置调度(FCS)问题
- 目标:分析当前可用并选择结果最有利信息
- 冲突
- 探索:收集更准确信息以告知未来决策
- 利用:模糊当前会导致更有利配置
- 黑盒
- 使用崩溃与bug数量以及目前为止花费的时间
- 改进:选择更高的成功率配置
- 均匀采样
- 伯努利->未知权值的加权券回收WCCP/UW:假设每个配置都有最终成功概率,并随时间推移学习,后者在概率衰减时非法保持一个上限
- 多武装强盗MAB:精确利用尚未衰减的配置
- 标准化成功概率:更快的fuzz收集bug更多或更快减少其未来成功的上限
- BFF编排从固定数量迭代转换为固定时间量
- 灰盒
- 模糊化配置时覆盖率获得
- 进化算法EA
- 根据适合度变异或重组产生后代
- 执行流边缘,更小更快
- 维护了有前途的种子库,大多数EA使用节点或分支覆盖作为适应度函数,使其成为所有可达路径的多样化子选择,代表对当前PUT的探索
- 细化适应度函数
- 度量复杂分支条件被评估时所满足条件的分数
- 自定义程序分析划分基本块为正常或错误块(EH)
- 正常块权重与包含CFG的随机访问概率反比
- 负权值,大小为基本块与配置的EH块之比,以阻止错误块执行
- 流控器
- 控制流边缘配置内选择最少的以循环探索
- 使用被最少使用的路径
- 配置选择频率低则提升优先级
- 可变次数进行模糊处理
- 小能量规范初始化探索并以指数增长充分开发
- 种子调度与输入源静态分析
- 静态分析二进制文件并优先级排序
- 进化算法EA
- 模糊化配置时覆盖率获得
- 功能类型:是否支持多种子调度
3.InputGen(conf)->tcs
- fuzz配置输入,返回具体测试用例tcs输出:
- 使用conf内种子生成用例
- 使用模型作为参数
- 使用语法作为参数
- 功能类型:是否支持种子突变,是否基于模型生成用例,是否采用符号分析,是否采用污点分析
- 基于生成(模型)的模糊
- 描述PUT希望的输入:输入格式的语法,不大精确的约束
- 预定义系统调用模板
- 指定系统调用希望的参数数量与类型
- 生成随机DOM对象
- 语法模型或网络协议模型设计
- 约束逻辑编程
- 随机组合种子与种子生成片段并突变
- 对XML与正则表达进行同上操作
- 推断模型
- 搜索PUT可用数据预测输入
- 给定种子语法使用数据驱动推断上下文敏感的概率无关语法,再生动一组新种子,侧重语义有效
- 系统API分析日志学习模型
- 分解JS代码并装配约束,静态动态分析计算
- 机器学习基于神经网络
- 编码器模型
- 文件格式隐性模型
- 编码器程序突变:计算编码器动态程序片并运行,改变其行为便可以产生畸形的测试用例
- 基于变异(无模型)的模糊
- 生成满足特定路径测试条件用例成功率不高
- 基于种子输入生成
- 位翻转
- 固定数量
- 突变率随机数量
- 指数比例突变率集合并分配更多迭代
- 算术突变
- 字节序列视为整数运算
- 突变影响尽量小,范围可控
- 块突变
- 随机生成块插入种子随机位置
- 种子中随机删除块
- 随机块替换为随机值
- 随机乱序
- 随机添加块
- 种子内随机取块插入/替换另一种子随机块
- 字典突变
- 修改预定义的高权重特殊值
- 位翻转
- 基于白盒输入生成
- 引导fuzz
- 获取PUT有用信息的程序分析
- 分析指导下生成用例
- 污染分析约束路径关联到对应字节
- 热字节与梯度下降算法引导突变
- 操作数与给定输入关系检测PUT如何利用输入
- PUT突变
- 污点分析识别校验和测试并补丁绕过验证,程序崩溃便生成正确校验和以生成用例崩溃未修改PUT
- NCC非关键检查不修改逻辑下分支转换。停止发现新路径时选择转换NCC并在新PUT重启
- 引导fuzz
4.InputEval(conf,tcs,Obug)->B,execinfos
-
接受模糊配置,测试用例,bug Oracle输入,输出一组bug与运行在exe上的fuzz信息,更新fuzz配置。
-
功能类型:是否使用堆栈散列回溯或代码覆盖来分类崩溃
-
fuzz run:执行tcs上PUT,并用bug Oracle检查是否违反安全策略
- Sanitizers:检测不安全或不想要的程序行为
- ASan(地址杀毒)
- 空间与时间记忆错误
- 一个影子内存(shadow memory):解除引用前快速检查地址有效性
- Msan(内存杀毒)
- 编译检测未初始化内存导致的未定义行为
- 一个影子内存表示可寻址位是否初始化
- UBsan(未定义行为杀毒)
- 未对齐指针,除以0,取消空指针引用,整数溢出
- TSan(线程杀毒)
- 两个线程同时访问一个共享内存位置且至少一次是写操作就会发生数据竞争,可能导致数据损坏
- KASan(未绑定访问)
- KMsan(未初始化读取)
- ASan(地址杀毒)
- 内存与类型安全
- 空间:指针在其对象之外解除引用
- 缓冲区溢出
- 下溢出
- 时间:指针不再有效后被访问
- MEDS:基于ASan在分配对象间创建大量不可访问内存使用的无效指针指向引发更大可能崩溃
- SoftBound/CETS:时空信息关联指针
- 编译时检查坏类型转换或强制转型
- 控制流完整性检查
- 空间:指针在其对象之外解除引用
- 未定义行为
- 依赖于优化设置,体系结构,编译器及其版本
- 输入验证
- XSS:真实浏览器解析用例,提取文档对象模型树并与指示成功的XSS攻击手动指定模式比较
- SQL:数据库代理截获目标web 与数据库通信
- 语义差异:相似程序
- Sanitizers:检测不安全或不想要的程序行为
-
代码动态插桩
- 方便插桩动态链接库,运行时执行
- Pin
- DynamoRIO
- QEMU
- 二进制级别
- 方便插桩动态链接库,运行时执行
-
执行优化:迭代时跳过重复加载部分(同内存模糊)
-
分流
- 重复数据删除
- 堆栈回溯哈希
- 崩溃时根据回溯内容分配哈希
- 需要同时对函数名称地址与名称偏移量或行进行散列
- 主散列只散列函数名分组崩溃,次散列只使用函数名与行号,含无限数量的堆栈帧
- 缺点:造成崩溃的代码并不在崩溃点附近
- 基于覆盖的重复数据删除
- 崩溃覆盖未见过的边缘
- 崩溃没有覆盖早期崩溃中出现的边缘
- 语义感知的重复数据删除
- 分析崩溃转储检查指针,递归确定指令或函数,合并到一个最大帧级别的函数
- 堆栈回溯哈希
- 优先级划分
- 严重性与唯一性进行排序分组,判断为可利用性
- 简化污染分析搭建:可利用>可能可利用>未知>不可利用
- 测试用例迷你化
- 识别违反用例的触发违反必要条件,生成更小的用例(无法利用bug oracle
- 最小化与种子文件不同比特数
- 字节设置为0并缩短用例长度
- 指数递减大小删除相邻行或字节块
- 重复数据删除
5.confupdate(C,conf,execinfos)->C
- fuzz配置,当前配置,运行exe的fuzz信息输入,更新fuzz配置集。
- 黑盒仅使用bug Oracle不修改配置
- 灰白盒合并或删除配置,白盒为新用例产生新配置
- 功能类型:是否更新种子池,是否在线方式学习输入模型,是否会从种子池内移除种子
- 模型推断
- 模糊迭代后更新
- 网络抓包后推断模型并使用学习协议模糊。内部构建状态机映射状态相关令牌,用于稍后更多状态测试用例
- IO行为推断web状态服务机
- IO综合上下文无关语法并语法推断模糊PUT
- EA进化算法(详见上)更新种子池
- 维护minset(详见上)减小配置风险
6.continue(C)->{True,False}i
- fuzz配置输入,输出bool值并指导是否发生下一轮模糊迭代
适合度,选择合适配置变异重组