论文:Finding Bugs Using Your Own Code: Detecting Functionally-similar yet Inconsistent Code
今天分享的论文是美国东北大学卢龙教授团队在usenix security2021发的一篇关于机器学习检测漏洞的论文
简介
概率分类在检测已知类型的漏洞取得了很好的效果。但是这些方法需要很大的数据量来训练模型。论文提出了新的基于机器学习的漏洞检测技术。这种技术不需要外部代码或样例来训练。而是从检测漏洞的代码库上训练,然后就不需要去收集和清洗训练数据。
这个技术的关键是对给定的代码库的聚类。聚类过程会去识别工程里的代码片段,这些代码片段功能相似但是以不同的形式出现。这种不一致性就会导致一系列的bug,从缺少检查到不安全的类型转换。不像前面的工作一样,这个技术是通用的,而不是针对某一种bug。
背景
近些年,使用机器学习去检测漏洞的工作还是比较多的。现有的工作基本是按一个思路来做,在一个已知漏洞的数据集上训练模型,然而用训练好的模型去真实环境中检测相似的漏洞。这些工作在检测已知漏洞上具有很高的有效性。然而,这种“learn-from-bug”检测技术面临着两个局限性。
- 需要大量已知漏洞的数据集去训练,收集和清洗数据的工作都比较复杂。
- 模型通常是针对某一种漏洞进行训练和检测的,是bug-specific。而且,不同类型的漏洞检测的准确性还是相差比较多的。
方法
论文提出了FICS,其工作流程如上图所示。
- 首先从给定的代码库中提取代码片段。
- 先对功能相似的片段进行聚类
- 再对实现不一致的进行检测。
- 手工分析,对bug分类
对于这个FICS来说有两个比较难以解决的挑战:
- 找到一个合适的代码粒度去有效地找到功能性和不一致性
- 使方法可以扩展到大型的程序
针对以上问题,论文提出了一种过程内的粒度,命名为Construct,实际上就是可调整大小的过程内数据依赖图的子图。结果表明,这种粒度足够提取出代码相似性和不一致性,而且也足够小可以让聚类算法扩展到大型程序上。
另外,还提出了两种图嵌入技术,一个叫bag-of-nodes,一种用于第一步聚类的粗粒度的嵌入方法。一个叫graph2vec,一个细粒度的图嵌入技术用来第二步聚类。这两种技术能够在不同尺度下准确地比较Construct的相似性和不一致性。
再具体的话,上面的四个步骤可以再细分为9个步骤。具体如下图所示。
(1)首先将C代码编译为LLVM 字节码,然后用过程内的数据流分析去提取每个函数的代码片段的基本操作和计算。
(2)在提取完Constructs后,FICS对Construct抽象为一个通用的形式,进而为后续的聚类做准备。
(3)第一步聚类将功能相似的Construct组在一起,在一个组里去看一致性和其他不同的construct
(4)最后做误差分析,去识别出不一致性指出的bug。人工再去验证,分类漏洞。
结果
主要针对以下四个问题,进行了评估:
- 论文提出的方法检测漏洞有多有效?
- 论文提出的方法是否能够找到未知的漏洞,而不需要人工过多的验证?
- 什么是非漏洞的不一致性?是否需要修复?
- 论文方法的扩展性有多好?
设计了一个对比实验,测试对象是iBench。分别和其他工具APIsan,Crix,LRSan进行对比。
第二步是去针对五个开源软件去发现未知的漏洞。
最后确认了22个漏洞。
另外,还提到了性能方面的评估。在20核,200GB的内存的工作站上只花了5小时跑完了其中三个程序。其他两个OpenSSL和Qemu花了12和72小时去分析。
局限性
由于是从被检测对象的代码库进行学习的,如果代码量比较小,学习的效果就会比较差。但是太大的代码库,比如linux内核,也是处理不了的。因为运行基于图的代码相似度比较需要超过16000的编译单元,而这需要大于200GB的内存空间。
一些小的bug(one-liners)也是解决不了的。因为FICS最小的Construct粒度包含一个完整的基本块,仍然要比这些tiny bug要大得多。
研究的原型不支持C++,因为数据依赖图(DDG)的提取步骤不能处理C++特定的指令,比如涉及到向量或者异常处理的操作。
总结
这篇论文的主要核心点是用聚类的方法来检测漏洞代码,所以不需要给代码打标签啥的。之前就想过漏洞检测相关的数据集比较少,怎么用机器学习来做还是个问题,现在果然有工作是做这块的。看起来效果不错,还发现了新的漏洞。
相比以前的工作针对特定漏洞进行检测,这篇漏洞检测的方法更通用。或许其他相关漏洞的工作也可以借鉴这里头的思路。