模糊测试--强制性安全漏洞发掘

文档分享地址链接:链接:http://pan.baidu.com/s/1dDeROHj 密码:o15z


前 言

我知道"人类和鱼类能够和平共处" 。

--George W. Bush, 2000年9月29日

简介

模糊测试的概念至少已经流传了20年,但是直到最近才引起广泛的关注。安全漏洞困扰了许多流行的客户端应用程序,包括Microsoft的Internet Explorer、Word和Excel,它们中的许多漏洞在2006年通过模糊测试技术被发现。模糊测试技术的有效应用产生了许多新的工具和日益广泛的影响。本书是第一部公开发表的关于这一主题的专著,这一尴尬事实同时也预示着未来人们将会对模糊测试有更浓厚的兴趣。

多年来,我们参与了许多有关安全漏洞的研究工作,并且在日常工作中使用了各种不同的模糊测试技术,从不成熟的、凭借个人嗜好的项目到高端的商业产品,都用到过模糊测试。每一位作者都曾参与开发过自用版本的和公开发行版本的模糊器。这本书凝聚了我们以往的实践经验和正在进行的研究项目所花费的心血,我们希望读者能够从中获益。

目标读者

安全性领域的书籍和文章通常由这一领域的研究者所写,以方便该领域的其它研究者参考。我们坚信,只要安全性领域的研究小组把解决安全性问题视为其唯一责任,那么安全性问题的数量和严重程度就会随着时间的推移而继续增长。因此,我们付出巨大的努力以使本书能够服务于更多的读者,既包括模糊测试的新手也包括早已对本领域有所了解的读者。

假设我们只是将开发完成的应用程序提交给一个安全小组,然后让他们在产品发布之前对其进行一个快速审核,相信这样的过程能够产生安全的应用程序显然是不现实的。当开发者或QA组的组员说:"安全根本不是问题--我们有个安全小组关心这件事呢",如此这般,日子就会一天一天的过去。安全性必须被融入软件开发生命周期(SDLC),而不是到了最后才草率处理。

让开发组和QA组把注意力集中在安全性问题上可能是个离谱的要求,特别是对那些以往没有这么做的开发组和QA组来说尤其如此。我们认为模糊测试是一种独一无二的安全漏洞发掘方法学,由于它能够被高度自动化,因此学习和掌握这种方法学的读者可以相当广泛。我们希望经验丰富的安全领域的研究者可从本书获得有价值的东西,同样希望开发人员和QA人员从中获益。模糊测试可以并且应该是任何完整SDLC的一部分,不仅在测试阶段需要考虑,在开发阶段也同样需要考虑。缺陷被发现得越及时,修补缺陷的成本就越低。

预备知识

模糊测试是一个广泛的主题。尽管本书中会介绍一些不专属于模糊测试的背景内容,但是我们仍然假设读者应该拥有这一领域的预备知识。在学习本书之前,读者至少应该对程序设计和计算机网络有一定的基本了解。模糊测试是关于自动化安全测试的,这本书的内容自然要包括如何构造自动工具。我们有目的地选择了多种编程语言来完成这个任务。语言的选择是根据具体任务的,这也说明了模糊测试可以用多种方法实现。当然,没有必要一一罗列所用到的所有编程语言的背景知识,但是介绍一两种语言无疑会帮助读者从这些章节中获益。

本书自始至终都贯穿着对各种安全漏洞的详细描述并讨论如何通过模糊测试来识别这些漏洞。然而,定义或剖析安全漏洞本身的性质并不是本书的目标。一些优秀的书籍是专门讨论这一主题的。如果需要寻找一部关于软件安全漏洞的初级读本,可以参看Greg Hoglund 、Gray McGraw所著的《Exploiting Software》和Jack Koziol、David Litchfiel等的《Shellcoder's Handbook》,它们都是极好的参考读物。

学习方法

如何最好地利用本书,这取决于读者的背景和目的。如果你是一位模糊测试的初学者,我们推荐你按顺序逐章消化理解,因为本书的内容进行了有目的的编排,前面先介绍一些必要的背景信息,随后转入高级主题。反之,如果你已经在使用各种模糊测试工具方面花费了一些时间,那么请不要犹豫,可以直接进入感兴趣的主题,因为本书的不同逻辑章节的划分大致上是相互独立的。

本书的第一部分主要介绍不同的、具体的模糊测试类型,这些模糊测试类型将在随后的章节中逐一讨论。如果读者对模糊测试比较陌生,可以考虑把这一部分作为必读章节。模糊测试可以被作为多种目标下的安全性测试方法学,不过这些目标下的方法学都遵循相同的基本原则。在第一部分,我们试图将模糊测试定义为一种安全漏洞发掘方法学并详细介绍相关的知识,不考虑这种方法学运用于何种目的。

第二部分关注模糊测试的各种相关应用目标。每种目标的介绍跨越了两到三章。最前面的一章介绍每类目标的背景信息,随后的章节集中介绍这些目标下的模糊测试自动化,详细阐述如何针对这种目标构造模糊器。当认为有必要分别介绍Windows平台和Unix平台下的模糊器工具时,这两个主题分别安排在有关自动化的两章。例如,以第11章"文件格式模糊测试"为例,该章详细描述有关模糊文件分析器的内容,第12章"文件格式模糊测试:Unix平台上的自动化测试"则深入介绍基于Unix的文件模糊器的实用程序设计,第13章"文件格式模糊测试:Windows平台上的自动化测试"讲解运行在Windows环境中的文件格式模糊器如何构造。

第三部分讨论模糊测试领域的高级主题。对于那些已经牢固掌握模糊测试背景知识的读者,可以直接跳入第三部分,不过大部分读者很可能需要先了解第一部分和第二部分,然后再学习第三部分。第三部分关注的是近年来浮现出的新技术,这些技术刚刚得到实施,但是未来将成为安全漏洞发掘的高级工具可以利用的模糊测试技术。

最后,在第四部分,我们将总结学习过本书后所得到的收获,然后深入洞察未来的发展方向。尽管模糊测试并不是一个新概念,但是这一领域仍然有足够的发展空间,并且我们希望本书将为未来的研究空间注入一丝灵感。

少许幽默

写书是一件严肃认真的工作,尤其是对诸如模糊测试这样的复杂主题。这就是说,我们希望尽量给随后的读者(实际上这些人可能比写书的人更重要)带来一些乐趣,同时也尽最大的努力让写作的过程更愉快。出于这样的考虑,我们决定在每一章的开头引用美国第43届总统George W. Bush (别名 Dubya)的一段话。不论你的政治倾向或信仰是什么,没人能够否定Bush先生在过去几年中所炮制出的一些引文,这些引文甚至能够写满一年的日历!我们从中挑选了一些最喜欢的引文与读者分享,希望读者和我们得到同样的快乐。读完本书后,读者会发现模糊测试可以被应用于各种不同的目标,显然也可以应用到对英语的模糊测试。

关于封面

有时,安全漏洞经常被称为"鱼"(例如,可以参看DailyDave安全性邮件列表中关于"TheL Word & Fish"的提示线索)。这是一个有用的类比,在讨论安全性和安全漏洞时可以被应用到这个问题的各个方面。这一领域的研究者可以被比作钓鱼者。对应用程序的汇编代码实施逆向工程、逐行分析查找安全漏洞,这样的人可被比作"深海钓鱼者"。同许多其它的审核手段相比,模糊测试充其量只是海面搜索,并且通常只对"容易抓的鱼"更有效。此外,大灰熊是一个著名的"模糊动物",当然也是强大的动物。这些场景组合在一起,就构成了本书的封面,其中有一个模糊的动物,正在捕捉一条鱼,后者代表一个安全漏洞。

配套站点:WWW.FUZZING.ORG

站点fuzzing.org绝对是本书不可分割的一部分,并不仅仅起到补充资源的作用。除了包含本书出版后的勘误表外,该站点还是书中所有源代码和工具的一个中央资源仓库。经过一段时间的努力,我们打算让fuzzing.org从一个以图书为中心的资源站点发成为模糊测试这一学科的资源、工具和信息的有价值的社区。我们欢迎你们提出反馈信息,以帮助我们让该站点成为一个有价值的、开放的知识库。

目录

作者序
译者序
前 言
第一部分
第1章 安全漏洞发掘方法学
1.1 白盒测试
1.1.1 源代码评审
1.1.2 工具和自动化
1.1.3 优点和缺点
1.2 黑盒测试
1.2.1 人工测试
1.2.2 自动测试或模糊测试
1.2.3 优点和缺点
1.3 灰盒测试
1.3.1 二进制审核
1.3.2 自动化的二进制审核
1.3.3 优点和缺点
1.4 小结
1.5 脚注
第2章 什么是模糊测试
2.1 模糊测试的定义
2.2 模糊测试的历史
2.3 模糊测试阶段
2.4 模糊测试的局限性和期望
2.4.1 访问控制缺陷
2.4.2 设计逻辑不良
2.4.3 后门
2.4.4 内存破坏
2.4.5 多阶段安全漏洞
2.5 小结
第3章 模糊测试方法和模糊器类型
3.1 模糊测试方法
3.1.1 预先生成测试用例
3.1.2 随机方法
3.1.3  协议变异人工测试
3.1.4 变异或强制性测试
3.1.5 自动协议生成测试
3.2 模糊器类型
3.2.1 本地模糊器
3.2.2 远程模糊器
3.2.3 内存模糊器
3.2.4 模糊器框架
3.3 小结
第4章 数据表示和分析
4.1 什么是协议
4.2 协议域
4.3 简单文本协议
4.4 二进制协议
4.5 网络协议
4.6 文件格式
4.7 常见的协议元素
4.7.1 名字-值对
4.7.2 块标识符
4.7.3 块长度
4.7.4 校验和
4.8 小结
第5章 有效模糊测试的需求
5.1 可重现性和文档记录
5.2 可重用性
5.3 过程状态和过程深度
5.4 跟踪、代码覆盖和度量
5.5 错误检测
5.6 资源约束
5.7 小结
第二部分
第6章 自动化测试和测试数据生成
6.1 自动化测试的价值
6.2 有用的工具和库
6.2.1ETHEREAL /WIRESHARK
6.2.2LIBDASM 和LIBDISASM
6.2.3LIBNET /LIBNETNT
6.2.4LIBPCAP
6.2.5METRO PACKET LIBRARY
6.2.6PTRACE
6.2.7PYTHON EXTENSIONS
6.3 编程语言的选择
6.4 测试数据生成和模糊启发式
6.4.1 整型值
6.4.2 字符串重复
6.4.3 字段分隔符
6.4.4 格式化字符串
6.4.5 字符翻译
6.4.6 目录遍历
6.5 小结
第7章 环境变量和参数的模糊测试
7.1 本地化模糊测试介绍
7.1.1 命令行参数
7.1.2 环境变量
7.2 本地化模糊测试准则
7.3 寻找目标程序
7.4 本地化模糊测试方法
7.5 枚举环境变量
7.6 自动化的环境变量测试
7.7 检测问题
7.8 小结
第8章 环境变量和参数的模糊测试:自动化
8.1 iFUZZ本地化模糊器的特性
8.2 iFUZZ的开发
8.3 iFUZZ的开发语言
8.4 实例研究
8.5 益处和改进的余地
8.6 小结
第9章 Web应用程序和服务器模糊测试
9.1 什么是Web应用程序模糊测试
9.2 目标应用
9.3 测试方法
9.3.1 建立目标环境
9.3.2 输入
9.4 漏洞
9.5 异常检测
9.6 小结
第10章 Web应用程序和服务器的模糊测试:自动化
10.1 Web应用模糊器
10.2 WebFuzz的特性
10.2.1 请求
10.2.2 模糊变量
10.2.3 响应
10.3 必要的背景知识
10.3.1 识别请求
10.3.2 漏洞检测
10.4 WebFuzz的开发
10.4.1 开发方法
10.4.2 开发语言的选择
10.4.3 设计
10.5 实例研究
10.5.1 目录遍历
10.5.2 溢出
10.5.3 SQL注入
10.5.4 XSS脚本
10.6 益处和改进的余地
10.7 小结
第11章 文件格式模糊测试
11.1 目标应用
11.2 方法
11.2.1 强制性或基于变异的模糊测试
11.2.2 智能强制性或基于生成的模糊测试
11.3 输入
11.4 漏洞
11.4.1 拒绝服务
11.4.2 整数处理问题
11.4.3 简单的栈和堆溢出
11.4.4 逻辑错误
11.4.5 格式化字符串
11.4.6 竞争条件
11.5 漏洞检测
11.6 小结
第12章 文件格式模糊测试:UNIX平台上的自动化测试
12.1 NOTSPIKEFILE和SPIKEFILE
12.2 开发方法
12.2.1 异常检测引擎
12.2.2 异常报告(异常检测)
12.2.3 核心模糊测试引擎
12.3 有意义的代码片段
12.3.1 通常感兴趣的UNIX信号
12.3.2 不太感兴趣的UNIX信号
12.4 僵死进程
12.5 使用的注意事项
12.5.1 ADOBE ACROBAT
12.5.2 REALNETWORKS REALPLAYRE
12.6 实例研究:REALPLAYERREALPIX格式化字符串漏洞
12.7 语言
12.8 小结
第13章 文件格式模糊测试:Windows平台上的自动化测试
13.1 Windows文件格式漏洞
13.2 FileFuzz的特性
13.2.1 创建文件
13.2.2 应用程序执行
13.2.3 异常检测
13.2.4 保存的审核
13.3 必要的背景知识
13.4 FileFuzz的开发
13.4.1 开发方法
13.4.2 开发语言的选择
13.4.3 设计
13.5 实例研究
13.6益处和改进的余地
13.7 小结
第14章 网络协议模糊测试
14.1 什么是网络协议模糊测试
14.2 目标应用
14.2.1APPLEGATE
14.2.2 网络层
14.2.3 传输层
14.2.4 会话层
14.2.5 表示层
14.2.6 应用层
14.3 测试方法
14.3.1强制性或基于变异的模糊测试
14.3.2 智能强制性模糊测试和基于生成的模糊测试
14.3.3 修改的客户端变异模糊测试
14.4 错误检测
14.4.1 人工方法(基于调试器)
14.4.2 自动化方法(基于代理)
14.4.3 其它方法
14.5 小结
第15章 网络协议模糊测试:UNIX平台上的自动化测试
15.1 使用SPIKE进行模糊测试
15.1.1 选择测试目标
15.1.2  协议逆向工程
15.2 SPIKE 101
15.2.1 模糊测试引擎
15.2.2 通用的基于行的TCP模糊器
15.3 基于块的协议建模
15.4 SPIKE的额外特性
15.4.1 特定于协议的模糊器
15.4.2 特定于协议的模糊测试脚本
15.4.3 通用的基于脚本的模糊器
15.5 编写SPIKENMAP模糊器脚本
15.6 小结
第16章 网络协议模糊测试:Windows平台上的自动化测试
16.1 ProtoFuzz的特性
16.1.1 包结构
16.1.2 捕获数据
16.1.3 解析数据
16.1.4 模糊变量
16.1.5 发送数据
16.2 必要的背景知识
16.2.1 错误检测
16.2.2 协议驱动程序
16.3 ProtoFuzz的开发
16.3.1 开发语言的选择
16.3.2 包捕获库
16.3.3 设计
16.4 实例研究
16.5 益处和改进的余地
16.6 小结
第17章 Web浏览器模糊测试
17.1 什么是Web浏览器模糊测试
17.2 目标
17.3 方法
17.3.1 测试方法
17.3.2 输入
17.4 漏洞
17.5 错误检测
17.6 小结
第18章 Web浏览器的模糊测试:自动化
18.1 组件对象模型的背景知识
18.1.1 在Nutshell中的发展历史
18.1.2 对象和接口
18.1.3 ActiveX
18.2 模糊器的开发
18.2.1 枚举可加载的ActiveX控件
18.2.2 属性,方法,参数和类型
18.2.3 模糊测试和监视
18.3 小结
第19章 内存数据的模糊测试
19.1 内存数据模糊测试的概念及实施该测试的原因
19.2 必需的背景知识
19.3 究竟什么是内存数据模糊测试
19.4 目标
19.5 方法:变异循环插入
19.6 方法:快照恢复变异
19.7 测试速度和处理深度
19.8 错误检测
19.9 小结
第20章 内存数据的模糊测试:自动化
20.1 所需要的特性集
20.2 开发语言的选择
20.3 Windows调试API
20.4 将其整合在一起
20.4.1如何实现在特定点将"钩子"植入目标进程的需求
20.4.2如何来处理进程快照和恢复
20.4.3如何来选择植入钩子的点
20.4.4如何对目标内存空间进行定位和变异
20.5你的新的最好的朋友PYDBG
20.6 一个构想的示例
20.7 小结
第三部分
第21章 模糊测试框架
21.1 模糊测试框架的概念
21.2 现有框架
21.2.1 ANTIPARSER
21.2.2 DFUZ
21.2.3 SPIKE
21.2.4 PEACH
21.2.5 通用模糊器(GeneralPurpose Fuzzer)
21.2.6 AUTODAF?
21.3 定制模糊器的实例研究:SHOCKWAVEFLASH
21.3.1 SWF文件的建模
21.3.2 生成有效的数据
21.3.3 对环境进行模糊测试
21.3.4 测试方法
21.4模糊测试框架SULLEY
21.4.1 SULLEY目录结构
21.4.2 数据表示
21.4.3 会话
21.4.4
21.4.5 一个完整的实例分析
21.5 小结
第22章 自动化协议解析
22.1 模糊测试存在的问题是什么
22.2 启发式技术
22.2.1 代理模糊测试
22.2.2 改进的代理模糊测试
22.2.3 反汇编启发式规则
22.3 生物信息学
22.4 遗传算法
22.5 小结
第23章 模糊器跟踪
23.1 我们究竟想要跟踪什么
23.2 二进制代码可视化和基本块
23.2.1 CFG
23.2.2 CFG示例
23.3 构造一个模糊器跟踪器
23.3.1 刻画目标特征
23.3.2 跟踪
23.3.3 交叉引用
23.4 对一个代码覆盖工具的分析
23.4.1 PSTALKER设计概览
23.4.2 数据源
23.4.3 数据探查
23.4.4 数据捕获
23.4.5局限性
23.4.6 数据存储
23.5 实例研究
23.5.1 测试策略
23.5.2 测试方法
23.6 益处和改进的余地
23.7 小结
第24章 智能故障检测
24.1 基本的错误检测方法
24.2 我们所要搜索的内容
24.3 选择模糊值时的注意事项
24.4 自动化的调试器监视
24.4.1 一个基本的调试器监视器
24.4.2 一个更加高级的调试器监视器
24.5
24.6 动态二进制插装
24.7 小结
第四部分
第25章 汲取的教训
25.1 软件开发生命周期
25.1.1 分析
25.1.2 设计
25.1.3 编码
25.1.4 测试
25.1.5 维护
25.1.6 在SDLC中实现模糊测试
25.2 开发者
25.3 QA研究者
25.4 安全问题研究者
25.5 小结
第26章 展望
26.1 商业工具
26.1.1 安全性测试工具beSTORM
26.1.2 BREAKINGPOINT系统BPS-1000
26.1.3 CODENOMICON
26.1.4 GLEG PROTOVER PROFESSIONAL
26.1.5 安全性测试工具MU-4000
26.1.6 SECURITY INNOVATION HOLODECK
26.2 发现漏洞的混合方法
26.3 集成的测试平台
26.4 小结

