本文摘录自链接: Eastmount大佬的文章.
这里写目录标题
漏洞挖掘技术
漏洞挖掘发展过程中,有很多的技术被提出,最有效的技术仍然是Fuzzing。学术界提出的包括静态分析,动态分析,污点分析,符号分析等方法多少具有局限性。
Fuzzing与AFL
Fuzzing与AFL的历史
Fuzzing在90年代被提出,目前已有30年历史,但真正大的发展是在2013年,它是一个动态测试的过程基本思路是想办法生成一大堆输入,扔给程序测试,观察在测试过程中出现的bug和问题。
整个过程的核心是怎样有效地生成输入去触发Bugs,因为对于程序来说,它输入空间是个无限的空间,能够触发漏洞是非常少的。那么,怎样在无限空间中有效找到少量能触发漏洞的输入,这是它的核心问题。
90年左右提出的Fuzzing问题基本是偏向随机输入的,后面又提出方法告诉输入的格式,然后基于格式去生成,但相对来说它挖漏洞的效率仍然很低,让人去写这个输入格式工程量也比较大。
2013年后,提出了一个叫做AFL的方案,利用遗传算法
来完成操作。
Fuzzing的原理思想
-
Fuzzing的核心是在无穷多个输入空间里去寻找有限的输入,去触发漏洞,那需要怎么在无穷空间里去做有限的探索呢?它用到了遗传算法。下图中间的核心循环,通过一轮一轮的迭代测试,测试过程中它会把上一轮测试比较好的测试用例留下来,作为种子进入下一轮,下一轮是在上一轮比较好的种子基础上进一步变异测试。它在无穷空间中探索时,不是盲目的去探索,而是在上一轮探索基础上去找比较好的方向,接着再这个方向上往下探索。该方法还是比较有效的。
具体分析,每轮保留测试例子。当然,挖漏洞同样需要一个进化指标,很自然就有一个指标是漏洞数量,但是用漏洞数量作为指标来进化的效果很差,因为漏洞是个非常稀疏的,你可能挖了几个小时都没挖到一个漏洞。这意味着没有进化的信号,整个算法效果就很差。 -
漏洞数量
2013年AFL工作使用的指标是代码覆盖率,测试工程中去监控程序的代码覆盖率情况,如果一个新的测试例提升了代码覆盖率,就认为它是好的种子就保留下来。通过这种方式就能不断提升代码覆盖率。
为了支持做覆盖率跟踪以及在测试过程中发现代码漏洞是否被触发,通常会对程序进行插桩,做代码覆盖率收集和Security Sanitizers(安全检测工具),插桩完成之后在测试过程中,程序会自动收集Coverage信息以及检测是否触发安全问题。
AFL
真正的AFL
在过程的每一步都有一些策略,比如怎么选种子(Select Seed)、怎么变异(Mutate Seed)、变异后怎么测试(Test)、测试过程中怎么跟踪覆盖率(Coverage Tracking)、覆盖率怎么过滤保留新的种子(Filter Seeds)等等。该算法提出来之后非常有效,改变了大家在这块的研究。AFL的重要特点如下:
Evolving
:filter out only GOOD samples contributing to code coverage
遗传算法是个进化的特征。Scalable
:mutation-based, few knowledge required
方案是可量化的,不需要知道目标软件太多的知识,给它一个软件就能测。Fast
:fork-server, persistent, parallel
测试过程非常快,一秒钟平均能测上千个测试用例。Sensitive
: support different sanitizers to catch security violations
捕获漏洞能力比较强,可以支持不同的Sanitizers,也可以扩展,比较有名的是谷歌写的AddressSanitizer,常用安全防护和漏洞挖掘。这个非常重要,有时候在测试过程中触发漏洞但程序并不一定会让崩溃,一个好的Sanitizers能够在程序未崩溃的情况下发现漏洞。
AFL的工作流程
AFL是基于覆盖引导的模糊测试工具,通过记录样本的代码覆盖率,从而调整样本以提高覆盖率,增加发现漏洞的概率,工作流程如下:
- 从源码编译程序时进行插桩,以记录代码覆盖率(Code Coverage)
- 选择一些输入文件,作为初始测试集加入输入队列(queue)
- 将队列的文件按一定的策略进行‘突变’
- 如果经过变异文件更新了覆盖范围,则将其保留添加到队列中
- 上述文件会一直循环进行,期间触发了crash的文件会被记录下来
Fuzzing提升的思路
鉴于Fuzzing整个的流程环节都可以改进,所以有许多关于Fuzzing的论文.
安全四大顶会
CCS(ACM Conference on Computer and Communications Security):链接: https://www.sigsac.org/ccs.html.
NDSS(Network and Distributed System Security Symposium)
网址:https://www.ndss-symposium.org/
Oakland S&P(IEEE Symposium on Security & Privacy)
网址:https://www.ieee-security.org/TC/SP-Index.html
USENIX Security(USENIX Security Symposium)
网址:https://www.usenix.org/
初始种子的选取非常影响Fuzzing的效率,目前学术界方法如下:
第一种是借用机器学习的方法
- 基本思路是从程序的合法输入,网上爬取样本中学出一个模型,再用这个模型生成新的测试例,这样构造的初始种子相对来说更好。典型论文方法包括Skyfire、Learn&Fuzz、GAN、Neuzz等。
第二种是通过符号执行(Symbolic Execution)来辅助
这种辅助手段一般称为混合Fuzzing,其基本思路的核心还是Fuzzing来做,但Fuzzing有些代码过不去,比如一个复杂的数组检查,Fuzzing很难通过。对于这些过不去的分支,Drillers就提出用符号执行来辅助,遇到分支过不去的情况用符号执行来求解,并生成新的种子再丢给Fuzzing去通过分支,这是当时他们做CGC比赛的方案。符号执行和Fuzzing混合确实能提升过不去的分支。最近几年有进一步改进符号执行和Fuzzing的经典方法,比如QSYM、DigFuzz、HFL等。
第三种基于静态分析和动态分析的
还有一些是基于静态分析、动态分析,以及去学习输入的规范,通过程序分析的技术手段去分析程序接受什么样的输入,再去指导测试例的生成。今年张老师他们有一篇针对Android服务的工作,也是这个思路,即FANS(USENIX Sec20)。
还有一部分是针对不同测试目标的工作,包括针对二进制程序的,针对内核程序的,针对JAVA、IoT、SDN的,还有虚拟机、手机驱动、文件系统、数据库、智能音响等等。
在遗传算法每轮的迭代中,它首先需要从现有的种子池(Seed Pool)中选择种子,该步骤也是有一些策略的。因为一个种子池中可能积累了很多种子,通过历史上不断测试留下来的,但每一轮可能只选择一个种子,而先选择哪一个的效率也是不一样的,虽然大家都是种子,但可能有些种子效率更好。
- ####4. Seed Mutation
第一种是偏AI的方法:
第二种是偏程序分析的方法:
通过污点分析来判断应该对哪些字节进行变异