从研究者的视角看Fuzzing技术发展30年

源起

1988年,在威斯康星大学Barton Miller教授的计算机实验课上(http://pages.cs.wisc.edu/~bart/fuzz/CS736-Projects-f1988.pdf ),首次提出Fuzz生成器(Fuzz Generator)的概念,用于测试Unix程序的健壮性,即用随机数据来测试程序直至崩溃。因此,Barton Miller教授也被多数人尊称为”模糊测试之父”。但是,当时更多是为了验证代码质量和程序的稳定性,而非专门用于挖掘安全漏洞,真正用于软件安全漏洞挖掘的开端要从下面两件事说起。

从学术界到工业界的证明

2001年,芬兰奥卢大学公布PROTOS测试集项目的研究成果(https://rd.springer.com/content/pdf/10.1007%2F978-0-387-35413-2_16.pdf ),首次将Fuzzing技术应用在网络协议的安全测试当中,他们针对不同的网络协议构造出不同的测试用例集,这些在其官网(https://www.ee.oulu.fi/research/ouspg/Protos )上依然可以下载到。2002年,PROTOS逐渐成熟,Microsoft开始为该项目提供资金支持。于是,2003年项目组成立了Codenomicon公司,开始将Fuzzing技术应用于商业产品,也确实发现了不少安全问题。因此,PROTOS项目可以说是Fuzzing技术发展历程中的一次重要里程碑。可能大家对Codenomicon公司不太了解,但说到”心脏滴血”漏洞,应该无人不知无人不晓。没错,它就是Codenomicon公司发现的。

2002年,在BlackHat USA黑客大会上,来自Immunity安全公司(PS:还有人记得Immunity Debugger吗?)的Dave Aitel发表议题“An Introduction to SPIKE, the Fuzzer Creation Kit”,至此著名的Fuzzer工具SPIKE公布了,它是基于块模板定义的网络协议测试工具,优点是支持定义可变长度数据块的能力,除生成随机数据外,它还提供一些现成的边界值生成,以提供触发崩溃的概率。SPIKE的诞生,使得广大用户能够依据自身需求定制网络协议Fuzzer,这对Fuzzing技术的普及起到巨大的推动作用。早些年,笔者也曾写过一篇SPIKE的教程“基于SPIKE的网络协议Fuzzing技术(http://riusksk.me/2011/12/30/spike-fuzz/)",但现在其实基本不用了。

PROTOSSPIKE的诞生,代表着学术界与工业界对Fuzzing技术在商业与安全实战领域的应用提供了有力的证明。

文件Fuzzing技术的兴起

2004年,Peach模糊测试框架的发布,标志着文件Fuzzing时代的到来。最初Peach是用Python开发的,后来在2007年被收购后改用C#重写,并分为社区版和付费版。Peach支持文件格式、网络协议、ActivieX控件等多种形式,通过编写pit文件(xml格式)来定义数据格式,每次开始写的时候其实挺费劲的,后来有人提供自动将010editor格式解析器(仿C语言的bt文件)转换为pit,在一定程度上可以缓解劳动力。笔者第一次通过文件Fuzzing挖到漏洞也是借助Peach实现的。

直至今日,Peach依然还有人在用,更有人将PeachAFL打通,在Github上发布aflsmart的开源项目。

文件Fuzzing应该是当前Fuzzing应用中最为普遍的形式,即使是网络协议等其它目标的Fuzzing,也是可以转换为文件Fuzzing的。比如OpenSSL网络协议Fuzzing,通过源码打Log的方式先收集网络数据为本地文件,再调用其API写个hareness用AFL或libfuzzer进行本地测试,就顺利地将网络协议Fuzzing转换为文件Fuzzing。

语法模板Fuzzing:打开攻击浏览器的大门

2008年,Mozilla安全团队发布了jsfunfuzzDOMfuzz,基于JS语法模板来生成测试用例,以挖掘浏览器漏洞,后来两款工具合称funfuzz( https://github.com/MozillaSecurity/funfuzz ),以开源的形式对外公开。这款工具在当时确实挖到了不少浏览器的漏洞,但其语法模板的可扩展性并不友好,只能在其代码上作修改,这点不如dharma( https://github.com/MozillaSecurity/dharma ),以及后来Project Zero发布的Domato( https://github.com/googleprojectzero/domato )。这种基于语法模板的Fuzzing方式,挖完一波后,就要求保持模板的更新才能持续产出,同时要理解测试目标在JS代码上的触发逻辑,比如JIT可通过for循环来触发代码优化,Dom UAF可通过创建Dom元素,并调用相关元素的方法来触发删除和引用,以探测是否存在UAF的可能。整体上依赖于对语法和目标原理的理解,才能构造出好的语法模板。

funfuzz之后,业界也出现了好多款优秀的JS语法Fuzzing工具,比如grinderndujacrossfuzz等等。当年PC流行时代,用grinder来Fuzzing Windows IE浏览器的人应该比较多。

浏览器一直是网络攻击中最受关注和最常用的入口,过去如是,现今依然。因为系统自带,且用户使用率高,又是远程访问的最佳途径。渲染引擎和JS引擎一直是浏览器主要攻击面,主要以html、js、vbs作为解析语言,因此对这些语言的语法Fuzzing就自然而然的产生了。除此之外,如今WebSQL也开始备受关注,比如Chrome上的sqlite模块,SQL语法的fuzzing也随之而来。

除了浏览器,pdf的JS和flash的as语法解析,也一度作为攻击Adobe Reader和Adobe Flash的入口。

符号执行:学术与工业之争

2008年,基于LLVM的符号执行引擎KLEE发布后,引领了一波程序分析新姿势的潮流。后来,符号执行被应用于Fuzzing中,经常被用来打CTF比赛,用来找key、解混淆、fuzzing等用途。比如,将AFLangr结合的drillerhttps://github.com/shellphish/driller ),还被用在了CGC(Cyber Grand Challenge)自动网络攻防竞赛上,但这种比赛都是特定场景下的比赛,不能完全代表真实的软件世界;还有,将AFL与KLEE结合的kleeflhttps://github.com/julieeen/kleefl ),这款工具知道的人应该不多。

符号执行在学术界中应用得比较多,工业界相对少一些,这是现状。将符号执行应用在Fuzzing中,通过约束求解新路径的条件值,以增加代码覆盖率,可以一定程度上弥补暴力变异的不足。符号执行主要的挑战在于路径爆炸问题,约束求解能力的局限性,以及性能消耗问题,比如内存和时间消耗过大。符号执行与约束求解对于小型应用比较有效果,也常被用于CTF比赛,在CTF中使用最广的当属angr框架。但是,基于当前的业界情况,符号执行仍然比较难以应用于大型软件中。符号执行在Fuzzing中的应用并没有真正带来新的技术浪潮,真正的技术浪潮始于代码覆盖引导技术的引入。

代码覆盖引导技术:Fuzzing技术的分水岭

2013年底,afl-fuzzhttp://lcamtuf.coredump.cx/afl/ )发布了,首次采用源码编译插桩和QEMU模式来实现代码覆盖引导Fuzzing的方式,这绝对是Fuzzing技术发展历程中最重要的一次里程碑,也是技术分水岭,它开启了Fuzzing技术的新篇章。刚发布的时候,afl并没有那么火,主要是在2014和2015年期间,被很多人使用后挖到不少主流开源软件的0day,并在Twitter上宣传,使得更多人关注到并使用,这证明了代码覆盖引导技术在Fuzzing实战中的价值。

随后,基于afl二次开发的fuzzer如雨后春笋般涌现出来,比如winafl、libfuzzer、AFLFast、Vuzzer等等,而且针对各种语言的版本出相继出现,比如go、python、js、ruby等等。一些已知名的Fuzzer也迅速跟进,比如syzkaller内核Fuzzer,它原本是基于API调用模板的,后来也引入了代码覆盖引导能力。同时,业界都在试图将其移植到各种平台上(比如windows、android、IOT平台等等),并实现支持闭源程序的代码覆盖引导能力,这一直是近几年来Fuzzing技术研究的热点方向,比如动静态插桩、虚拟机模拟执行、硬件特性等等。无论是工业界大会(BlackHat、OffensiveCon、CCC等等),还是学术界四大顶会,关于Fuzzing的议题也越来越多,相信这种趋势会持续下去。

系统函数调用模板Fuzzing一度成为攻击内核的常用手段

2015年Google开源了syzkaller,一款用于Fuzzing Linux内核的工具,漏洞产出特别高。现在依然很多人用它来挖各系统平台的内核漏洞,包括Android、macOS、Windows等主流系统平台。syzkaller通过定义系统函数调用模板来实现,在模板中定义系统调用函数参数类型,并解决函数调用的顺序依赖和值依赖问题。Project Zero官方博客就曾写过一篇利用syzkaller fuzz socket挖掘Linux内核漏洞的文章,叫“Exploiting the Linux kernel via packet sockets(syzkaller usage)” ( https://googleprojectzero.blogspot.com/2017/05/exploiting-linux-kernel-via-packet.html ),详细讲述了如何编写模板,以及syzkaller的使用方式。

Windows平台也常被通过构建GUI API调用模板来Fuzzing系统内核,macOS平台内核Fuzzing就常拿IOKit函数开刀,都是基于这种系统函数调用模板的Fuzzing方式实现的。

2016年Google提出”结构感知型Fuzzing”(Structure-Aware Fuzzing ),并基于libfuzzerprotobuf实现了libprotobuf-mutator(https://github.com/google/libprotobuf-mutator),其实现思路与syskaller相似,它弥补了peach的无覆盖引导的问题,也弥补了afllibfuzzer对于复杂输入类型的低效变异问题。正如前面提到的,也有人将aflpeach整合成aflsmart,以此实现类似功能。现在Project Zero也用libprotobuf-mutator来fuzzing iOS内核,详见“SockPuppet: A Walkthrough of a Kernel Exploit for iOS 12.4”(https://googleprojectzero.blogspot.com/2019/12/sockpuppet-walkthrough-of-kernel.html)。

结构感知型Fuzzing并不是什么新技术,跟Peach的实现思路是一样的,只是对输入数据类型作模板定义,以提高变异的准确率。只是当前大家更倾向于将结构感知与覆盖引导等多种技术优势整合一块,基于系统函数模板用于Fuzzing系统内核,相信这种方式未来仍会被经常使用。

助力开源生态安全建设

在工业界中,最知名的Fuzzing平台当属Google的clusterfuzzhttps://github.com/google/clusterfuzz ),运行在25000+台机器上,发现过16000+个Chrome bug,11000+开源项目bug,这个平台整合了OSS-Fuzz(https://github.com/google/oss-fuzz ),既支持libfuzzer和AFL的代码覆盖引导Fuzzing,也支持黑盒Fuzzing。OSS-Fuzzclusterfuzz分别在2016年和2019年开源对外,业界同行可以协同开发,对于提交fuzzer后挖到新漏洞的,Google会美刀奖励提交者,对于主流开源项目的0Day,Google也是有奖励机制的。同时,Google开发了ASanMSanTSanUBSanLSan等多种编译时插桩工具用来帮助检测漏洞,有些漏洞只在开启相关Sanitizer之后才会触发异常,跟在Windows下开启页堆的方式类似,可以帮助更有效地发现崩溃场景,在发现和分析漏洞上提供帮助。

Google对开源生态安全建设的贡献,是各大互联网公司不能比的。他们的贡献不仅帮助提高各大主流开源项目的安全性,也降低了Fuzzing的技术成本,在资金和技术上对开源生态安全的建设提供了大力支持。

语法树变异成为语法解析引擎漏洞挖掘的新方向

2012年,USENIX安全顶会上发布一篇论文“Fuzzing with code fragments”,研究者开发了一款叫LangFuzz的工具,他们从firefox、webkit、chromium等开源的浏览器项目以及网络上去收集js测试样本,然后用ANTLR其进行AST语法树分析,再将样本拆分成非终止语法的代码片断,放入代码池中,最后再基于代码池的代码片断对输入样本作交叉变异,主要取同类型的代码片断作替换或插入,再运行生成的变异样本进行测试。

基于LangFuzz的思路,后面又有人开源了IFuzzer,并发表相关论文公开,在LangFuzz的基础上增加遗传算法,对输入样本进行评估,筛选出优秀的个体进行组装以产生新样本。不过这个工具并没有那么完善,也未见到比较好的实际漏洞产出。

2018年,Project Zero的Samuel Groß发布一款叫fuzzilli的JS语法Fuzzer工具,它整合了语法变异、模板生成、覆盖引导等多种技术,使用自定义中间语言用于语法变异,再将变异后的中间语言转换成JS代码。fuzzilli在3大主流JS引擎的测试中,战果颇丰,发现了不少漏洞,也因此被业界同行拿去作二次开发,又发现了其它新的漏洞。

2019年,有2篇学术论文发布,他们都引用了论文“Fuzzing with code fragments”中的思想,它们分别是”CodeAlchemist: Semantics-Aware Code Generation to Find Vulnerabilities in JavaScript Engines“和”Superion: Grammar-Aware Greybox Fuzzing“,CodeAlchemist将输入样本进行语法树分析和数据流分析,为拆分出来的代码片段设置前置和后置的约束条件,前置条件代表一些引用的变量需要先定义,后置条件代表代码片段的输出结果,通过两者来解决一些未定义变量引用的问题。Superion是将语法树变异规则置入AFL中实现的,借助AFL筛选变异后的输入样本,而且支持多种语言,也是采用ANTLR作语法树分析,其在语法扩展上比较友好。两款工具均在最新JS解析引擎上发现过若干0day漏洞,并且均已在Github上开源。

除了传统的模板Fuzzing,语法变异(无论是AST,还是自定义中间语言)也是一项值得探索的方向。

人工智能在Fuzzing中的应用仍亟待探索

2018年是人工智能元年,很多领域都在探索AI的应用,漏洞挖掘领域亦然。之前笔者阅读过一些AI应用在Fuzzing中的相关议题,主要集中在测试样本生成的训练上,利用已知漏洞的样本或正常样本作训练,然后重新生成测试样本进行测试。可惜从效果上来看,是一种高投入低产出的事情,有些还不如直接暴力变异来得高效高产。但这也不是说,AI没有价值,只是它在这方面的发展时间较短,很多东西仍亟待探索,还有待时间的年轮来证明。