作者序

安全漏洞是研究安全问题的生命线。无论是执行渗透测试、评价新产品还是审核关键构件的源代码--安全漏洞都驱动着我们的决策、让我们有理由花费时间,并且很多年来一直影响着我们的选择。

源代码审核是一种白盒测试技术,这是一种很长时间以来都流行的软件产品安全漏洞检测方法。这种方法需要审核者了解编程概念和产品功能的每一个细节,深入洞察产品的运行环境。除此之外,源代码审核还有一个显而易见的缺陷--必须首先要获得产品的源代码。

幸运的是,除了白盒技术外,我们还可以使用不需要访问源代码的黑盒技术。模糊测试就是黑盒技术中的一种可选的方法,这种方法对于发掘那些用审核方法无法发现的产品关键安全漏洞方面被证明是成功的。模糊测试是这样的一个过程:向产品有意识地输入无效数据以期望触发错误条件或引起产品的故障。这些错误条件可以指导我们找出那些可挖掘的安全漏洞。

模糊测试没有实际的执行规则。它是一种技术,测试的结果是这种技术的成功性的唯一度量。任意一个给定的产品都可能接受无限的输入。模糊测试技术旨在预测产品中可能存在的编程错误以及什么样的输入可能会触发错误。正因为如此,与其说它是一门科学,不如说它是一种技术。

模糊测试可以简单到就是随意敲打键盘来输入随机数据。我的一个朋友有个3岁的儿子,它就是用这么简单的手段发现了Mac SO X操作系统的屏幕界面锁定功能中的一个漏洞。我的朋友锁定了屏幕界面然后到厨房找酒喝。当他回来的时候,他的儿子已经设法成功地解除了锁定,并且打开了浏览器,所用的方法正是随意敲打键盘。

过去的几年里,我用模糊测试技术和模糊工具在大量的软件中发现了数百个漏洞。2003年12月,我编写了一个简单的程序向一个远程服务发送随机UDP 包流。结果这个程序发现了Microsoft WINS服务器的两个新的漏洞。该程序后来又帮助我在其它产品中找出了少量的缺陷。最后的结果证明,用简单的随机UPD包流能够发现计算机协会的多个产品中的漏洞,包括Norton Ghost管理服务和OS X操作系统的一个公共服务。

模糊器对发现网络协议以及其它的许多产品都有效。在2006年的第一季度,我精心设计了3个不同的浏览器模糊工具,结果发现了多种浏览器中的缺陷。2006年第二季度,我又编写了一个Active X模糊器(AxMan),仅在Microsoft的产品中就发现了超过100个缺陷。这些缺陷许多都是在"Month of Browser Bugs"项目中形成的,结果导致该项目组又进一步开发了"Metasploit"框架中的模块。在最初开发AxMan后的接近一年的时间里,我还利用模糊测试发现了AxMan本身所包含的一些漏洞。模糊器真是一个能够不断赐予我们新礼物的工具。

本书是一部真正让我们有理由相信模糊测试是一门技术的专著。书中所介绍的内容涵盖了对新产品执行模糊测试以及创建有效的模糊工具所需要的全部知识。有效模糊测试的关键在于明确对什么样的产品使用什么样的测试数据,以及需要什么工具来操纵、监控和管理模糊测试过程。本书的作者是模糊测试技术的先锋,在阐明模糊测试的复杂过程方面作出了卓越贡献。

第1章 安全漏洞发掘方法学

"Internet高速公路将会越来越少么?"

--George W. Bush, Concord, N. H., 2004年8月5日

如果询问任何一位有成就的安全领域的研究者,问他如何发现漏洞,很可能会得到一大堆答案。为什么?因为可用于安全性测试的方法太多,每种方法都有自己的优点和缺点。没有一种方法是绝对正确的,也没有一种方法能够揭示一个给定目标下所有可能的漏洞。在较高的层次上,有三种主要的方法用来发现安全漏洞:白盒测试、黑盒测试和灰盒测试。这些方法之间的差别是由测试者可得到的资源决定的。白盒测试是一个极端,它需要对所有资源进行充分地访问。这包括访问源代码、设计规约,甚至有可能还要访问程序员本人。黑盒测试是另一个极端,它几乎不需要知道软件的内部细节,很大程度上带有盲目测试的味道。远程Web应用的Pen测试是黑盒测试的一个好例子,它不需要访问程序源码。位于两个极端中间的是灰盒测试,它的定义因询问的人的不同而不同。就我们的应用目的而言,灰盒测试需要访问编译后得到的二进制代码,或许还要访问一些基本的文档。

本章将考察漏洞发掘的各种不同的高层和低层方法,起点是白盒测试,你以前可能听说过这种方法也被称为玻璃、透明或半透明测试。之后我们再定义黑盒测试和灰盒测试,模糊测试可能就属于后两者。我们将阐述每种方法的利弊,这些方法的利弊将成为本书后面介绍模糊测试时所需要的背景知识。模糊测试只是漏洞发掘的一种方法,了解其它可选的实用方法也是相当重要的。

1.1 白盒测试

作为一种测试方法学,模糊测试(fuzzing)主要属于黑盒测试和灰盒测试领域。尽管如此,我们还是要考察另一种软件开发人员可选的漏洞发掘方法,即白盒测试(whit box testing)。

1.1.1 源代码评审

源代码评审既可以人工完成也可以在自动化工具的辅助下完成。假设计算机程序通常包含数万到数十万行代码,那么单纯的人工评审一般情况下是不可行的。自动化工具是一个宝贵的资源,它能够减少长时间对着代码行阅读而带来的繁重任务,但是自动化工具只能识别出可能的漏洞或可疑的代码片段。检测出的问题是否有效,仍然需要人工分析。

让源代码分析工具产生有用的结果,必须要克服许多障碍。对这些复杂问题的剖析已经超出了本书的范围,让我们考虑如下的一个C程序代码段,这段程序只是简单地将文本"test"拷贝到一个10字节的字符数组。

代码

下面,考虑同样的代码示例,该段代码示例经过了修改,允许用户的输入被拷贝到字符数组。

代码

上面的两段代码都使用了strcpy()例程将数据拷贝到一个基于栈的缓冲区。在C/C++编程中,一般不推荐使用strcpy()库函数,因为它缺少对目的缓冲区的边界检查。因此,如果程序员不小心自己编写代码而增加了边界检查代码,便可能会发生缓冲区溢出,从而将数据置于目的容器的边界之外。第一个代码例子不会发生缓冲区溢出,因为字符串"test"(包括后跟的null终止标记)的长度总是固定值5,因此小于目标缓冲区的10字节。第二个例子中的场景中可能会也可能不会发生缓冲区溢出,这要取决于用户在命令行中输入的参数数据长度。这里的关键问题是用户是否能够控制输入给有可能发生漏洞的函数的参数长度。

一次彻底的代码评审可能会将strcpy()所在的一行标记为"可能产生漏洞"。尽管如此,还是需要跟踪该函数的实际输入参数,以理解可被人为利用的执行条件是否真正存在。

这并不是说源代码评审对安全领域的研究者不是一个有价值的技术工具。源代码评审应该在可获得代码的任何时候进行。然而,是否要进行源代码评审取决于你的角色和观点,通常人们不需要在如此细节的层次上访问被测对象。

人们经常不正确地假设白盒测试是比黑盒测试更有效的方法。对软件的何种审视能够比访问源代码更好、更精确呢?不过无论如何都不要忘记,对源代码来说,你看到的东西并不一定是实际执行的东西。软件构建过程在从源代码到汇编代码的转换中可能会发生很大的改变。因为这个原因和其它的原因,不能说一种测试方法就一定比另一种测试方法更好。它们仅仅是不同的方法,通常会揭示不同类型的漏洞。为了达到充分覆盖的目的,有必要结合多种测试方法。

微软源代码泄露

为了说明源代码评审未必一定优于黑盒测试,让我们考虑和分析发生在2004年2月的一个事件。在没有事先告戒的情况下,有传闻说Microsoft Windows NT 4.0和Windows 2000操作系统提供的部分源代码文档被传播到互联网上。Microsoft后来确认这些文档是真实的操作系统代码。许多公司当时感到极度紧张,因为这一泄露事件将导致上述两个操作系统产生大量的安全漏洞。但是他们所担心的事情没有发生。事实上,直到今天,只有很少的安全漏洞被归因于当时的源代码泄露。其中的一个安全漏洞是CVE-2004-0566,其具体细节是当操作系统在处理位图文件时会产生一个整数值溢出1。有趣的是,Microsoft否认这个安全漏洞的发现,声称其所属的软件公司在一次内部审核中早已经发现了这个问题2。为什么我们没有发现大量的安全漏洞?不是说源代码评审可以发现一切吗?事实是源代码评审尽管是应用程序或操作系统安全性审核的一个重要部分,但是由于代码的规模和复杂性,源代码评审难以充分地进行。此外,反汇编分析技术也可以发现大部分源代码中的问题。例如,让我们考察一下TinyKRNL3和ReactOS4项目,它们的目的都是为应用软件提供与Microsoft Windows内核及其操作系统的兼容性。这些项目的开发者并没有访问Microsoft的内核源代码,但是仍然能够在一定程度上创建项目,提供一个兼容Windows的环境。在审核Windows操作系统时,不大可能会直接访问Windows源码,相反,上述项目的源代码可以被作为解释Windows汇编代码的指南。

1.1.2 工具和自动化

源代码分析工具一般可以分为三类--编译时检查器、源代码浏览器或自动源代码审核工具。编译时检查器在源代码编译时查找漏洞。此类工具通常与编译器集成在一起,但是主要查找与安全性有关的问题而不是应用程序的功能问题。Microsoft Visual C++的/analyze编译选项是一个编译时检查器的例子。Microsoft还提供了PREfast for Drivers6,它能够检测针对驱动程序开发的不同类型的漏洞,而编译器可能检测不到这些漏洞。

源代码浏览器是专门设计用于辅助人工检查和评审源代码的软件工具。这类工具允许评审者执行代码的高级搜索、枚举代码以及在代码的交叉引用位置之间导航。例如,一位评审者可能会使用这样的工具来定位所有strcpy()调用的位置,力图识别可能的缓冲区溢出漏洞。Cscope7和Linux Cross-Reference8是当前流行的源代码浏览器。

源代码自动审核工具用于扫描源代码以及自动识别可能的关注区域。同大多数安全性工具一样,源代码自动审核工具既有商业工具也有开源的自由软件解决方案。除此之外,此类工具倾向于关注具体的编程语言,因此如果目标软件使用了不同的编程语言的话,可能需要多种源代码自动审核工具。在商业软件工具方面,有来自Fortify6、Coverity10、KlockWork11、Gramma Tech12和其它供应商的产品。一些流行的自由软件工具列于表1.1,其中包括它们审核的语言以及支持的平台。

表1.1 源代码审核的自由软件工具

名称

语言

平台

下载

RATS(Rough Auditing Tool for Security)

C、C++、Perl、PHP、Python

UNIX、Win32

http://www.fortifysoftware.com/security-resource.jsp

  本表中其余内容同原文,全部不需要翻译。

 

 

 

 

 

 

 

 

 

 

 

 

重要的是要记住,没有任何自动化工具能够完全替代有经验的安全研究员的技能。它们只不过是工具,能够将繁重的分析源代码的任务进行流水化和自动完成,有助于节省时间和解决一些问题。这些工具生成的报告仍然必须由具备经验的分析员评审,以识别出伪问题,也需要有开发人的员评审以实际实施问题的修复。例如,下面的一段代码输出是由Rough Auditing Tool for Security(RATS)自动生成的,被分析的代码是本章前面所举的存在安全漏洞隐患的程序示例。分析结果可以表明这样一个事实:该段程序使用了固定长度缓冲区并且对strcpy()的使用可能是不安全的。但是,分析结果没有肯定地说确实存在一个漏洞,只是提醒用户注意存在可能不安全的代码区域,但是该区域是否真的不安全,则需要由他(她)决定。

代码

1.1.3 优点和缺点

如同前面提到的,发现安全漏洞不存在唯一正确的方法学。那么如何选择一种合适的方法学呢?当然,有时的决策是由我们自己确定的。例如,如果我们不能访问目标软件的源代码,那么就不可能进行白盒测试。这对大部分安全研究者和用户都是一样的,尤其是那些购买了Windows环境的商业软件的人来说。那么白盒测试有什么优点呢?

覆盖能力:由于白盒测试能够获得所有源代码,因此代码评审允许完全的覆盖。所有可能的代码路径都可以被审核,以发现可能的漏洞。当然,这个过程也可能导致误报的出现,因为一些代码路径可能在代码执行的时候不可达。

代码评审并不总是可行的。即使能够执行代码评审,也应该和其它漏洞发掘方法学结合使用。源代码分析有下面一些缺点:

复杂性:源代码分析工具是不完善的,有可能报告出伪问题。因此,这类工具产生的错误报告并不是游戏的结束。这些结果必须经过有经验的程序员的评审,以识别出那些能够合理代表安全性漏洞的问题。重要的软件项目典型地要包含数十万行代码,工具所产生的报告可能相当冗长并且需要大量的时间阅读。

可用性:由于白盒测试能够获得所有源代码,因此代码评审允许完全的覆盖。尽管许多UNIX项目是开放源代码的,并且允许源代码被评审,但是这种情况在Win32环境下比较罕见,对于商业软件这种情况则通常不存在。如果不能访问源代码,那么白盒测试则根本不能作为一个测试选项。

1.2 黑盒测试

黑盒测试意味着只能了解外部观察到的东西。作为终端用户,可以控制输入,从一个黑盒子的一端提供输入,从盒子的另一端观察输出结果,但是并不知道被控目标的内部工作细节。在远程访问Web应用和Web服务时这种情形最常见。我们可以采用超文本置标语言(HTML)或可扩展置标语言(XML)请求的形式产生输入并观察生成的Web页面或返回值,但是并不知道幕后发生了什么。

让我们考虑另一个例子。假设你在某个时候购买了一个应用软件,例如Microsoft Office,通常你只能得到一个经过编译后得到的二进制程序,并不能得到用于构建这个应用程序的源代码。在这种情况下,你的观点将决定测试方法的盒子的颜色。如果你不打算采用逆向工程技术,那么黑盒测试是有效的。反之,就应该采用灰盒测试方法,灰盒测试将在下一节讨论。

1.2.1 人工测试

仍然以Web应用为例,人工测试可能要包括使用一个标准的Web浏览器来浏览一个Web站点的层次结构,同时冗长乏味地在所观察到的感兴趣的区域输入可能导致危险的各种输入数据。在审核的最初阶段,这个技术可能只是被零星地应用,例如,通过给各种各样的参数增加引用,以期望揭示出SQL注入漏洞。

在没有自动化工具辅助的情况下通过人工测试应用程序来查找漏洞,这通常并不是一个可行的方法(除非公司雇佣了一大批实习生)。这种方法能够带来益处的场景是它被用于扫除(sweeping)多个应用程序中的类似的一个漏洞时。当多个程序员在不同的项目中犯了相同的错误时,就需要利用这种扫除技术。例如,在一个专门的LDAP(Lightweight Directory Access Protocol)服务器中发现了一个缓冲区溢出,并且通过测试其它LDAP服务器发现同样的安全漏洞。如果我们假设程序之间经常共享代码或者同一个程序员参加过多个开发项目,那么这种情况就并非不经常出现。

扫除技术

CreateProcess()是Microsoft Windows 应用程序编程接口(API)提供的一个函数。正如它的名字所暗示的那样,CreateProcess()将发起运行一个新进程及其主线程13。该函数的原型是如下定义的:

代码

如果lpApplicationName参数被赋予一个NULL值,那么被发起执行的进程将是lpCommandLine参数中第一个空白符号分隔之后的值,这是人们都知道并且记录在文档中的该函数的行为。考虑下面的例子中对CreateProcess()函数的调用:

代码

在上情形下,CreateProcess()函数将试图反复发起每一个空格符之后的值所代表的进程:

代码

这种尝试会一直进行,直到发现了一个可执行文件的名字或者所有的选择都已经被穷尽。因此,如果一个可执行文件program.exe位于C:\路径下,带有类似上述结构中对CreateProcess()不安全调用的应用程序将会执行该program.exe程序。这为攻击者提供了一个便利的机会,他们会试图让实际要执行的程序不可达并执行另外一个程序。

2005年10月发布的一个安全性咨询报告中列举了好几个流行的应用程序中采用了不安全的CareateProcess()调用。这些问题的发现得益于一次成功的但是却非常简单的扫除技术实践。如果你想要利用该扫除技术发现相同的安全漏洞,那么可以拷贝和重新命名一个简单的应用程序,例如notepad.exe,然后将它置于c:\路径下。然后正常使用计算机。如果先前拷贝的这个应用程序突然在预期之外被执行了,那么就很可能发现了一个因不安全调用CreateProcess()而引起的漏洞。

1.2.2 自动测试或模糊测试

模糊测试在很大程度上是一种强制性的技术,因此它缺乏"优雅性",它的目标是简单加有效。模糊测试这个术语将在第2章"什么是模糊测试"中详细定义和解释。简单地说,模糊测试包括把能够想到的所有东西都抛给被测目标然后监视结果。大部分软件都应该被设计成带有具体输入并且足够健壮,在接收到异常输入的时候能够完美地恢复。考虑如下所示的一个简单的Web表单。

合理的假设是Name域应该接受一个字符串值,Age域应该接受一个整数值。如果用户偶然改变了两个域的实际输入范围并且在Age域输入了一个字符串后会发生什么呢?字符串值会被自动转换为基于ASCII码的整数值吗?是否会显示一条错误报告消息?应用程序会崩溃吗?模糊测试试图通过一个自动化的过程来回答这个问题。研究者不需要知道应用程序的内部工作细节,因此执行的是黑盒测试。要做的就是站在后面向目标投掷石头,等待玻璃被打破的声音。就这个意义而言,模糊测试可被归结为黑盒测试。然而,在本书中,我们将说明,为什么强制性的模糊测试可以变得更像"外科手术",从而让石头每次的飞行路线更直接并且更真实。因此,模糊测试也可以应用在灰盒测试中。

Microsoft使用模糊测试吗?

答案是肯定的。Microsoft2005年3月发布的可信计算安全开发生命周期(SDL)文档15清楚地阐明了Microsoft考虑将模糊测试作为一种在软件发布之前寻找安全漏洞的关键技术。SDL是一个关于在软件开发生命周期中嵌入安全性的倡议,认为安全性是参与开发过程的每一个人的职责。模糊测试在SDL中被提及为是一类安全性测试工具,应该在软件实现阶段加以利用。实际上,这个文档中说到:"尽管对模糊测试的强调只是最近才被加入到SDL,但是到目前所取得的效果却非常鼓舞人心"。

1.2.3 优点和缺点

黑盒测试尽管并不总是最好的方法,但却总是一个可选的方法。黑盒测试的优点包括以下几个方面:

可用性:黑盒测试总是可以应用的,甚至在源代码可用的情况下应用黑盒测试也是有益处的。

可重现性:由于黑盒测试的实施不需要事先对目标做出假设,以文件传输协议(FTP)服务器为例,黑盒测试可以很容易地被定制成能够测试任何其它FTP服务器

简单性:一些测试方法,例如代码逆向工程(RCE)等,需要专业的技巧,而黑盒测试只是测试的一个最基本层次,它能够在不十分了解应用程序内部细节的情况下执行。尽管如此,事实上,尽管通过简单地使用测试工具就能够发现诸如拒绝服务攻击这样的安全漏洞,但是判断一次简单的应用程序崩溃是否能够被关联到某些让人更感兴趣的地方(例如代码的执行),则通常需要对软件的深入理解。

尽管黑盒测试容易使用,但是也有一些缺点。黑盒测试的缺点包括:

覆盖能力:如何确定测试何时结束以及测试的有效性程度,这是黑盒测试面临的最大挑战之一。这个问题将在本书的第23章"模糊器跟踪"中详细讨论。

理解力:黑盒测试最适合那些安全漏洞由一个单独的输入向量所引起的场景。然而,复杂的攻击需要多种攻击向量,其中的一些攻击将目标应用程序置入一种脆弱状态,其它攻击向量进一步触发漏洞。此类攻击需要深刻理解应用程序的底层逻辑,并且典型地需要通过人工代码评审和RCE才能发现漏洞。

1.3 灰盒测试

灰盒测试的定义是在白盒测试和黑盒分析之间浮动的,我们给它下的定义是首先它包括了黑盒测试审核,此外还包括通过逆向工程(RE)获得的结果,逆向工程也被称为逆向代码工程(RCE)。源代码是宝贵的资源,它应该容易被阅读并且支持人们理解软件具体功能的操作细节。此外,它还隐含地提示了具体的功能所预期需要的输入,以及该具体功能的预期输出。即使缺少源代码这样的重要资源,也不意味着我们会失去一切。分析编译后得到的汇编指令能够帮助阐明类似的故事,但是要付出更多的努力。在汇编代码层次上进行安全评估而不是在源代码层次上进行安全评估,这种安全评估典型地被称作二进制审核(binary auditing)。

1.3.1 二进制审核

RCE通常与二进制审核这个词汇被当作同义词而使用,但就我们的目的而言,我们将RCE作为一类子方法,以使它区别于完全自动化的方法。RCE的最终目标是决定一个编译后得到的二进制应用程序的底层功能。尽管将一个二进制文件完全转换回它的源代码形式是不可能的,但是有可能对汇编指令序列应用逆向工程,从而得到一种位于源代码和构成二进制代码的机器码之间的一种表示形式。通常,这种表示是汇编语言和应用程序代码流的图形表示之间的一种中间形式。

一旦二进制文件被转换成人可读的形式,这样的代码就可以被评审以查找其中可能包含漏洞之处,这在很大程度上与源代码的评审是类似的。就源代码评审而言,找到可能存在漏洞的代码片段并不是游戏的结束。还必须决定一个终端用户是否能够影响脆弱的代码片段。遵循这样的逻辑,二进制审核也被人们称为是一种"从里向外"的技术:研究者首先识出反汇编结果中令其感兴趣的可能存在的漏洞,然后反向追溯到源代码中以确定漏洞是否可以被别人所利用。

逆向工程(reverseengineering)是一种外科手术式的技术,它利用诸如反汇编器、反编译器或调试器这样的工具。反汇编器将难以被辨认的机器码解析为某种更适合人理解的汇编码。有各种自由的反汇编器软件可供使用,但是出于重要的RCE工作的要求,我们很可能需要花费一些投资来购买DataRescue的Interactive Disassembler(IDA) Pro16。IDA是一个运行在Windows、UNIX和MacOS平台上的商用反汇编器,能够解析很多不同机器体系架构下的二进制代码。


同反汇编器类似,反编译器可对二进制代码执行静态分析并将其转换为人可读的源代码形式。反编译器试图产生更高级的语言组件,例如条件和循环结构,而不是直接将机器码转变为汇编指令。反编译器不可能完全恢复产生最初的源代码,因为包含在源代码中的一些信息,例如注释、变量名、函数名甚至基本结构在编译之后都不再被保存。对于将源代码直接翻译成机器码的编译型语言(例如C和C++),它们的反编译器在本质上存在一些限制。对于那些将源代码编译为某种中间形式的字节码的语言,如C#,由于其字节码中包含更多的信息,因此对这些语言的反编译通常要成功的多。

与反汇编器和反编译器不同,调试器通过打开或附加到一个目标程序并采用动态分析技术来监控目标程序的执行。调试器能够显示应用程序正在运行时CPU寄存器的内容和内存状态。Win32平台下的流行调试器包括OllyDbg18,其运行时的一个屏幕快照。此外还有Microsoft WinDbg(也被人称做"wind bag")19。WinDbg是Windows软件调试工具包20中的一部分,可从Microsoft的网站上免费下载。OllyDbg是一个由Oleh Yuschuk开发的调试器,用户友好性稍好于WinDbg。这两个调试器都允许用户创建自定制的扩展功能组件,有许多第三方插件可用于扩展OllyDbg的功能21。UNIX环境下也有各种各样的调试器,GNU Project Debugger22(GDB)是最流行的也是最容易被移植的调试器。GDB是一个命令行调试器,许多UNIX/Linux产品中都包含这个调试器。

1.3.2 自动化的二进制审核

最近出现了少量的工具,这些工具试图对RCE的过程实现自动化,以识别二进制应用程序中潜在的安全漏洞。这些工具既有商业软件,也有自由软件,它们或者是IDA Pro的插件或者是单独开发的应用程序。表1.2列举了它们中的一小部分。

表1.2 自动化二进制审核工具

名称

开发商

许可类型

说明

LogiScan

LogicLibrary

商业软件

LogicLibrary在2004年9月收购了BugScan,此后对后者的二进制审核工具的品牌重新进行了命名,并将其包含到Logidex SDA管理方案中。

BugScam

Halvar Flake

自由软件

BugScam是一个IDA Pro的IDC脚本集合,它枚举了二进制文件中的函数调用,以识别出对各种不同库函数可能的不安全调用。该应用程序主要是在BugScan的基础上采用“哄骗”的手段发现安全漏洞。

Inspector

HB Gary

商业软件

Inspector是一个RCE管理系统,它统一了来自各种不同RCE工具的输出,例如IDA Pro和OllyDbg。

SecurityReview

Veracode

商业软件

VeraCode的产品将一个二进制分析套件直接集成进开发环境,类似于源代码分析套件的功能,如Coverity。在二进制代码层次上进行分析,使得VeraCode能够检测出一些问题,这些问题与“所看到的并不一定是所执行的”有关。

BinAudit

SABRE Security

商业软件

BinAudit在本书出版之前还没有正式发布。尽管如此,根据SABRE Securite的Web站点上的介绍,它是一个IDA Pro的插件,被设计用来识别一些安全漏洞,例如数组访问越界、内存破坏、内存泄露等。

1.3.3 优点和缺点

如前所述,灰盒测试在某种程度上是一种混合的解决方案,它包括传统的黑盒测试,外加通过RCE获得的洞察结果。同其它方法一样,灰盒测试也有自己的优点和缺点。灰盒测试的优点有:

可用性:除了远程Web服务和应用程序之外,应用程序的二进制版本一般总是可用的。

覆盖:从灰盒测试分析获得的信息可被用于帮助和改进采用黑盒测试的模糊测试技术。

灰盒测试同时也有以下的缺点:

复杂性:RCE是一个专业的技能,因此它需要的资源有可能不可用,从而导致这种方法无法使用。

1.4 小结

在最高的层次上,安全漏洞发掘方法可被分为白盒、黑盒和灰盒测试方法三大类。测试者可以获得的资源决定了这三种方法的差别。白盒测试需要使用所有可用的资源,包括源代码,而黑盒测试只访问软件的输入和观察到的输出结果。介于两者之间的是灰盒测试,它在黑盒测试的基础上通过对可用的二进制文件的逆向工程而获得了额外的分析信息。

白盒测试包括各种不同的源代码分析方法。可以人工完成也可以通过利用自动化工具完成,这些自动化工具包括编译时检查器、源代码浏览器或自动源代码审核工具。

在执行黑盒测试时,源代码是不可用的。仅仅通过黑盒测试来执行模糊测试,这被认为带有盲目性。尽管能够生成输入,也可以监控输入所对应的响应,但是没有办法获知和分析目标软件的内部状态。基于灰盒测试的模糊测试类似于基于黑盒测试的灰盒测试,但是增加了由RCE获得的数据。模糊测试企图反复为应用程序提供非预期的输入,同时监控可能产生的异常结果。本书的剩余部分将将关注模糊测试,将其作为一种发现安全漏洞的方法。

第2章 什么是模糊测试

"他们不正确地低估我。"

--George W. Bush,Bentonville,Ark,2000年11月6日

在主流的词典中,模糊测试(fuzzing)这个术语并不存在,同时有许多别名,对许多读者来说它很可能是一个完全陌生的名词。模糊测试是一个宽泛的研究领域,同时也是一种令人激动的软件安全性分析方法。贯穿本书,我们将深入讨论不同的模糊测试方法的细节和目标。不过在详细介绍模糊测试之前,我们先要给模糊测试下一个定义,考察模糊测试的历史,阐述模糊测试的各个阶段,最后再指出模糊测试的局限性。

2.1 模糊测试的定义

如果在你的词典中查找"fuzzing"这个词,恐怕很可能找不到任何能够为安全性领域的研究者所用的定义。Wisconsin-Madison大学的一个研究项目中首次公开提及了这个术语,现在这个术语已经被广为采纳,成为一种完整的软件测试方法学的代名词。在学术界,与模糊测试最接近的专业术语可能是边界值分析(boundary value analysis, BVA)1,在边界值分析中,需要考察给定输入的合法值的边界,然后以边界内和边界外的合法和非法值来创建测试。

BVA可以帮助确保异常处理机制能够过滤掉非预期的值,同时允许最大范围的可接受的输入值。模糊测试类似于VBA,但是在执行模糊测试时,我们并不仅仅唯一关注边界值,而是需要额外关注任何可能引发未定义或不安全行为的输入。

出于本书的写作目的,我们将模糊测试定义成是一种通过提供非预期的输入并监视异常结果来发现软件故障的方法。模糊测试典型地是一个自动的或半自动的过程,这个过程包括反复操纵目标软件并为其提供处理数据。当然,这是一个相当泛化的定义,但是这个定义抓住了模糊测试的最基本概念。所有的模糊器都可分被分为两大类:基于变异的模糊器,这种模糊器对已有数据样本应用变异技术以创建测试用例;基于生成的模糊器,这种模糊器通过对目标协议或文件格式建模的方法从头开始产生测试用例。这两大类模糊器分别有各自的优点和缺点。在本书的第3章"模糊测试方法和模糊器类型"中,我们将对模糊测试方法学做更进一步的分类并讨论每一类方法的好处和易犯的错误。

如果你是一位模糊测试的新手,那么可以将模糊测试类比为如何闯进一幢房子。假设你在当前的职业生涯中遭受了挫折,进而转入从事违法犯罪活动。为了破门进入某人的家,假设采用纯白盒的方法,那么在实施破门之前应该能够得到对所有关于这个家的充分信息。这些信息可能要包括房屋设计图、各种锁的制造商列表、房屋建筑材料的详情,等等。尽管这种方法有独一无二的优点,但是也并非万无一失而没有短处。在这种方法下,执行破门的过程中,你要做的不是在实际执行时去检查房屋的设计而是要对房屋的设计执行静态分析。让我们打个比方,例如,事先研究表明起居室的侧面窗户代表了一个弱点,可以砸破这面窗户然后破门而入,如果是这样的话,那你肯定不希望到时候有一个拿着鸟枪的愤怒房主站在里面正在等着你。另一方面,如果采用一种纯的黑盒测试方法来完成破门的话,那么应该在黑夜的掩盖下逐步靠近这个房子,安静地尝试所有的门和窗户是否有漏洞,向房子内窥视以决定哪里可能是最好的突破口。最后,如果选择采用模糊测试来完成突破的话,便可以不必研究设计图更不用人工测试各种锁。要做的就是让找枪和破门而入的过程自动化--这就是强制性的漏洞发掘!

2.2 模糊测试的历史

我们所知道的对模糊测试的最早的引用可以追溯到1989年。Barton Miller教授(被很多人认为是模糊测试之"父")和他的高级操作系统课开发和使用了一个原始的模糊器,用来测试UNIX应用程序的健壮性2。测试的关注点并不是评价系统的安全性,而是验证全部代码的质量和可靠性。尽管在该项研究的过程中提及到了安全性考虑的问题,但是在对setuid应用程序的测试中并没有专门强调这一点。1995年的研究发现模糊测试让setuid应用程序的可靠性得到了改进,但是该应用程序仍然被认为"具有较高的失效概率"。

Miller的小组所用的模糊测试方法是非常粗糙的。如果应用程序崩溃或者挂起,那么就认为测试失败,否则就认为测试通过。所用的方法是简单地向目标应用程序抛出随机字符串,这是一种纯黑盒的方法。尽管这种方法看上去可能过于简单,但是不要忘了,在那个时候甚至还没有人听说过模糊测试的概念。

大约在1999年,Oulu大学开始了它们的PROTOS测试集的开发工作。通过首先分析协议规约然后产生违背规约或者很有可能让协议实现体无法正确处理的报文,各种各样的测试集先后被开发出来。一开始时,产生这样的测试集需要耗费相当大的努力,但是一旦产生之后,这些测试集就可以用来测试多个供应商的产品。这个例子属于白盒和黑盒混合的测试方法,标志着模糊测试发展历程中的一个重要里程碑,由于后来证明大量的故障是用这样的方法发现的。

2002年Microsoft为PROTOS3提供了资金支持,2003年PROTOS组的成员发起了Codenomicon,这是一个专门致力于设计和产生商业用模糊测试集的公司。该公司今天的产品仍然基于最初的Oulu测试集,不过又包括了一些图形用户界面、用户支持以及通过健康特征检测而实现的故障检测能力,此外还增加了其它一些特征4。关于Codenomicon和其它商业模糊测试解决方案的更多信息,请阅读本书的第26章"展望"。

随着PROTOS在2002年逐渐成熟,DaveAitel发布了一个开放源代码的模糊器,名为SPIKE5,采用了GNU的许可(GPL)。Aitel的模糊器实现了一种基于块的方法6,目的是为了测试基于网络的应用程序。SPIKE采用了一种比Miller的模糊器更高级的方法,最显著的特征是包括了描述可变长数据块的能力。此外,SPIKE不仅能够生成随机数据,而且包括了一个数据值的库,库中的数据值有较大可能性让编写较差的应用程序产生故障。SPIKE还包括一组预定义的函数,这些函数能够产生常见的协议和数据格式,其中包括Sun RPC和Microsoft RPC。它们是两个常见的通信协议,过去发现的许多安全漏洞都起源于它们。作为第一个允许用户无痛创建自定制模糊器的公开框架,SPIKE的发布标志着一个重要的里程碑。这个框架在本书中将被反复提及多次,包括本书的第21章"模糊测试框架"。
大致在SPIKE被发布的几乎同一时间,Aitel还发布了一个名为sharefuzz的本地UNIX模糊器。与Miller的模糊器不同,Sharefuzz以环境变量为目标而不是以命令行参数为模糊测试目标。Sharefuzz还采用了一个有用的技术来促进模糊测试过程。它使用共享库来挂起函数调用,这些函数调返回环境变量值,它们是一些长字符串,为的是发现缓冲区溢出漏洞。

在SPIKE发布之后的大多数模糊测试技术是以不同类型的具体模糊测试工具出现的。Michal Zalewski7(又名lacamtuf)于2004年发布的mangleme8关注Web浏览器的模糊测试,mangleme是一个公共网关接口(CGI)脚本,可用来持续产生畸形的、在一个目标Web浏览器中被反复刷新的HTML文件。随后很快又出现了几个以Web浏览器为测试目标的模糊器。H.D. Moore和Aviv Raff开发了Hamachi9,用来对动态HTML脚本进行模糊测试以查找其实现缺陷,他们两个后来与Matt Murphy和Thierry Zoller合作又推出了CSSDIE10,这是一个对重叠样式表进行解析的模糊器。

文件模糊测试的兴起始于2004年。那一年Microsoft发布了MS04-28安全公告,详细描述了负责处理JPEG文件的引擎中的一个缓冲区溢出漏洞。尽管这并不是第一个被发现的文件格式方面的安全漏洞,但是它还是吸引了人们的注意力,因为许多流行的Microsoft应用程序都共享这段有漏洞的代码。文件格式安全漏洞对网络的保护者也提出了挑战。尽管相当数量的类似漏洞在随后几年相继出现,但是如果我们仅仅阻止有可能存在漏洞的文件格式进入公司的网络,那样显然是不切实际的。图像和媒体文件构成了Internet流量中相当大的一部分。如果Web离开了图像的话,那将是多么令人振奋的新闻?此外,这些漏洞中的大多数在不久后就会折磨Microsoft的Office文档文件--这些文件类型对任何企业都至关重要。文件格式安全漏洞最后成为基于变异的模糊测试要发现的主要侯选漏洞,因为漏洞的样品容易被获得,并且文件格式可以在监视目标应用程序的故障的同时被快速地连续变异。我们在Black Hat USA Briefings in 200512上做了陈述并发布了一系列同时基于变异和生成的工具,用于对文件格式进行模糊测试,包括FileFuzz、SPIKEfile和notSPIKEfile13。

2005年,一个名为Mu Security的公司开始开发一种硬件模糊装置,其目的是让网络中传输的协议数据发生变异14。这个商业产品供应商的出现,恰好吻合了当前人们关注模糊测试的潮流。我们开始看到日益增加的可用的商业模糊测试解决方案,同时伴随着模糊测试技术的自由软件解决方案的出现。另外,大量的开发者和安全领域的研究人员开始对模糊测试感兴趣,一个明显的证据是他们创建了模糊测试邮件列表15,该邮件列表由Gadi Evron维护。只有时间才能说明未来将会有什么样的创新在等待着我们。

ActiveX模糊测试在2006年开始流行,当年David Zimmer发布了COMRaider,H.D.Moore发布了AxMan16。这两个工具关注那些在访问Microsoft Internet Exploer浏览器时能够被Web应用程序实例化的ActiveX控件。此类应用程序中的可被人远程利用的漏洞代表了一种高风险,因为此类应用程序用户群体十分庞大。就象人们最后发现的那样,ActiveX控件是一种优秀的模糊测试目标,由于其中包括了接口、函数原型和实际的控制逻辑,从而允许对其实施自动化的智能测试。有关ActiveX和浏览器的模糊测试将在第17章"Web浏览器模糊测试"和第18章"Web浏览器模糊测试:自动化"中做更进一步的研究。

尽管模糊测试在历史上还有许多其它里程碑和标志节点,不过我们认为前面介绍的内容已经足够绘制一幅综合的历史发展路线图。模糊测试的简要发展历史的图形表示参见图2.1。

译文:

Oulu University begins work on PROTOS Test Suites:Oulu大学开始PROTOS测试集的工作

SPIKE demonstrated at BH USA by Dave Aitel:DaveAitel现在BH USA展示SPIKE

FileFuzz, SPIKEfile, notSPIKEfile released at BH USA:FileFuzz,SPIKEfile, notSPIKEfile在BH USA发布

Commercial Fuzzers(i.e. Codenomicon, Mu Security, etc.) introduced:商业模糊器(例如Codenomicon,Mu Security等)被引入市场

COMRider(David Zimmer) and AxMan(H.D. Moore) ActiveX fuzzersreleased:COMRider(David Zimmer)和AxMan(H.D. Moore)等ActiveX模糊器发布

Professor Barton Miller uses Fuzzing to test Robustness of UNIXApps at UW-Madison 1989:Barton Miller教授在UW-Madison使用模糊测试方法来测试UNIX应用程序的健壮性

Oulu Univer releases PROTOS SNMP Test Suite:Oulu大学发布PROTOSSN MP测试集

Mangleme released by Michael Zalewski(aka lcamtuf):MichaelZalewski(又名lcamtuf)发布Mangleme

Hamachi and CSSDIE released by HD Moore, Aviv Raff, et. al:HD Moore、AvivRaff等发布Hamachi和CSSDIE

注:上述文字中未包括年号。

尽管到目前为止取得了一定的进展,但模糊测试这项技术仍然是襁褓中的婴儿。到现在为止大部分工具仍然是相对较小的项目,由几个人组成的小组甚至是一名程序员维护。仅仅在最近几年,一些早期的研究成果才开始步入商业发展阶段。尽管这些发展让人们对模糊测试已经成为独立的研究领域增加了信心,但是也预示着模糊测试未来将会有许多革新和发展,随着未来的几年人们对这一领域投入的日益增加,模糊测试还会达到新的里程碑。

在下一节,我们将考察执行一次完整的模糊测试审核所要经历的几个不同阶段。

2.3 模糊测试阶段

模糊测试方法的选择依赖不同的因素,可能有很大的变化。没有一种绝对正确的模糊测试方法。模糊测试方法的选择完全取决于目标应用程序、研究者的技能,以及需要测试的数据所采用的格式。然而,无论要对什么进行测试,也不论确定选择了哪种方法,模糊测试总要经历几个基本的阶段。

1.识别目标。在没有考虑清楚目标应用程序的情况下,不可能对模糊测试工具或技术作出选择。如果是在安全审核的过程中对内部开发的应用程序进行模糊测试,目标应用程序的选择应该小心谨慎。相反,如果是对第三方应用程序进行安全漏洞的发掘研究,这种选择就有一定的灵活性。在识别目标应用程序的时候,需要考察开发商过去被发现的安全漏洞的相关历史。这可以通过浏览安全漏洞收集站点来完成,例如SecurityFocus17或Secunia18。一个开发商如果在过去的安全漏洞历史记录方面表现不佳,那么很可能有比较差的编码习惯,最终将会导致更多的安全漏洞被进一步发现。选择了目标应用程序之后,还必须选择应用程序中具体的目标文件或库。如果确要选择目标文件或库,应该选择那些被多个应用程序共享的库,因为这些库的用户群体较大,出现安全漏洞的风险也相应较高。

2.识别输入。几乎所有可被人利用的漏洞都是因为应用程序接受了用户的输入并且在处理输入数据时没有首先清除非法数据或执行确认例程。枚举输入向量对模糊测试的成功至关重要。未能定位可能的输入源或预期的输入值对模糊测试将产生严重的限制。在查找输入向量同时应该运用水平思考。尽管有一些输入向量是很明显的,但是其它一些则难以捉摸。最后要说的是,任何从客户端发往目标应用程序的输入都应该被认为是输入向量。这些输入包括消息头、文件名、环境变量、注册键值等等。所有这些都应该被认为是输入向量,因此都应该是可能的模糊测试变量。

3.生成模糊测试数据。一旦识别出输入向量,模糊测试就必须被生成。如何使用预先确定的值、如何变异已有的数据或动态生成数据,这些决策将取决于目标应用程序及其数据格式。不管选择了哪种方法,这个过程中都应该引入自动化。

4.执行模糊测试数据。这一步与前一步并行进行,在这一步,模糊测试成为一个动词。执行过程可能包括发送数据包给目标应用程序、打开一个文件或发起一个目标进程。同样,这个过程中的自动化也是至关重要的。没有自动化,我们便无法执行真正的模糊测试。

5.监视异常。在模糊测试过程中,一个至关紧要但却被经常忽视的步骤是对故障或异常的监视过程。举一个例子,如果我们没有办法准确指出是哪一个数据包引起崩溃的话,那么向目标Web服务器发送10000个模糊测试数据包并最终导致服务器崩溃便是一次无用的努力。监视可以采用多种形式并且应该不依赖目标应用程序和所选择的模糊测试类型。

6.确定可利用性。一旦故障被识别,因审核的目标不同,还可能需要确定所发现的bug是否可被进一步利用。这典型地是一个人工过程,需要具备安全领域的专业知识。因此执行这一步的人可能不是最初执行模糊测试的人。

不管采用什么类型的模糊测试,所有上述阶段都应该被考虑到,只有确定可利用性这一步有可能例外。各个阶段的顺序和侧重点可依据研究者的目标而改变。尽管模糊测试非常强大,但绝不意味着它对任何被测软件都将发现百分之百的错误。在下一节,我们将罗列一些bug的类型,这些类型的bug很有可能在"雷达的监视下飞行"。

2.4 模糊测试的局限性和期望

模糊测试本质上有助于发现目标软件中一些类型的弱点。同时它对发现某些类型的漏洞也存在一定的局限性。在这一节里,我们将给出几类典型的漏洞,这些漏洞无法被模糊器所发现。

2.4.1 访问控制缺陷

一些应用程序需要分层的权限来支持多个帐户或用户级别。例如,考虑一个在线日历系统,它可能通过一个Web前端被访问。该应用程序可能指定一名管理员来控制谁被允许登录到系统。可能还存在另一个专门的用户组,该组能够创建日历。所有其它用户可能只有读的权限。最基本的访问控制形式是确保常规用户不能执行管理员的任务。

模糊器可能会发现日历软件系统中的一个故障,该故障允许一个攻击者获得对系统的完全控制。在较低的层次上,软件故障通常是跨越不同目标的,因此相同的逻辑可被用于检测不同的目标。

在测试过程中模糊器可能会成功地访问只有管理员才能访问的功能,但是它却只拥有常规用户的访问身份。这种访问控制机制被绕过的事实并不大可能会被检测出来。为什么?因为我们必须考虑到模糊器并不理解应用程序的逻辑这样一个事实。模糊器并没有办法知道管理员的访问区域是否能被常规用户所访问。在模糊器中实现应用程序逻辑感知能力并不是不可行,但是要付出极大的代价,并且在测试其它应用程序时如果不做大的修改便很可能无法被重用。

2.4.2 设计逻辑不良

模糊器不是用来识别设计逻辑不良的最好的工具。例如,考虑在Veritas Backup Exec中发现的漏洞,该漏洞允许攻击者远程访问Windows Server的注册表,包括创建、修改和删除注册表键值19。这样的访问几乎不会立即让系统的安全发生妥协。这个漏洞的发现归功于一个侦听服务,该侦听服务在一个传输控制协议(TCP)端点上实现了一个远程过程调用(RPC)接口,该接口不需要认证但是仍然接受操作注册表的命令。这并不是一个认证问题,因为根据软件设计规约,该过程根本不需要认证。但是,这却是一个不恰当的设计决策,该决策很有可能源自一个因为某个假设而引起的失效,该假设认为攻击者愿意花时间破解用来描述目标例程的Microsoft Interface Description Language (IDL)并随后编写了一个自定制的实用程序用来与服务器通信。

尽管模糊器可能会发现通过RPC连接而提供的数据确实存在解析问题,从而导致某种形式的底层故障,但是却不能确定所展示出的功能是否不安全。这个观点在本书第18章"Web浏览器模糊测试:自动化"讨论ActiveX的模糊测试时将起到重要作用,因为许多控件只为攻击者提供操作方法,例如创建和执行文件的方法。检测这些设计上的bug还需要专门的考虑。

2.4.3 后门

对于一个模糊器来说,如果受到限制或不知道关于目标应用程序的结构信息,它的后门(backdoor)看上去和其它目标应用程序的逻辑没有什么区别,例如一个登录屏幕界面。它们都是简单的输入变量,接收身份认证秘钥。不仅如此,除非给予模糊器组的信息来让它认可成功的登录,它将没有办法通过使用一个硬编码的密码来识别一个成功的等录企图。例如,在对密码域进行模糊测试时,模糊器典型地可以发现输入的异常,但是却不能发现随机猜测的硬编码的密码。

2.4.4 内存破坏

内存破坏问题通常会导致目标进程崩溃。这种类型的问题可以根据与拒绝服务类似的症状而加以识别。然而,一些内存破坏问题被目标应用程序很好地屏蔽掉了,因此简单的模糊器永远不会发现它们。例如,一个格式字符串漏洞可能不会被发现,除非为目标应用程序附加一个调试器。格式字符串漏洞通常归结于机器代码层的违例指令。例如,在一个X86的Linux平台的机器中,如果格式字符串中带有一个%n的攻击字符特征,那么经常会在执行下列指令时发生故障:

代码

如果模糊器向带有某种格式触发字符串(例如这里或那里的%n)的进程发送随机数据,exx寄存器将在许多情况下不包含一个可写的地址,而是包含一些来自栈的垃圾字。在这种情况下,该指令将会触发一个段违例信号SIGSEGV。如果应用程序有一个SIGSEGV信号处理器,那么它将简单地杀死这个进程然后派生一个新的进程。对于某些应用程序,信号处理器甚至会企图允许过程继续执行而不需要重新启动。对于一个格式字符串漏洞,这实际上是可能的(尽管我们极力反对这么做),因在SIGSEGV信号没有发生前内存实际上并没有遭到破坏。因此,如果我们的模糊器没有检测到它又如何呢?这个进程不会崩溃,相反它会恰当通过信号处理器来处理信号,因此这里并不存在安全问题,对吗?但是,不要忘记,对格式字符串的成功利用将造成这样一个结果:在内存中写入数据并随后使用这些受到控制的内存区域获得对进程的控制权。信号处理器不能阻挡格式字符串被他人精确地利用,同样精确地利用也可能不会引起任何故障。

因对,在模糊测试目标的监视范围不同的情况下,模糊器也可能捕捉不到这种情况。

2.4.5 多阶段安全漏洞

可利用性并非总意味着简单地对一个弱点的攻击。复杂的攻击通常涉及连续利用若干个漏洞来让机器妥协。也许一个初始的缺陷让机器接受了未被授权的访问,随后的缺陷让这种未授权被进一步放大。模糊测试对识别单个的缺陷是有用的,但是通常对那些由小的漏洞链而组成的大缺陷价却并没有多少价值,对那些让人不感兴趣的多重输入向量而引起的攻击也是如此。

2.5 小结

尽管模糊测试的发展已经有一段时间,但是这种方法学它仍然未被安全研究领域之外的人们所广泛理解。出于此种原因,你很可能会发现,给模糊测试所下的定义的版本数与你询问的人数一样多。我们就本书的目的而定义了模糊测试的概念,介绍了其简要发展历史,为模糊测试定下了未来的期望,下面是该深入研究模糊测试的类型和具体方法的时候了。

第3章 模糊测试方法和模糊器类型

"太多的好文档从企业中流失,太多的妇产医生不能与全国的妇女进行爱的实践。"

--George W. Bush, Poplar Bluff, Mo,2004年9月6日

模糊测试定义了一种发现漏洞的综合方法。然而,在模糊测试这把大伞下,有各种不同的具体方法来实现模糊测试方法学。本章开始对模糊测试进行详细剖析,考察各种不同的具体的模糊测试方法。我们还将考察不同类型的模糊测试如何实现这些方法以及如何利用这些方法来发现具体目标中的漏洞。本书的第二部分将对每种模糊器类型做进一步的专门探讨。

3.1 模糊测试方法

前一章曾提到,所有模糊器都属于两大类中的一类。基于变异的模糊器(mutation-based fuzzer)对已有数据样本应用变异技术来创建测试用例,基于生成的模糊器(generation-basedfuzzer)通过对目标协议或文件格式建模来从头创建测试用例。在这一节,我们将对这两类模糊器进一步划分子类。当然,子类的划分并没有公认的结果,但是鉴于本书是第一部关于模糊测试的专著,我们把模糊器分为下面介绍的5类。

3.1.1 预先生成测试用例

如前一章所述,这是PROTOS框架采用的方法。测试用例的开发始于对一个专门规约的研究,其目的是为了理解所有被支持的数据结构和每种数据结构可接受的值范围。硬编码的数据包或文件随后被生成,以测试边界条件或迫使规约发生违例。这些测试用例可用于检验目标系统实现规约的精确程度。创建这些测试用例需要事先完成大量的工作,但是其优点是在测试相同协议的多个实现或文件格式时用例能够被一致地重用。

预先生成测试用例的一个缺点是这种方式下的模糊测试存在固有的局限性。由于没有引入随机机制,一旦测试用例表中的用例被用完,模糊测试只能结束。

3.1.2 随机方法

这种方法是迄今为止最低效的,但是它可被用于快速发现目标软件是否是彻头彻尾的恐怖代码。随机方法只是简单地大量产生伪随机数据给目标软件,希望得到最好的或最坏的结果,这取决于测试者所持的观点是什么。下面是这种方法的一个简单例子,它总是让人感到幽默,不过却很有效:

代码

上面的一行命令从Linuxurandom设备读取随机数据,然后用netcat命令将这些数据传输到一个目标地址及其端口。While循环确保这一过程持续进行,直至用户中断该过程。

信不信由你,影响任务关键软件的漏洞在过去就是使用这样的技术发现的。这是多么令人窘迫!真正的随机模糊测试最困难之处在于如何逆向搜索服务器崩溃时软件发生异常的起因。这种搜索可能是一个令人痛苦的过程,需要回溯跟踪可能引起服务器崩溃的大约500000个随机字节。你可能希望利用探测器捕获通信数据来帮助减少跟踪范围。此外,在调试器和反汇编器上还将花费大量的时间。使用这个技术拆分栈需要痛苦的调试过程,尤其是调用栈可能遭到破坏。与某企业应用系统和网络监视实用程序相连的调试器的一个屏幕快照,被测系统刚刚在一次模糊测试攻击中发生崩溃。你能决定故障的原因吗?很可能无法确定原因,除非你是一个占星术士。精确地指出问题的起因还需要更多的研究工作。这里需要注意的是,一些数据还可能事先经过了混淆以毁灭罪证。

3.1.3 协议变异人工测试

按理说,人工协议测试应该比/dev/urandom方法在技术复杂性封面更加初级。人工协议测试中不需要自动化的模糊器。事实上,研究者本人就是模糊器。在加载了目标应用程序后,研究者仅仅通过输入不恰当的数据来试图让服务器崩溃或使其产生非预期的行为。这就是他的模糊测试方法,但是在过去毫无疑问是有效的。这种方法有它的优点,优点当然是分析员本人在审核过程中可以发挥他的历史经验和"天生直觉"。此类模糊测试方法最常用来测试Web应用程序

3.1.4 变异或强制性测试

这里所说的强制性,是指模糊器从一个有效的协议或数据格式样本开始,持续不断地打乱数据包或文件中的每一个字节、字、双字或字符串。这是一个相当早期的方法,因为这种方法几乎不需要事先对被测件进行任何研究,实现一个基本的强制性模糊器也相对简单直接。模糊器要做的全部事情就是修改数据然后传递它。当然,在故障检测过程中可以引入许多"铃声和口哨声",还可以引入登录功能等作为辅助工具,这种工具一般可以在很短的时间内完成开发。

此种方法带有几分低效性,因为许多CUP周期被浪费在数据生成上,并且这些数据并不能立刻得到解释。然而,这些问题所带来的挑战在一定程度上可以得到缓解,因为测试数据生成和发送的全过程都可以被自动化完成。使用强制性方法的代码覆盖依赖于已知的经过测试的良好的数据包或文件。大部分协议规约或文件定义都比较复杂,即使对它进行表面的测试覆盖也需要相当数量的样本。强制性文件格式模糊器的例子包括FileFuzz和notSPIKEfile,分别对应Windows和Linux平台。

3.1.5 自动协议生成测试

自动协议生成测试是一种更高级的强制性测试方法。在这种方法中,需要进行前期的研究,首先要理解和解释协议规约或文件定义。然而,与前一种方法不同,这种方法并不创建硬编码的测试用例,而是创建一个描述协议规约如何工作的文法。为了达到这个目的,需要识别数据包或文件中的静态部分和动态部分,后者代表了可被模糊的变量。之后,模糊器动态解析这些模板,生成模糊测试数据,然后向被测目标发送模糊后产生的包或文件。这种方法的成功依赖研究者的能力,研究者需要指出规约中最容易导致目标软件在解析时发生故障的位置。此类模糊器的例子包括SPIKE和SPIKEfile。这两个模糊器都以SPIKE脚本来描述目标协议或文件格式,并使用一个模糊引擎来创建模糊后的数据。这种方法的不足之处是需要耗费一定的时间来产生文法或数据格式的定义。

3.2 模糊器类型

前面定义了几种不同的模糊测试方法或手段,下面让我们看一些具体的模糊器类型。模糊器的不同类型取决于被测的目标程序。不同的目标需要采用不同的模糊测试方法并且每种模糊测试方法都有其适合的模糊器。

在这一节,我们将简要考察一些不同的模糊器类型。本书的后面会逐一介绍每一类模糊器的细节,包括它们的背景知识、自动化技术和开发样例。

3.2.1 本地模糊器

在UNIX世界中,setuid应用程序允许一个普通用户临时获得更高的权限。这一功能使该应用程序成为一个显然的模糊测试目标,因为setuid应用程序中的任何漏洞都将使一个用户永久提升权限并执行他或他自己选择的程序。对setuid进行模糊测试有两个不同的被测目标。第一个目标是命令行参数的模糊测试,关注的是在命令行中如何向setuid传递变形的参数。第二个目标也是传递变形的参数,不过采用不同的方式。第二种目标下的变形参数是通过UNIX shell环境传递的。下面让我们分别了解命令行和环境变量的模糊测试。

命令行模糊器

当一个应用程序开始运行时,通常要处理用户传递给它的命令行参数。来看下面的例子,这个例子在第1章"安全漏洞发掘方法学"中也曾使用过,该例子说明了命令行参数栈溢出的最简单形式。

代码

如果这个程序真的是setuid,可以用本地模糊器对其测试。这里的问题是在源代码不可获得的情况下安全研究者如何才能在setuid应用程序中找到更难以发现的bug。如果你听到解决这个问题最好的办法是模糊测试,应该别感到吃惊。

下面是一些有用的命令行模糊器:

warlock的clfuzz1。一个命令行模糊器,可用于测试应用程序中的格式字符串和缓冲区溢出漏洞。

Adam Greene的iFUZZ1。一个命令行模糊器,可用于测试应用程序中的格式字符串和缓冲区溢出漏洞。包括对若干个不同参数的模糊选项,并可根据应用程序的"使用"帮助信息智能地产生模糊测试数据从而对应用程序执行测试。

环境变量模糊器

另一类本地模糊器涉及到环境变量向量,它也以setuid应用程序为目标。考虑下面的应用程序,该程序不安全地使用了来自用户环境中的值:

代码

有好几种有效的方式可以对一个应用程序中的环境变量的使用进行模糊测试,但是并没有多少自动化的支持工具存在,尽管这个过程很简单。许多安全研究者试图自行拼凑脚本来完成这任务,这也是公开发布的支持工具较少的部分原因。充其量,本地环境变量模糊器并不是复杂到难以开发,因此许多这样的工具被开发并公开发行。下面是一些有用的环境变量模糊器:

Dave Aitel的Sharefuzz3。第一个公开发布的可用的环境变量模糊器,它中途拦截对getenv函数的调用消息并且返回恶意数据。

Adam Greene的iFUZZ4。尽管它主要是一个命令行模糊器,iFUZZ包还包括了基本的环境变量模糊能力。iFUZZ使用与Sharefuzz相同的方法,并且比后者在使用过程中更容易被定制。

在本书的第7章"环境变量和参数模糊测试"和第8章"环境变量和参数模糊测试:自动化"中,我们将详细讨论本地模糊测试和setuid的有关概念。

文件格式模糊器

大量的应用程序必须在某个时间点处理文件的输入输出,不论是客户端还是服务器端都是如此。例如,反病毒网关通常需要解析压缩了的文件以诊断其中包含的内容。另一个例子是Office Productivity软件套件,它需要打开文档。当这些类型的应用程解析到恶意编撰的文件时,就有理由怀疑被分析的软件存在安全漏洞。

文件格式模糊测试的目的即在于此。文件格式模糊器动态创建变形了的文件,然后利用目标应用程序发起和处理这些文件。尽管文件模糊测试的具体方法严格讲与其它类型的模糊器存在不同,但是它们的总体思路是一致的。一些有用的文件格式模糊测试工具包括:

Michael Sutton的FileFuzz5。一个基于Windows图形用户界面(GUI)的文件格式模糊测试工具。

Adam Greene的notSPIKEfile和SPIKEfile6。基于UNIX的文件格式模糊测试工具,其中的一个基于SPIKE,另一个则不基于SPIKE。

Cody Pierce的PAIMEIfilefuzz5。类似于FileFuzz,它是另一个基于Windows GUI的文件格式模糊测试工具,构建于PaiMei逆向工程框架的基础之上,本书的后面将对PaiMei做更详细的介绍。

文件格式模糊测试的内容将在本书的第11章"文件格式模糊测试"、第12章"文件格式模糊测试:UNIX平台上的自动化测试"和第13章"文件格式模糊测试:Windows平台上的自动化测试"等章节详细介绍。

3.2.2 远程模糊器

远程模糊器以侦听一个网络接口的软件为测试目标。基于网络的应用程序很可能是模糊测试最重要的测试目标。随着Ineternet的发展,几乎所有的公司现在都会公开发布一些可被访问的服务器来提供Web页、e-mail、域名系统(DNS)解析服务,等等。这类软件中的漏洞为攻击者提供了访问敏感数据或对更可信的服务器发起攻击的机会。

网络协议模糊器

网络协议模糊器可以被分为两个主要的类:以简单协议为测试目标的模糊器和以复杂协议为测试目标的模糊器。下面将分别介绍这两类模糊器的主要特征。

简单协议

简单协议通常只有简单的认证或根本没有认证。它们通常基于可打印的ASCII文本,而不是二进制数据。简单协议不包括长度或校验和字段。此外,典型的简单协议应用程序并不包含很多的状态。

简单协议的一个例子是FTP。在FTP协议中,所有的受控信道中的通信多是以普通ASCII码文本的形式传输的。就认证而言,FTP只需要简单文本形式的用户名和密码认证。

复杂协议

复杂协议典型地要包含二进制数据,偶尔包含人可读的ASCII码字符串。认证可能需要通过加密或某中形式的混淆来实现,其中可能包括多个复杂的状态。

复杂协议的一个例子是Microsoft远程调用(MSRPC)协议。MSRPC是一个二进制协议,在数据传输之前需要执行若干个步骤才能建立起一个通信信道。协议需要长度描述域和分解域。总体上讲,它不是一个很容易实现模糊测试的协议。下面是几个网络协议模糊测试的有用工具:

Aitel的SPIKE8。SPIKE是第一个公开发布的模糊测试框架。它包括好几种流行协议的预生成的模糊测试脚本,同时也可以被作为一个API来使用。

Michael Eddington的Peach9。一个用Python编写的跨平台的模糊测试框架。它非常灵活并且可用来对几乎所有网络目标应用程序进行模糊测试。

网络协议模糊测试的内容将在本书的第14章"网络协议模糊测试"、第12章"网络协议模糊测试:UNIX平台上的自动化测试"和第13章"网络协议模糊测试:Windows平台上的自动化测试"等章节详细介绍。

Web应用模糊器

Web应用程序已经成为访问后端服务的一种流行的便捷方式,这些后端服务包括电子邮件和计帐系统等。随着Web2.0的到来(先不论它究竟是什么),诸如文字处理等传统的桌面应用程序也开始向Web上转移10。

在对Web应用程序进行模糊测试时,研究者主要考察Web应用所特有的漏洞,例如SQL注入、交叉站点脚本(XSS)等。这使得模糊器必须具备通过超文本传输协议(HTTP)进行通信以及通过捕获网络响应来分析安全漏洞的能力。下面是一些有用的Web应用模糊测试工具:

OWASP的WebScarab11。一个开放源代码的Web应用程序审核套件,具有模糊测试能力。

SPI Dynamics的SPI Fuzzer12。一个商业的HTTP和Web应用程序模糊器,它包含在WebInspect漏洞扫描器之内。

Codenomicon的Codenomicon HTTP Test Tools13。一个商业用HTTP测试套件。

Web应用模糊器在本书的第9章"Web应用和服务器模糊测试"和第10章"Web应用和服务器模糊测试:Windows平台上的自动化测试"等章节详细介绍。

Web浏览器模糊器

尽管Web浏览器模糊器在技术上只是一类特殊的文件格式模糊器,但是由于基于Web的应用程序的普及性,我们认为应该将它专门列为一类。Web浏览器模糊器通常利用HTML的功能来使模糊测试的过程自动化。例如,lcamtuf的mangleme实用程序是最先公开可用的浏览器模糊测试支持工具之一,它利用 标签以自动的方式连续加载测试用例。这种Web浏览器具有的独特特征使得客户端的模糊器能够完全被自动化,并且不需要对应用程序进行任何复杂的包装。而对其它客户端的浏览器,这种包装则是典型的需求。

Web浏览器模糊测试不仅仅限于对HTML文本进行解析,还有许多其它的测试目标。例如,See-Ess-Ess-Die模糊测试工具能够对重叠样式表进行解析,另一个模糊测试工具COM Raider则关注那些能够被加载进Microsoft Internet Explore的组件对象模型(COM)对象。其它可模糊测试的元素包括图形和服务器应答头域。下面是几个有用的Web浏览器模糊测试工具:

lcamtuf的mangleme14。第一个公开可用的HTML模糊器。它是一个CGI脚本,可反复发送打乱了的HTML数据到一个浏览器。

H.D. Moore和Aviv Raff的DOM-Hanoi15。一个DHTML模糊器。

H.D. Moore和Aviv Raff的Hamachi16。另一个DHTML模糊器。

H.D. Moore、Aviv Raff、Matt Murphy和Thierry Zoller的CSSDIE17。一个CSS模糊器。

David Zimmer的COM Raider17。一个易于使用、GUI驱动的COM对象(ActiveX控件)模糊器。

COM和ActiveX对象的模糊测试将在第17章"Web浏览器模糊测试"和第18章"Web浏览器模糊测试:自动化"中详细介绍。CSS和图形文件的模糊测试并没有在Web浏览器的模糊测试部分专门介绍,但是其中给出的基本概念对CSS和图形文件模糊测试同样适用。

3.2.3 内存模糊器

有些时候在测试过程中存在一些困难阻碍了模糊测试的快速有效进行。这时候内存模糊测试就可能会派上用场。内存模糊测试的基本思想是简单的,但是开发一个合适的实现工具却不是一件容易的事。有一种实现方法涉及到冻结进程并记录它的快照,以及快速将故障数据注入进程的输入解析例程。在每个测试用例被执行之后,以前所记录的快照被存储下来并且新的数据被注入。上述过程反复进行直至所有测试用例都被穷尽。和其它模糊测试方法一样,内存模糊测试也有优点和缺点。它的优点包括:

速度。不仅没有网络带宽需求,并且任何位于接收离线数据包和实际解析数据包之间的不重要的代码都可以被删去,从而使得测试性能有所提高。

捷径。有时协议程序使用了定制的加密或压缩算法或者其中充满了校验和确认代码。内存模糊器能够记录下解压、解密、或校验和确认之后某一时间点上的内存快照,而不是花费大量的时间来测试所有的过程,减少了测试的工作量。

内部模糊测试的缺点是:

假像。由于原始数据被注入到进程地址空间,不仅没有网络带宽需求,并且任何位于接收离线数据包和实际解析数据包之间的不重要的代码都可以被删去,从而使得测试性能有所改进。

重现。尽管引起一个异常可能会展示出一个可被人利用的漏洞,但是研究者仍然需要在进程之外远程创建异常。这可能会花费不少时间。

复杂。正如我们在第19章"内存模糊测试"和第20章"内存模糊测试:自动化"中将要看到的,这种模糊测试方法的实现极为复杂。

3.2.4 模糊器框架

模糊测试框架可用于对不同目标的模糊测试。模糊测试框架其实就是一个通用的模糊器或模糊器库,它简化了许多不同类型的测试目标的数据表示。到目前为止前面提到的许多模糊器实际上是模糊测试框架,包括SPIKE和Peach。

典型地,模糊测试框架包括一个库用来产生模糊测试字符串或者通常能够导致解析例程出现问题的数据值。此外,典型的模糊测试框架还包括一组例程以简化网络和磁盘输入输出。模糊测试框架还应该包括某种脚本类语言,以便在创建具体的模糊器时使用。模糊测试框架是流行的,但它绝对不是完全充分的解决方案。使用或设计一个模糊测试框架也有它的优点或缺点。优点包括:

可重用性。如果创建了一个真正通用的模糊测试框架,这个框架便可以在测试不同目标软件时被反复使用多次。

群体介入。大项目的群体参与比小项目的群体参与更容易,并且大项目可以被反复使用多次,小项目只有相对狭小的使用空间,例如专门协议的模糊器。

模糊测试框架有下列缺点:

局限性。即使对于仔细设计的框架,研究者也很可能遇到不适合该框架的测试目标。这通常令人沮丧。

复杂性。获得一个通用的模糊测试接口是好事,但是却要花费时间来学习如何驱动这样的框架。有时掉转车轮重新开辟模糊测试接口可能在时间上会更高效,但这样做可能会破坏原来的框架。

开发时间。对于一个具体的协议来说,设计一个完全通用和可重用的模糊测试框架要比单独为其开发一个程序所需要的工作量大得多。这些工作量的差异几乎总是会在开发时间上有所体现。

一些研究者强烈地认为框架对任何重要的模糊器来说是最好的方法,而另一些人则认为并非如此,每一个模糊器都应该在具体环境下创建。这两种思路的实验验证以及取舍显然要取决读者自己。

3.3 小结

在执行模糊测试的时候,应该考虑到应用本章所讨论的各种方法的价值。通常情况下,组合各种不同的方法可能会收到最好的效果,因为一种方法可能是另一种方法的补充。

一个模糊器可能要关注不同的目标软件,正因为这样,可能需要多种不同类型的模糊器。在开始学习消化各种各样的模糊器类型背后隐藏的基本概念的同时,应该设想一下如何去实现它们。这将有利于在阅读本后面要介绍的有关实现的内容时给出中肯的评价。

第4章 数据表示和分析

"我的工作是,诸如,考虑能立即考虑到的之外的事情"

--George W. Bush, Poplar Bluff, Mo,2004年9月6日

计算机在内部和外部通信的各个方面都要使用协议。这些协议构成了数据传输和数据处理的基础结构。如果要进行成功的模糊测试,必须首先对目标软件使用的协议有所理解。有了理解之后才能够针对协议中最有可能引起异常条件的部分进行测试。此外,为了访问到协议中的脆弱部分,在进行模糊测试之前我们还需要提供合法的协议数据。了解开放的和专有的协议必然会使得安全漏洞的发掘更为高效。

4.1 什么是协议

协议对通信来说是必须的。有时候一些协议被定义成标准,其它一些时候协议只是人们遵循的事实上的标准。口语就是一种事实上的标准的典型例子。尽管没有正式的文档定义说话的规则,但是人们一般都能够听懂别人在说什么,听者是沉默的,但是它可以找到机会给出回答。同样,你不会没有理由地简单地走到一个人面前滔滔不绝讲出你的名字、住址甚至其它至关重要的个人信息(更不会说出你的生日和身份证号)。相反,个人信息需要以元数据(metadata)开始。例如"我的名字是John Simith,我住在Cambers大街345号"在这些信息中,说者和听者都应该遵循一种有意义的数据通信方式。在计算领域,协议绝对至关重要。计算机本身并没有智能。更不能奢望它还有直觉。因此,严格地定义协议对通信和数据处理是必不可少的。

百科全书对协议的定义是:"在两个计算端点之间建立或控制连接、通信或数据传输的约定或标准"1。这些端点可以是两个单独的计算机,也可以是一个计算机中两个单独的点。例如,从内存读取数据时,计算机必须访问硬盘上的存储区,通过数据总线将数据转移到内存,然后在将其传送至处理器。在每个端点,数据必须具有某中形式,以便发送方和接收方都能够恰当地处理数据。在最底层,数据不就是一堆比特位的集合。只有在某个上下文中理解,这些比特集合才具有含义。如果发送方和接收方端点不能就上下文达成一致,那么被传输的数据就是无意义的。

机器间的通信也依赖协议。这种通信协议的一个常见的例子就是Internet。Internet是许许多多个分层的协议集合体。位于美国的一台家用桌面计算机可以通过连续的协议对数据进行包装,然后将数据通过Internet传送到中国的另一台桌面计算机。只有理解了相同的协议,接收方计算机才能解包数据并解释它。在上面的特定场景下,中国政府很可能中途拦截了被传输的数据,按照自己的理解解除数据的包装并对其进行了解释。这是因为参与通信的两个机器都能理解这种协议,从而使得双方的通信成为可能。在两个端点之间存在数量众多的路由器,这些路由器也必须理解协议的某些方面以便合理地对数据执行路由转发。

尽管协议起到了一个非常通用的作用,但是却可以采取各种不同的具体形式。一些协议是人可读的,并且以简单文本形式表达。其它一些协议则是以二进制形式传输数据,这些协议的数据格式不适合人们直接理解。诸如表示GIF图象或Microsoft Excel表单的文件格式便是二进制协议的典型例子。

4.2 协议域

在设计一个协议的时候要作出许多决策,其中最关键的决策之一是协议中的数据如何被分隔为不同的成分。发送方和接收方的机器都需要知道如何解释数据中的个体元素,这些个体元素是由协议定义的。有三种典型的方法可用来应付这个问题:定长域、变长域和分隔域。简单文本协议通常以回车等字符作为分隔符。当客户端或服务器端在接收到的数据中解析到一个回车符,就表明此处是某个命令的结尾。以字符分隔的域在一些文件格式中也常常被使用,例如以逗号分隔的值(CSV)文件,用于表示二维数组数据,这些数据可以被诸如Microsoft Execl这样的电子表单程序所读取。XML是使用字符分隔的域的另一个例子,不过它不是以简单的单字符来表明一个域的结束,XML利用多个字符来分隔文件。例如,XML元素都定义在两个尖括号(<和>)之间,元素属性的名字[nd]值用等号(=)分隔。

定长域预先定义了每个域使用的字节数。这种方法常常在网络协议的头域中被使用,例如以太网、Internet协议(IP)传输控制协议(TCP)和用户数据报文协议(UDP),因为这些协议的头域每次都需要高度结构化的数据。例如一个以太网数据包总是以标识目的媒体访问控制(MAC)地址的6个字节开始,后面跟着额外的定义源MAC地址的6个字节。

为优化目的而选择域

能够规定某些域的大小及位置的优点是规约的作者通过选择灵活的字节排列可以有助于处理器的优化。例如,IPv4头中的许多域方便地按照32位边界来排列。正如Intel Optimization Manual中的第3.41节中所述的,不按排列顺序读取数据对Pentium处理器来说相当耗费资源的。

未对准的访问对Pentium处理器家族需要耗费三个CPU周期。在PentiumPro和PentiumII处理器上,一个跨越了缓存线的未对准的访问需要耗费6至9个CPU周期。数据缓存单元(DCU) 阵列是一个内存访问装置,它跨越了32个字节的线边界。未对准的访问可能会让DCU阵列使Pentium Pro和Pentium II处理器停止运行。为了获得最佳性能,应该确保超过32字节的数据结构和数组按照32字节的数组元素对齐,并且这种对数据结构和数组元素的访问模式不能破坏对齐规则2。

相反,对一些精简指令集计算(RISC)体系结构,例如SPARC,在执行不对齐的内存访问时将会彻底失效,抛出致命的总线错误异常。

当数据不太结构化时,可变长域是人们想要的。这种方法通常被用于媒体文件。一个带有很多可选的头和数据成分的图象或视频文件格式可能会相当复杂。可变长域为协议开发者提供了灵活性,同时能够创建一个有效的、高效利用内存的协议。可变长域并不是为特定的数据元素设置固定的字节数,所有这些可能都不需要,这种域通常前面是一个表明域的类型的标识符,然后是一个数据值用来说明域中随后包含的数据元素的所占的字节数大小。

4.3 简单文本协议

简单文本协议(plaintext protocol)这个术语指的是通信数据的数据类型大部分可被归属于可打印的ASCII字符。这些字符包括所有数字、大小写字母、百分号和美元符,还有回车(\r,十六进制数0x0D),换行符(\n,十六进制数0x0A)、制表符(\t, 十六进制数0x09),以及空格(十六进制数0x20),等等。

简单文本协议的设计目的是让人们可以读懂它。简单文本协议通常不如对应的二进制协议效率高,因为它属于内存密集协议,但是有许多场合需要我们设计这种人可读的协议数据。文件传输协议(FTP)的控制信道就是简单文本协议的一个例子。另一方面,数据传输信道能够传输二进制数据。FTP用于上载和下载数据到远程机器上。FTP控制的通信数据是人可读的,这样一个事实使得通信可以通过命令行工具实现。

代码

在上面的例子中,使用了流行的Netcat3工具来人工连接到一个MicrosoftFTP服务器。尽管还有许多可用的其它FTP客户端工具,不过Netcat允许我们完全控制发送到服务器的请求命令并显示出协议的文本性质。在这个例子中,所有黑体文本是发送到服务器的请求,常规文本则代表服务器的响应。我们可以清楚地看到这里所有的请求和响应都是人可读的。这使得人们可以准确的看到当时发生了什么,并且能够调试和解决出现的问题。

4.4 二进制协议

二进制协议对于人们来说理解起来更困难,因为这种协议传输的数据不是可读文本,你看到的是原始的字节流。如果对协议不理解,协议中的数据包可能就没有专门的意义。对于模糊测试来说,如果测试目标是协议中的某个位置的话,理解协议的结构是必须的。

为了更好的说明如何才能获得对一个二进制协议的恰当的理解以产生一个模糊器,让我们考察一个特征丰富的协议,这个协议现在每天被数百万人所使用,可用来与朋友们进行AOL即时消息(AIM)对话,对话的同时可能他们都在工作。特别地,我们将考察登录会话期间发送和接收的数据包。

在详细研究AIM协议的具体内容时,有必要讨论一下开发二进制协议所用的一些基本的构造块。AIM是一个专有协议4的例子。尽管没有官方的文档,但是有关该协议的结构的大量细节我们都可以获得,这归功于其他人对该协议所做的逆向工程的努力。AIM的官方名称也叫作OSCAR(for Open System for Communication in Realtime)。逆向工程考虑到可替换的客户端,例如GAIM5和Trillian6。此外,一些网络协议分析器如Wireshark能够对包结构完全解码。这是很重要的一点。如果试图构件一个以专有协议为目标的模糊器,绝不要试图对协议实施逆向工程来重头构建模糊器。尽管这种做法在一些情况下是必要的,但是也有一些情况下你对协议或文件格式的细节感兴趣,其它人也是如此。应该利用你的好朋友Google来搜索资料,了解别人已经为你做了什么。对于文件格式模糊器,可以尝试在站点Wotsit.org获取信息,这是一个非常优秀的、集官方和非官方为一体的有关专有和开放协议文档的网站。

下面让我们看一看AIM的认证或登录过程。理解协议的第一步是捕捉一些原始数据然后将这些数据分解为更有意义的结构。对于AIM来说,我们是幸运的,因为Wireshark就已经能够理解协议的结构。现在让我们略过开始的一些有关握手的数据包,转到用户登陆有关的数据点。其中显示了Wireshark输出的初始数据包,其中显示出AIM客户的用户名被发送到AIM服务器。可以看到,协议有两个单独的层次构成。高层包括AOL即时消息的头,它标识出被发送的数据的类型。在这种情况下,该消息头说明这个数据包包含来自AIM Signon家族(0x0017)和Signon子家族(0x0006)的数据。

在低层,我们可以看到位于AIMSignon之下的数据和登录的消息头。在此处,只有infotype(0x0001)、buddyname(fuzzing is cool)和buddyname的长度字节数(13)被发送。这是一个使用可变长域的例子。注意buddyname的长度后面紧跟着它本身。前面提到过,二进制协议普遍中使用数据块。数据块以一个字节数开头,说明该数据块的数据长度,后面是实际数据(buddyname)。有时字节数的值也包括这个值本身所占的字节数,其它一些时候,这个值只表示后面所跟的实际数据所占的字节数。对于模糊测试而言,数据块是一个重要的概念。如果创建一个向数据块注入数据的模糊器,那么就必须要仔细调整数据块的长度,否则接收方应用程序将无法理解数据包中其余的内容。诸如SIPKE7这样的网络协议模糊器在设计时要注意这一点.

响应初始请求的数据包,服务器提供了一个质疑值(challenge value, 3740020309)。AIM客户使用这个质疑值生成一个密码的杂凑值。这里要再一次注意的是,此处处理的是一个数据块,质疑值的前面还有该值所占的字节数。

一旦质疑值被接收,客户就会再一次响应,返回screen name,但是此时还包括密码的杂凑值。包括在登录密钥中的还有执行登录的客户一些细节信息,用来帮助服务器识别哪个客户有能力登录。这些值以名字[nd]值对的形式提交,这是二进制协议中又一种常见的数据结构。在这里例中使用的是client id string(name)的形式,它与AOL即时消息同时被提交,版本号5.5.3591/WIN32。同样,前面还包括一个说明值域长度的长度值。

4.5 网络协议

前面介绍的FTP和AIM协议都是网络协议的例子。Internet上到处都是网络协议,并且哪里都不缺少这种协议。网络协议包括数据传输、路由、电子邮件、流媒体、即时消息以及其它更多形式的通信协议等,种类之多超乎你的想象。正如一位智者所言"关于标准,最好的事情就是有太多的标准可供选择"。网络协议标准也不例外。

网络协议是如何被开发的呢?这个问题的答案很大程度上依赖于协议是专有的还是开放的。专有协议是被封闭的小组开发的,这个小组属于某一个公司,协议主要用于专门的产品,其维护也是由这个公司来控制。不管怎么说,专有协议都有它的固有优点,因为开发者只需要在一个小的群体范围内就标准达成一致就可以了。另一方面,Internet协议在本质上是开放的,因此需要在大量不同的群体之间达成一致。总体上讲,Internet协议是由Internet Engineering Task Force(IETF)8开发和维护的。IETF对Internet标准的提案有一个冗长的发布和获取反馈的过程,首先要发布"Request for Comment(RFC)",它们是描述协议和供同行进行评审的公开文档。随后是发布评论意见和修改意见,之后RFC才能被IEFT采纳为Internet标准。

4.7 常见的协议元素

为什么要介绍这一部分背景知识呢?因文件格式或网络协议的结构不同,需要相应地调整对其进行模糊测试的方法。对数据结构了解的越多,就越容易关注那些容易引发异常条件的模糊测试对象。下面就让我们简单学习协议中的常见元素。

4.7.1 名字-值对

不论是二进制协议还是普通文本协议,其中的数据通常以名字-值对(例如size=12)的形式表示,,不过这种情况对普通文本尤其适用。回过头看一看前面提到的Content.xml文件,在整个XML文件中都可以看到名字-值对。作为一个一般规则,在处理名字-值对的时候,至多可能通过对其中的值部分进行模糊测试而识别出潜在的漏洞。

4.7.2 块标识符

块标识符是标识二进制数据类型的数字值。它的后面可能跟着可变的或固定长度的数据。在前面讨论过的AIM的例子中,AIM Sinon的头域中包含infotype(0x001)块标记。它的值定义了其后所跟的数据类型,在该例中是buddyname。模糊测试可被用于识别一些未被记录为文档的块标识符,这些标识符可嫩接受格外的数据类型,它们都可以被模糊测试。

4.7.3 块长度

块的长度在前面曾经提到过,它通常包含诸如名字-值对这样的数据,名字-值对的前面通常是一到过个字节用来说明该区域的数据类型以及后面所跟的变长域的数据长度。在模糊测试的时候,可以试着修数据块的长度值,以使它大于或小于其后所跟的数据的实际长度,同时监视测试结果。这种测试策略是缓冲区溢出或不满的一个常见的来源。作为可选的另一种方案,当对块内的数据进行模糊测试时,应确保这个长度值得到了相应的调整,以确保应用程序能够恰当地识别数据。

4.7.2 校验和

一些文件格式在整个文件中嵌入了校验和,以帮助应用程序识别可能由于各种原因而遭到破坏的数据。校验和并不一定要被实现为一种安全保护机制,因为文件可能会因为各种不同的原因而被拨坏,但是校验和却可以影响模糊测试的结果,因为应用程序通常在遇到不正确的校验和时会放弃文件处理过程。PNG图象格式所采用的文件类型是利用校验和的一个典型例子。在碰到校验和时,至关紧要的是让模糊器考虑到校验和并重新计算和重新写入校验和以确保目标应用程序能够恰当地处理文件。

4.8 小结

尽管对文件和网络通信可以采用强制性的模糊测试,然而更高效的做法是只对目标应用程序中有可能导致安全隐患的部分实施测试。虽然获得对目标应用程序的理解需要付出一定的前期努力,但是这些努力一般是值得的,尤其是在开放和专有协议的文档充足的情况下。经验有助于识别协议中最佳的目标位置,本章介绍的内容突出说明了历史上发现的应用程序中可能存在漏洞的某些弱点区域。在第22章"自动化协议解析"中,将详细介绍一些可用于解析协议结构的自动化技术。

第5章 有效模糊测试的需求

"你教一个孩子阅读,他或她将能够通过一个文化测试"

--George W. Bush, Townsend, TN, 2001年2月21日

在前一章,我们介绍了各种不同的模糊器类型和不同的模糊测试方法。本章要讨论的是进行有效和高效的模糊测试所需要的技巧和技术。一些明显的因素,例如测试计划和模糊器的可重用性应该在开发模糊器之前就予以考虑。这样的考虑有助于保证未来的工作可以依据和构建在当前工作的基础之上。使模糊器更复杂的一些更多的特征,例如过程状态和深度、跟踪和度量、错误检测以及约束条件等,本章也进行了分析讨论。在本书后面的第二部分,我们还将介绍几种模糊器的测试目标以及如何开发自动化工具来支持这些目标。在阅读本章的过程中,应该尽量记住本章介绍的一些概念,因为这些概念在构建新模糊器甚至在构建本书后面第12章"文件格式模糊测试:UNIX平台下的自动化"中要介绍的模糊测试框架的过程中将起到重要的作用。尽管当前有许多在售的商用模糊器产品,但是它们中间没有一个能够完全满足本章所描述的有效模糊测试的所有需求。

5.1 可重现性和文档记录

对模糊测试工具的一个明显需求是它应该具备重现测试结果的能力,测试结果既包括单个的测试也包括测试序列的测试结果。这个需求对测试者与它人或小组交流测试结果至关重要。作为一个模糊测试者,应该能够为模糊测试工具提供一个引起问题的测试例数,并且心里要明白被观察到的目标行为在不同的测试运行之间应该保持严格的一致。让我们考虑一个假设的场景:假设你正在测试Web服务器的能力,检验它是否能够处理非法的POST数据,在对其进行模糊测试时,向其发送的第50个测试用例引起服务崩溃,进而发现了一个可能被人利用的内存破坏条件。重新启动Web服务器并重传引起问题的那个数据给目标应用程序之后却什么也没有发现。难道这是假象吗?当然不是:计算机是确定性的计算工具,它本身没有随机的概念。前面的崩溃问题是由于输入的组合而产生的。或许前面输入的数据包首先让服务器进入了某个状态,最后第50个测试用例触发了内存破坏条件。没有进一步的分析,不具备系统的方法来重新回放整个测试过程,我们就无法进一步界定问题的起因。

即使不是强制规定要编写文档,各种各样的测试结果的记录也很有用途,这是信息共享时代的一个基本需求。在国际外包开发1的大趋势下,安全性测试者通常不可能直接走到与软件问题有关的开发者身边。外包如此普及,以至于计算机科学的学生都知道如何去利用它2。各种各样的通信障碍,包括时区、语言和通信媒体等,要求我们尽可能以清晰简洁的形式对信息打包,这一点非常重要。有组织的开发和记录文档所带来的工作负担不应该是完全的手工劳作。一个好的模糊测试工具应该产生和存储那些容易被分析和引用的日志信息。

我们应该考虑到前面讨论的模糊器如何满足软件问题的可重现性,如何自动记录日志和文档。还要考虑到如何根据这些目标改进模糊器的实现。

5.2 可重用性

从较大规模开发的角度考虑,如果我们正在构建一个文件格式模糊工具,那么我们不希望每次测试一种新的文件格式时必须要重新开发整个工具。我们可以构建出一些可重用的特征,以便未来在测试另一种不同的文件格式时能够节省时间。我们举一个例子,假设我们的目标是开发一个JPEG文件格式模糊测试工具,用来查找Microsoft Paint中的bug。事先我们考虑到并且知道需要重用现在的某些工作,那么很可能会决定将该工具分为三个构件。

JPEG文件生成器负责不停地生成变异了的JPEG文件。目标程序运行器是工具的前端,它负责周而复始地产生图象,每次都使用不同的参数让Microsoft Paint加载新生成的图象。最后,系统结构中还包括一个错误检测引擎,它负责监视Microsoft Paint的异常条件的每个实例。将系统结构分为三个构件允许我们改变测试集以适应其它文件格式,因为只需要改动生成器即可。

如果在较小规模上考虑,我们开发的许多构造块应该可以在不同的模糊测试项目之间被重用。例如,考虑一下e-mail地址。这种基本的字符串格式很常见,包括在简单邮件传输协议(SMTP)事务,登录屏幕和VoIP初始会话会话协议(SIP)中:

在上述每种情况下,e-mail地址都是一个有用的域,因为我们确定该域将被解析并且有可能被分隔为各种各样的构件(例如用户和网域)。如果我们打算花时间列举email地址可能引起问题的表示形式,那么如果让这些表示在所有的模糊器中被重用岂不是很好吗?

我们应该考虑如抽象或使模糊器被模块化,根据前面的分析思路让使模糊器具有可重用性。

5.3 过程状态和过程深度

为了牢固掌握过程状态和过程深度的概念,下面举一个许多人都很熟悉的例子:ATM银行业务。考虑下面的一个简单的状态图。

在一次典型的ATM交易中,人走到机器前(非常小心谨慎以防有人跟踪),插入他的卡,输入PIN,接着是按照一系列屏幕菜单的提示进行操作,选择希望提取的金额,然后是取出钱,最后是结束交易。同样的状态概念和状态转移的概念也适用于软件。稍后我们还要举一个这方面的具体例子。我们将过程状态(process state)定义为目标过程在任意给定的时间所处的具体状态。动作可以使状态之间发生转移,例如插卡或选择提取金额的动作。过程进行的深入程度被称为过程深度(process depth)。例如,描述一个取款金额发生在比输入一个PIN更深的过程深度上。

再举一个和安全更相关的例子,考虑一个secure shell (SSH)服务器。在连接到服务器之前,服务器处于初始状态。在认证过程中,服务器处于认证状态。一旦服务器成功通过了对一个用户的认证,那么服务器就处在已认证状态。

过程深度是达到一个具体的状态而所需要的"向前"步骤的具体度量。仍以SSH服务器为例。

在过程中,已认证状态比认证状态"更深",因为已认证状态需要在认证状态的基础上执行更多的子步骤。在模糊器的设计中,过程状态和过程深度是能够引起重要复杂情况的重要概念。下面是一个引起复杂情况的例子。为了对一个SMTP服务器的e-mail地址的MAIL FROM谓词参数进行模糊测试,就不得不连接到服务器并发出一个HELLO或EHLO命令。如图5.4所示,底层的SMTP实现可能采用同一个函数来处理MAIL FROM命令,不论最开始使用的命令是什么。

在函数1是唯一定义的用来处理MAILFROM数据的函数。还可以采用另一种可选设计方案,其中的SMTP实现包含了两个单独的例程用来处理MAIL FROM数据,这两个例程的选择取决于初始的命令。

这实际上是一个真实的案例。2006年9月7日,一个有关与IpswitchCollaboration Suite捆绑的SMTP的远程攻击栈溢出漏洞的安全公告被发布。该漏洞的产生是因为在解析email地址时遇到了其中包含位于字符@和:之间的长字符串。该分析例程只有在客户发起连接时输入了以EHLO开头的会话信息时才可达。在设计模糊器时,应该留心类似的、可能的逻辑拆分。为了获得充分的覆盖,我们在设计模糊器时对email地址进行了两次完全变异,第一是通过对EHLO变异,第二次是对HELO变异。如果沿这种思路获得更多的逻辑拆分并分别对其变异的话会产生什么结果呢?不要忘记完全充分的覆盖所需要的逻辑循环拆分是呈指数规模递增的。

在学习后面要介绍的各种各样的模糊器时,应该考虑到如何处理过程深度的变化和逻辑拆分。

5.4 跟踪、代码覆盖和度量

代码覆盖(codecoverage)是一个术语,它是指模糊器能够让被测目标系统达到或执行的过程状态的数量。直到写作本书为止,我们现在还不知道有哪种公开或商业可用的模糊测试技术具有跟踪和记录代码覆盖率的能力。这是分析员未来在这一领域进行开拓性研究的一个重要概念。质量保证(QA)小组可以利用代码覆盖提升对测试水平的信心。比方说,如果你是一位Web服务器产品的QA领导,在发布经过测试的产品时,你对产品达到零失效和90%的代码覆盖率比对产品达到零失效和25%的代码覆盖率很可能更有信心。软件漏洞的研究者也可以受益于代码覆盖率分析,通过对代码覆盖率的分析他们可以识别出一些应该对软件进行的必要修改之处,以使软件的代码被覆盖到一些不容易被他人注意的程序状态。代码覆盖率这个重要的概念将在本书的第23章"模糊器跟踪"中详细讨论。

在阅读本书后面对各种不同模糊器的介绍过程中,建议读者考虑一些创造性的方法来确定代码的覆盖率并分析这种方法的优点。在模糊测试时,人们总是会问"如何开始?",注意另一个问题"什么时候停止?"与这个问题同样重要。

5.5 错误检测

生成和传输可能引起异常的数据只是模糊测试战役的一半。战役的另一半是精确地确定错误何时出现。在写作本书的时候,大部分可用的模糊器对这个问题还处于"盲目"状态,因为这些模糊器并不知道被测目标如何对输入数据产生反应。一些商业解决方案在两次发送恶意请求之间加进了"ping"或"存活性"检查机制,用来控制和判定被测目标是否仍然正常履行功能。术语"ping"在这里只是随意地被使用,它的意思是指应该生成已知的良好响应的任何一个事务。此外还有其它一些解决方案,这些方案构建在日志输出分析的基础上。它们包括监视由单独的应用程序维护的ASCII文本日志或者查询系统日志,例如查询Windows Event Viewer中的日志。

这些错误检测方法的优点是它们中的大部分可以容易地在不同的平台和体系架构之间移植。然而,这些方法在能够检测到的错误种类方面严重受限。例如,它们中没有一种方法能够检测到发生在Window应用程序内同时被结构化异常处理例程(SHE)所处理的错误。

错误检测的下一代技术是使用轻量级的调试客户端来检测目标系统中的异常条件何时发生。例如,本书第二部分将要介绍的FileFuzz工具包括了一个用户定制的、开放源代码的Microsoft Windows调试客户端。利用这种类型的工具的负面作用是必须为测试的每一个目标平台开发一个调试支持工具。例如,如果想要在Mac OX、Microsoft Windows和Gentoo Linux上测试三个SMTP服务器,就很可能需要开发两个甚至三个不同的监视客户端。此外,由于测试目标所限,有可能无法或无法及时构建这样的调试客户端。再例如,如果测试一个硬件VoIP电话,那么就可能必须重新走到对测试实施控制或对日志实施监控的老路上去,因为硬件解决方案不不支持调试,因此可能需要另外的专用工具。

如果进一步向前展望,错误检测的万能药很可能是诸如Valgrind6和Dynamo Rio7这样的动态二进制插装/翻译平台。在这样的平台上,有可能在软件开发时就进行错误检测而不是在错误发生后再检测它。如果朝着"5000英尺"的距离展望,基于DBI的调试引擎能够非常专业地在底层对目标软件执行分析和插装。这种底层的控制能够支持内存泄露检查、缓冲区溢出检查和非越界检查,等等。回顾前面讨论可重现性时所举的内存破坏的例子,在该例子的情形中,轻量级的调试客户端能够告诉我们内存破坏是何时被触发的。回顾那个例子我们可以知道,当时的场景是许多数据包被发送给目标服务,结果第50个测试用例引起服务崩溃。在一个诸如Valgrind这样的平台上,我们有可能检测初始的内存破坏,它发生在触发异常之前的早期的测试用例的执行中。这种方法能够节省数个小时甚至许多天的模糊过程和错误跟踪。

5.6 资源约束

各种各样的非技术因素都可能对模糊测试带来限制,例如预算和时间期限。这些因素在设计和规划阶段必须被牢记。例如,你可能在模糊测试之前的最后一分钟产生试水前的恐慌感,因为没有人哪怕是很简单地检查过你投资50万美元所开发的产品的安全性。安全性在太多情况下成为软件开发周期(SDLC)过程中事后的追悔,在增加新特征或达到了产品发布期限时的追悔。如果我们希望开发安全的软件,那么安全性应该被我们"培养"而不是被我们"突击"。这种培养包括对SDLC做出根本性的改变以确保安全性在开发过程的每一个阶段都被恰当地考虑。换句话说,我们承认软件是在真实世界中而不是在一个乌托邦的国度中被开发的,后者的资源是充足的,缺陷是稀少的。因此,在阅读本书的过程中,同样重要的是心中应该对各种不同的技术做一个分类,思考哪些技术可以运用在时间和资源有限的情形下,哪些技术只能应用在梦想"最终"理想的模糊器套件的过程中。此外,还要考虑应该在SDLC的哪个阶段实现这样的工具,以及谁负责这个过程。

5.7 小结

本章的主要目的是介绍一些用来开发特征丰富的模糊器所要考虑的一些基本思想和创造性见解。当你在开发、比较和使用模糊测试技术时,如果再次阅读本章的内容,就会变得小心谨慎。未来的几章将介绍和分析各种不同的模糊测试解决方案,届时读者应该考虑本章所介绍的一些需求目标如何在相应的背景下得到实现和改进。

第6章 自动化测试和测试数据生成

"我们的敌人富有创新精神并且足智多谋,我们也一样。他们总想找到新的方法来损害我们的国家和人民,而我们不会。"

--George W. Bush, Washington, DC,2004年8月5日

模糊测试在很大程度上可以采用自动化的方式来实施。模糊测试相对于其他的软件测试方法而言,其核心价值体现在能够将大量的手工测试转换为高度的自动化测试。生成单个测试用例是一项费力和枯燥的工作,而某些任务则非常适合于计算机来完成。模糊器的核心竞争力就是它在人工干预最小的情况下,生成有用测试数据的能力。本章关注于自动化测试的不同方面,包括语言的选择、有用的构造块、以及为了有效的测试目标软件,在测试数据生成过程中如何选择适当模糊值的重要技巧。

6.1 自动化测试的价值

尽管自动化测试的优点是显而易见的,这里为了清晰起见,再从两个方面回顾一下。首先,我们根据人工计算的能力来强调其价值,然后再分析其可重用性。针对于早期的计算机而言,计算时间是非常宝贵的,事实上,它比人工计算的时间还要宝贵;因此,在这些系统上编程是一个非常枯燥乏味的手工过程。程序员要操作磁带、转换开关、并且要手工输入十进制或者二进制的机器操作码。随着科技的发展,程序员的聪明才智不断得以体现,计算时间更加充裕,使得成本代价的平衡发生了改变。目前,这种趋势仍在继续,并且在日益流行的高级编程语言如Java,.NET和Python中得到了印证。这些编程语言牺牲了一些计算时间,而却为程序员提供了更加简便的开发环境,以及更加迅速的开发转换时间。

鉴于此,尽管对于程序分析员而言,他和基于socket的daemon程序进行交互并且手工在程序漏洞中输入数据以发现软件缺陷是十分可行的,但是最好将人工时间花费在其他任务上。当比较模糊测试与人工审核工作如源代码审查和二进制审核时,也可以得出同样的结论。人工审核方法需要高级分析员花费大量的时间,而前一种方法即模糊测试,则可以或多或少的由任何人来实施。最后,自动化应当作为减少高级程序分析员工作量的第一步,使其能够象关注其它测试方法那样来发现缺陷。
接下来,强调一下可重用的必要性。下面两个关键因素奠定了可重用性的重要性:

如果我们能够为一个FTP服务创建一个可重用的测试过程,那么我们就可以使用同一个测试过程很方便的测试其它版本的程序,即便是完全不同的FTP服务也可以。否则,需要浪费大量的时间来为每一个FTP服务重新设计并实现一个新的模糊器。

如果一个非寻常的事件序列在目标程序中触发了一个缺陷,那么我们必须要再次产生整个序列以限制造成异常的特定结果。程序分析员需要创建不同的测试用例,因为它们不具备科学的可重用性。

简而言之, 测试数据生成和再生成、缺陷监测等耗时耗力的工作最适合于实现自动化。象大多数计算任务一样,对模糊测试而言,幸运的是我们可以利用许多已有的工具和库来实现自动化。

6.2 有用的工具和库

大多数模糊器的开发者将会发现他们从头开始创建工具,尽管已经有了大量的公共可用的模糊器脚本 。幸运的是,许多工具和库可以在模糊器的设计和实现阶段为你提供帮助。本节列举了一些这样的工具和库(按字母顺序排列)。

6.2.1ETHEREAL /WIRESHARK

Wireshark(Ethereal项目的一个分支) 是一个流行的开源网络嗅探器和协议分析器。尽管依赖于其中的一个库并不是必要的,但毫无疑问,该工具可以在创建模糊器的研究和调试阶段提供帮助。Wireshark所提供的许多开源的分析器也经常作为参考来发挥作用。被识别为一个拥有可用分析器的被捕获通信会被显示为一系列的字段/值对,这些字段/值对是和一组原始字节相对应的。在开始人工协议分析之前,通常是首先认真地查看一下Wireshark的分析结果。对于一个可用分析器的快速列表而言,可以参阅Wireshark的版本控制库,特别是epan/dissectors 目录。

6.2.2 LIBDASM和LIBDISASM

Libdasm和libdisasm都是免费和开源的反汇编程序库,你可以将它们嵌入到你的工具中,以用于从二进制流开发AT&T和Intel的语法解析器。Libdasm是用C语言编写的,而libdisasm则是用Perl编写的。在libdasm中包含一个Python的接口。尽管反汇编程序对于创建网络通信是不必要的,但是当在通信的终端进行自动化的错误检测时它是很重要的。上述两个库在本书中被广泛使用,尤其是在第12章"文件格式模糊测试:UNIX平台上的自动化测试",第19章"内存数据的模糊测试",第20章"内存数据的模糊自动化测试",第23章"模糊器跟踪"以及第24章"智能的故障检测"中。

6.2.3 LIBNET/LIBNETNT

Libnet是一个针对创建和注入低层网络包数据的免费、开源的高层API。该库隐藏了在生成IP和链接层通信时包含的许多复杂的细节,并且始终提供了跨不同平台的可移植性。如果你正在编写一个网络栈的模糊器,那么可能会对该库产生兴趣。

6.2.4 LIBPCAP

LibPCAP以及和微软Windows相兼容的WinPCAP 都是免费、开源的高层库,能够简化跨UNIX和微软Windows平台的网络捕获和分析工具的创建。许多的网络协议分析工具,例如前面提到的Wireshark都是依靠该库创建的。

6.2.5 METROPACKET LIBRARY

Metro Packet Library是一个C#库,它提供了一个与IPv4、TCP、UDP和Internet控制消息协议(ICMP)相交互的抽象接口。该库有助于创建包嗅探器和网络分析工具。对于该库的讨论和应用请见第16章"网络协议模糊测试:Windows平台上的自动化测试"。

6.2.6 PTRACE

在UNIX平台上进行调试,大部分是在系统调用ptrace()(进程跟踪)的帮助下完成的。一个进程可以利用ptrace()来控制寄存器状态、内存、执行过程、并且捕获另一个进程产生的信号。这种机制用于实现第8章"环境变量和参数的模糊测试:自动化"和第12章"文件格式模糊测试:UNIX平台上的自动化测试"中所讨论的工具。

6.2.7 PYTHONEXTENSIONS

在创建模糊器时可以使用不同的Python扩展。示例扩展包括Pcapy,Scapy和PyDbg。Pcapy 是针对LibPCAP/WinPCAP的Python扩展接口,以使得Python脚本能够捕获网络通信。Scapy 是一个功能强大的、简便的包操作扩展,它既可以通过交互方式使用也可以作为一个库使用。Scapy能够创建和解析许多不同种类的协议。PyDbg 是一个纯Python的微软Windows32位的调试器,是一个方便灵活的进程工具。PyDbg库是PaiMei 逆向工程框架的一个子集,该框架将在本书的第19、20、23和24章中使用。

这里所列出的某些库可以用于多种语言。而另外一些则要受到很大的限制。当决定采用何种语言来开发模糊器时,特定的需求以及能够帮助实现这些需求的可用的库是必须要考虑在内的因素。

6.3 编程语言的选择

许多人认为模糊器编程语言的选择问题是一个笃信性的争论,他们固执的坚持其选择而不论所要解决的问题是什么,而其他人则遵循"对于正确的任务使用正确的工具"的准则。我们倾向于后一种做法。在本书中,你将会发现源代码是采用不同的语言所编写的。在本书的编写过程中花费了大量努力以尽可能多的包含可重用的实际的代码示例。无论你个人的喜好如何,我们鼓励你在选择一种语言用于特定任务之前,先思考所给定的任何语言的优劣之处。

在最高的层次上,你将会在开发的起点上发现编程语言存在一个根本的不同:解释型语言和编译型语言。在低层上,编译型语言如C和C++提供了准确和直接访问底层构件的功能。例如前面提到的Libnet库,就是在此低层的接口。在高层上,解释型语言如Python和Ruby提供了更快的开发时间,并且不需要重复编译就可以进行修改。针对高层语言提供低层功能的库通常是存在的。如果模糊器既要求灵活性又要注重代码质量,那么模糊器将会用所有的语言来编写。依赖于特定的任务和对各种语言的熟悉程度,从shell脚本到Java,从PHP到C#,肯定有一种语言最适合所面临的任务。

6.4 测试数据生成和模糊启发式

如何生成测试数据的实现方法只是待解决问题的一部分,同等重要的是决定生成什么样的测试数据。例如,考虑如果我们正在创建一个模糊器以分析一个IMAP服务的健壮性。在许多IMAP请求动作和构造器中,需要加以研究的是命令继续请求(Command Continuation Request,CCR),其引自RFC3501 的描述如下:

7.5服务器响应--命令继续请求

命令继续请求响应是通过一个"+"标记来指示的,而不是一个标签。这种形式的响应意味着服务器已做好准备以接收来自客户端的命令继续请求。响应的剩余部分是一行文本。

该响应用在AUTHENTICATE命令中,将服务器数据传递给客户端,并请求额外的客户端数据。如果命令的参数是一个文本,那么也会用到该响应。

客户端不允许发送文本的字节,除非服务器表明它希望这样做。这就允许服务器来处理命令并且逐行的抛出错误。该命令的剩余部分,包括终止命令的CRLF,后面跟着文本的字节。如果有任何额外的命令参数,则文本字节的后面跟着一个空格和参数。

根据RFC,任何以{数字}(用黑体突出显示)形式结尾的命令指明了在后续行中接续的命令的剩余字节数。这是模糊测试的主要目标,但是应该使用什么值来测试该字段呢?所有可能的数字值都能够被测试吗?目标daemon程序可能会接收最大的32位整数值范围内的所有整数,其大小为0xFFFFFFFF(4,294,967,295)。如果模糊器能够每秒钟执行一个测试用例,那么将会花费大于136年的时间来完成测试!就算我们将模糊器加足马力运行,使其每秒钟可以运行100个测试用例,那么仍然需要大约500天的时间来完成测试。按照这个速率,当我们结束测试时,IMAP协议可能将早已过时。很明显,不能检查整个范围内的所有数据,必须要选择一个智能子集或者可能整数值的一个代表集。

在模糊字符串或者模糊数据列表中所包含的特定的潜在危险值,被称作模糊启发式(fuzz heuristics)。下面学习几个将要包含在智能库中的数据类型的类别。

6.4.1 整型值

在新的改进的整型测试用例列表中,选择两个极限边界(0和0xFFFFFFFF)测试用例是很明显的。还能增加其它什么值呢?可能所提供的数字被用作内存分配程序的大小参数。通常额外空间被指定的大小所包含以容纳一个头、尾或者终止空字节。

由于相同的原因,被指定的大小可能会被之前的值减去以进行分配。当目标程序知道它不准备将所有的指定数据拷贝到新分配的缓冲区时,就会发生这种情况。记住整型值的溢出(加法运算导致结果超出32位整数的最大范围)和下溢(减法运算导致结果小于0)可能会导致在下面的代码行中出现潜在的安全问题,因此应当谨慎的包含边界附近的测试用例,例如0xFFFFFFFF-1,0xFFFFFFFF-2,0xFFFFFFFF-3…,和1,2,3,4等等。

类似的,对所指定的大小同样可以应用乘法运算。例如,考虑如果提供的数据被转换为Unicode的情形。这就要求指定的大小去乘以2。另外,还应当包括额外的两个字节以确保空终止符。

为了在此以及类似的情形下触发一个整数的溢出,我们还应当包含下列边界附近的测试用例:0xFFFFFFFF/2,0xFFFFFFFF/2-1,0xFFFFFFFF/2-2等。那么被3除或被4除所得边界附近的测试用例是什么情形呢?在这种情况下,对16位整数(0xFFFF)而言,同样包含前面所列出的边界附近的测试用例又如何呢?那么对8位整数(0xFF)又如何呢?下面将这些问题与其它一些巧妙的选择列在一起。

在上面的列表中,MAX32表示32位整数(0xFFFFFFFF)的最大值,MAX16表示16位整数(0xFFFF)的最大值,MAX8表示8位整数(0xFF)的最大值,随机选择的范围16作为一个合理的数量。依赖于不同的时间和结果,可以增加该范围值。如果在目标协议中有数百个整型字段,那么额外的启发式整数将上百倍的增加测试用例的总个数。

采用有趣的方法可以获得这些所选择的"启发式"整数。启发式只不过是"技巧性猜测"的一种风趣的说法。更加高级的用户可以在反汇编程序的作用下研究二进制代码,并且通过研究内存分配和数据复制程序中的交叉引用来抽取潜在的感兴趣的整数值。这个过程也可以实现自动化,第22章"自动化协议解析"中阐述了相关的概念。

智能数据集的必要性

2005年9月1日,一则名为"NovellNetMail IMAPD 命令继续请求堆溢出" 的安全性公告同相应的提供商的补丁被一起公开发布。这个公告详细阐述了该缺陷,它允许远程未授权的攻击者以执行任意代码,进而全面危及整个系统的安全。

所描述的缺陷存在于CCR的处理过程中,作为用户所指定的大小值被直接用做名为MMalloc()的自定义内存分配函数中的一个参数。

MMalloc()函数在分配内存之前对所提供的值执行一步小的数学操作。攻击者可以指定一个引发整数溢出的恶意数字,从而导致一个小的内存块被分配。所提供的原始的较大的值将在后面的memcpy()中被使用。

该指令序列将把攻击者所提供的数据拷贝到分配堆区域的边界之外,并且随意的覆盖堆区域,最终破坏整个系统。

尽管Novell针对此问题的补丁成功的解决了这个特定的问题,但是它并没有解决所有可能的攻击路径。对于Novell而言,不幸的是,IMAP程序将会把人工可读的数字形式转换为等价的整型值。例如,"-1"转换为0xFFFFFFFF,"-2"转换为0xFFFFFFFE等。

上述这个问题后来被一个独立的研究者所发现,Novell于2006年12月22日发布了针对该问题的补丁 。

6.4.2 字符串重复

我们已经讨论了如何在模糊测试数据集中包含一些巧妙的整数,但是对于字符串而言,情形又如何呢?下面从经典 的"长字符串"开始讨论:

当我们对字符串进行模糊测试时,希望包含一些额外的字符序列。应当首先包含其它的ASCII字符,例如"B"。这是非常重要的,例如由于不同的行为,堆结构中"A"的ASCII值被"B"的ASCII值覆盖,这将在微软Windows操作系统中触发堆溢出。另外一个重要的原因是,实际上软件要特别的搜索并阻止A的长字符串。显然,某些提供商已经明白了使用长字符串AAAAAAAAAAAAAAAAA的强大功能。

6.4.3 字段分隔符

同样需要包含非字母字符,包括空格和制表符。这些字符通常被用做字段分隔符和终止符。将它们随机的包含到所生成的模糊测试字符串中,可以提高将被被测协议进行部分复制的机会,进而增加被模糊测试的代码的数量。例如考虑相关的简单HTTP协议,下面列出了一些非字母字符:

其中有多少个可以被识别为HTTP字段分隔符呢?

你可能会注意到的第一个样式就是这里有许多换行分隔符,通过序列0x0d 0x0a在字节层次上被表达。每个单独的行后面都使用不同的分隔符。例如在第一行,可以看到有字符空格( ),前斜线(/)和点(.)被使用以分隔响应代码。在后续行上都使用了一个共同的分隔符冒号(:)来分隔不同的项,例如Content-Type,Server和Date和它们相对应的值。进一步研究可以发现逗号(,),等号(=),分号(;)和短划线(-)也被用来分隔字段。

当生成模糊字符串时,很重要的一点就是要包括象前面所列出的不同字段分隔符所分隔的很长的字符串。另外,增加分隔符的长度同样也是很重要的。例如,考虑在2003年被发现的发送邮件头的处理漏洞 。此漏洞需要一个包含<>字符的长字符串来触发破坏可利用内存的行为。同样考虑下面的代码片段,它引发了重复字符串分隔符解析的漏洞。

该含有漏洞的解析器处理一个字符串,期望找到冒号分隔符的一个单个实例。当分隔字符被找到时,保存指向该字符串索引的指针。否则,以1来递增变量length。当循环结束时,将所计算的变量length减去1以为空终止符保留空间,然后针对找到的分隔符再减去1,将它与目标字符缓冲区的大小相比较,以确保存在足够的空间来执行后面的strcpy()函数调用。该处理逻辑字符串如name:pedram amini能被正确处理。对于此字符串,解析器将计算出length的值为16,意味着没有足够的空间,于是就跳过函数strcpy()的执行。如果字符串是name::::::::::::::::::::::pedram,情形又怎样呢?在此情况下,计算得到length的值将变为10,于是可以通过条件检查,开始执行strcpy()函数。然而,在strcpy()中所使用的该字符串的实际长度却是32,因此将触发一个栈溢出。

6.4.4 格式化字符串

格式化字符串是相对较为容易被发现的一类错误,模糊器所生成的测试数据应当包括这类字符串。格式化字符串漏洞可以用任意格式化字符串标记来揭露,例如%d,将显示一个十进制数,而%08x将显示一个十六进制数。在进行模糊测试时,格式化字符串标记最好选择%s或者%n(或者二者均选)。这些标记更有利于导致可检测错误的发生,例如内存访问违规。大多数格式化字符串标记都将导致从栈中读取内存,通常这并不会触发漏洞代码中的错误。因为栈被解除引用,%s标记将导致发生大量的内存读取操作以寻找一个表明字符串终止的空字节。在大多数情况下,%n标记提供了最大的可能性来触发一个错误。启发式模糊数据列表中应当包括%s%n的长序列。

利用格式化字符串漏洞的关键

尽管所有其它的格式化字符串标记,包括%d,%x和%s都将导致从栈中读取内存的操作的发生,但%n格式化字符串却具有唯一性,因为它还导致写内存。这是在代码执行中利用格式化字符串漏洞的关键需求。尽管其它被拒绝的格式化字符串标记可以被用来从漏洞软件中"泄露"潜在的关键信息,而利用%n标记则是通过格式化字符串直接写内存的唯一方法。正是因为这个原因,微软决定实现一个控制机制,以在printf函数类中完全支持%n格式化字符串标记。相应的API函数是_set_printf_count_output() ,若该函数被赋予一个非零值则提供对%n的支持,若赋予零则取消对%n的支持。实际上,%n在产品代码中很少被使用,因此默认的设置都不提供对它的支持。

6.4.5 字符翻译

需要关注的另外一个问题是字符转换和翻译,特别是关于字符扩展。例如,十六进制数0xFE和0xFF在UTF16下被扩展为四个字符。在解析代码中对这样的字符扩展缺乏有效的处理,是存在漏洞的一个重要方面。字符转换同样可能会被不正确的实现,尤其是在处理很少被看到或使用的边界数据时。例如微软的Internet浏览器,在从UTF-8转换为Unicode时就被该问题所影响 。该问题的症结在于,当转换程序为存储转换后的缓冲区而确定动态分配存储区的大小时,没有正确的处理5字节和6字节的UTF-8字符。然而,实际的数据拷贝程序却正确的处理了5字节和6字节UTF-8字符,因此就导致了基于堆的缓冲区溢出。启发式模糊数据列表中应当包含这些以及其它类似的恶意字符序列。

6.4.6 目录遍历

目录遍历漏洞会影响网络daemon程序以及Web应用程序。通常的一个误解是认为目录遍历漏洞只限于影响Web应用程序。目录遍历漏洞确实在Web应用程序中很常见,然而,这种攻击方式同样也会出现在私有网络协议和其它应用方面。根据2006年Mitre CVE的统计,尽管随着时间的发展,目录遍历漏洞在某种程度上有所减少,但其仍然是在软件应用程序中所发现的第五大类漏洞 。这些统计包括Web应用程序和传统的客户端/服务器应用程序。可以在开源漏洞数据库(OSVDB)中看到多年来所收集到的许多目录遍历漏洞的例子 。

例如,考虑ComputerAssociates公司的BrightStor ARCserve备份软件。BrightStor通过caloggerd程序和TCP协议为用户提供了注册接口,尽管该协议没有被公开,通过基本的包分析可以发现注册文件名实际上是在网络数据流中被指定的。用目录遍历修饰符给文件名加上前缀就允许一个攻击者指定一个任意的文件,将任意的注册消息写入到该文件中。当注册程序以超级用户的身份运行时,该漏洞就可以被发现。例如在UNIX系统中,一个新的超级用户可以添加到/etc/passwd文件中。在编写本书时,仍然存留着一个在安全补丁发布前而被了解和掌握的漏洞,计划于2007年7月公开发布。启发式模糊数据列表中还应当包含如../../和..\...\等的目录遍历修饰符。

类似于目录遍历漏洞,命令注入漏洞通常与Web应用程序有关,特别是CGI脚本。再次重申,认为这类错误只与Web应用程序有关是一个普遍的误解,因为它们可以通过公开的私有协议来影响网络daemon程序。任何目标程序,无论Web应用程序还是网络daemon程序,向形如exec()或system()的API调用传递未经过滤或者过滤不正确的用户数据,都将潜在的暴露一个命令注入漏洞。

在正常情况下,系统路径被服务器所接收,路径下面所列出的文件是确定的,并且列表将被返回给客户端。然而由于缺乏对输入进行过滤,因此可以指定特定的字符以允许执行额外的命令。对于UNIX系统而言,这些字符包括:&&,;和|。例如,参数var/lib;rm-rf/转换为ls/var/lib;rm-rf,该命令可以给被影响系统的管理者带来严重的问题。启发式模糊数据列表中同样应当包含这些字符。

6.5 小结

在本章的开始,讨论了自动化测试的必要性,同时简要列举并描述了可以用来简化开发的不同库和工具。其中,对某些库作了更为详细的解释,并应用到了本书第2和第3部分的自定义工具的开发中。针对数字、字符串和二进制序列,如何选择智能和高效的模糊数据是本章所讨论的一个核心问题。这些知识将在后续章节中得到应用并加以扩展。

在后续章节中,我们将讨论不同的模糊测试目标程序,包括Web应用程序,私有命令行应用,网络服务程序等等。在阅读后续章节时要记住本章所讨论的概念。在模糊器的开发过程中可以应用所介绍的不同库和工具。思考一下本章所讨论的智能模糊数据,以及将来会向模糊数据列表中添加哪些值。

第7章 环境变量和参数的模糊测试

"这样的外交策略令人感到一丝沮丧。"

--George W. Bush,引自2002年4月23日《 NewYork Daily News》

可以证明,本地化模糊测试是最简单的一种模糊测试。尽管许多攻击者和研究者将通过发现远程和客户端的漏洞来获取更加重要的结果,但本地私有程度的增强仍然是一个重要的研究课题。即使利用一个远程攻击来获得对目标机器的访问,也通常要使用本地化攻击作为一种辅助攻击手段来获取所需要的私有信息。

7.1 本地化模糊测试介绍

用户可以通过两种主要的方法将变量引入到程序中。除了显而易见的标准输入设备,通常是键盘之外,命令行参数和进程-环境变量也可以表示输入变量。我们首先采用命令行参数的方式来实现模糊测试。

7.1.1 命令行参数

除了最纯粹的Windows用户之外,其他人可能都或多或少的遇到过需要命令行参数的程序。命令行参数被传递给一个程序,并通过在C程序中的main函数中声明的argv指针来寻址。变量argc也要传递给main函数,它表示了传递给程序的参数的个数,另外还要加上1,因为它所激活的程序名被作为一个参数来计算。

7.1.2 环境变量

用户将变量引入到程序中的另外一种方法是使用环境变量。每个进程都包含由环境变量组成的所谓的环境。环境变量是全局变量,它定义了应用程序的行为。用户可以设置或取消环境变量,但通常都在软件包的安装过程中或被管理员设置为典型值。大多数的命令解释器将导致所有的新进程都继承当前的环境。command.com shell是Windows中的命令解释器的一个例子。UNIX系统通常有多个命令解释器如sh,csh,ksh以及bash。

通常使用的一些环境变量的例子包括HOME,PATH,PSI和USER。这些值分别保存了用户的主目录,当前可执行的搜索路径,命令提示符以及当前的用户名。这些特别的变量是很标准的;然而许多其它的公共变量,包括软件提供商所创建的那些变量,只能用于其应用程序的操作中。当一个应用程序需要某个特定变量的信息时,它只需使用getenv函数,该函数将变量名指定为参数。尽管Windows进程和和UNIX应用程序以相同的方式来包含环境变量,这里主要关注于UNIX系统,因为Windows没有setuid应用程序的概念,该应用程序可以被无特权的用户所启动,并在执行过程中获取私有信息。

用户可以使用export命令来操纵该列表中的每个变量。在理解了命令行参数和环境变量的用法之后,就可以开始讨论对它们进行模糊测试的一些基本准则。

7.2 本地化模糊测试准则

环境变量模糊测试和命令行模糊测试的基本思想很简单:如果一个环境变量或者命令行选项包含一个非预期值,那么当该值被接收之后,应用程序将如何做出响应呢?当然,这里只对非正常运行的授权应用程序感兴趣。这是因为本地化模糊测试要求对机器进行本地访问。因此,只需要有限的值就可以使应用程序崩溃,你必须要自己执行拒绝服务的攻击。如果一个环境变量发生了溢出而导致被多个用户共享的系统或应用程序崩溃,那么就会带来一些风险。然而,我们最感兴趣的是在授权的应用程序中寻找一个缓冲区溢出,而该溢出将允许一个受限用户来提升其特权。寻找授权目标程序将在本章后面的"寻找目标程序"一节中进行讨论。

许多应用程序都进行了这样的设计,即当其被激活时,能够从用户接收命令行参数。然后,应用程序便使用此数据来决定它应当进行的操作。几乎在所有UNIX系统中都能找到的'su'应用程序就是一个极好的例子。当用户不使用任何参数来激活应用程序时,它将会对根用户进行鉴别;然而,如果用户指定了一个不同于第一个参数的用户名,那么它将会切换到此用户,而不是根用户。

考虑下面的C语言代码,它简要的说明了su命令如何根据不同的参数来采取不同的行为。

命令行参数和环境变量是将变量引入到程序中的两种最基本的方式。对其进行模糊测试的思想很简单。当我们通过命令行向程序输入错误数据时将会怎样呢?这种行为会导致安全风险吗?

7.3 寻找目标程序

当执行本地化模糊测试时,通常在系统中只有少量的所需要的二进制目标程序。这些程序在执行时具有更高的优先级。在基于UNIX的系统中,这些程序很容易被识别,因为它们包含有setuid或者setgid位集。

setuid 和setgid位表明当一个程序运行时,它可以要求提升其特权。对于setuid位,进程将拥有文件的所有者所具有的特权,而不是程序执行者的特权。在setgid位的情况下,进程将拥有文件的组所有者具有的特权。例如,成功的开发一个有setuid根用户和setgid组用户的程序,可能会生成具有这些特权的一个shell。

使用find命令来构建setuid二进制代码的列表是很简单的,该命令是UNIX和与UNIX类似的操作系统中的一个标准工具。下面的命令可以完整的列出系统中所有的setuid二进制代码。它应当作为根来运行,以防止文件系统的读取错误。

find命令是一个强有力的工具,它可以被用来发现特定的文件类型,设备驱动程序以及文件系统中的目录。在上面的例子中,我们只使用了find命令所支持的一小部分选项。第一个参数指明了我们将要搜索整个系统以及在根目录/下面的所有文件。type选项告诉find命令我们只对文件感兴趣。这意味着不会返回符号链接,目录或者设备驱动程序。-perm选项描述了我们所感兴趣的特权。-o选项的使用允许find命令使用逻辑操作符或or。如果一个二进制文件含有setgid位或者setuid位集,那么其值将为true,并且打印出该文件的路径。总之,该命令将搜索所有含有setuid位(4)或者setgid(2)位集的规则文件。

7.3.1 UNIX文件许可说明

在UNIX系统中,文件许可模型允许三种基本的不同类型的访问:读、写和执行。对每个文件而言,同样也存在着三种许可访问集,它们分别属于用户、组以及其它类型。在任意给定的条件下,实际上只有这些许可当中的一个被应用。例如,如果你拥有一个文件,则将会被用到的许可集就是该用户。如果你不拥有该文件,而组拥有该文件,那么组许可将会被应用。对于所有其它的情形,其它类型的许可将会被应用。

在这个例子中,用户dude拥有该文件。那么该用户的许可包

作者:k0shl ####一些环境说明: 编译环境:Windows 10 x64 build 1607 项目IDE:VS2013 测试环境:Windows 7 x86、Windows 10 x86 build 1607 参数介绍: "-l" :开启日志记录模式(不会影响主日志记录模块) "-s" :驱动枚举模块 "-d" :打开设备驱动的名称 "-i" :待Fuzz的ioctl code,默认从0xnnnn0000-0xnnnnffff "-n" :在探测阶段采用null pointer模式,该模式下极易fuzz 到空指针引用漏洞,不加则常规探测模式 "-r" :指定明确的ioctl code范围 "-u" :只fuzz -i参数给定的ioctl code "-f" :在探测阶段采用0x00填充缓冲区 "-q" :在Fuzz阶段不显示填充input buffer的数据内容 "-e" :在探测和fuzz阶段打印错误信息(如getlasterror()) "-h" :帮助信息 ####常用Fuzz命令实例: kDriver Fuzz.exe -s 进行驱动枚举,将CreateFile成功的驱动设备名称,以及部分受限的驱动设备名称打印并写入Enum Driver.txt文件中 kDriver Fuzz.exe -d X -i 0xaabb0000 -f -l 对X驱动的ioctl code 0xaabb0000-0xaabbffff范围进行探测及对可用的ioctl code进行fuzz,探测时除了正常探测外增加0x00填充缓冲区探测,开启数据日志记录(如增加-u参数,则只对ioctl code 0xaabb0000探测,若是有效ioctl code则进入fuzz阶段) kDriver Fuzz.exe -d X -r 0xaabb1122-0xaabb3344 -n -l 对X驱动的ioctl code 0xaabb1122-0xaabb3344范围内进行探测,探测时采用null pointer模式,并数据日志记录
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值