SySeVR

SySeVR
foresta.yang摘要
软件漏洞检测目前为止还是一个待被解决的重要问题,因为每天都有很多新的漏洞被发现。使用深度学习方法进行代码漏洞检测是非常有效的,这种方式减轻了对于人为定义特征的要求。尽管深度学习在各个领域取得了巨大的成功,但在漏洞检测领域并没有被研究透彻。为了填补这一空白,本文提出了第一个使用深度学习在C/C++源代码上进行漏洞检测的系统性框架,框架名称叫做SySeVR,全称是“基于语法语义的向量表征”,该框架聚焦于如何获取包含语法和语义信息的代码表征以应用于漏洞检测。该方法检测出了15个没有在NVD中报告过的漏洞,验证了模型的有效性。
简介
假设软件漏洞是不可避免的,那关键的问题是如何更早的发现这些漏洞。基于源代码的静态检测方法包含有基于代码相似性的方法和基于模式的方法,基于代码相似性的方法只能检测与代码克隆相关的漏洞,而基于模式的方法则需要耗费大量的人力去定义模式。因此,最有效的方法是使用深度学习。
之前的VulDeepecker方法是在代码切片层级上进行漏洞检测的,这个方法有4个缺点:1)只能检测与API调用相关的漏洞;2)只包含了语义信息中的数据依赖;3)特征提取模块只用了BLSTM来实现;4)没有解释假阳性和假阴性的原因。本文的SySeVR框架则克服了以上4个缺点。
本文提出SySeVR框架核心是为了解答这个问题——“如何提取代码的向量化表征,该表征包含着适用于漏洞检测的语法和语义信息?”为了回答该问题,本文引入SyVCs(语法漏洞候选)和SeVCs(语义漏洞候选)两个概念,分别表示漏洞的语法特征和语义特征(数据依赖和控制依赖)。同时,本文设计了自动化提取SyVCs和SeVCs的算法。
为了评估SySeVR有效性,本文给出了一个从NVD和SARD中提取出来的包含126种漏洞的数据集,数据集的地址为SySeVR。有了新数据集,SySeVR可以实现以下功能:
- SySeVR验证了多种神经网络模型来进行漏洞检测。BRNN,BGRU比RNN,CNN模型更有效,也比DBN和浅层学习模型更有效。
- BGRU的有效性依赖于训练数据,如果某些语法元素经常出现在代码的漏洞(非漏洞)片段中,那这些语法元素就会导致高的假阳(阴)性率,解释了假阳性率和假阴性率的原因。
- 考虑更多的语义信息(比如控制依赖和数据依赖)可以减少30.4%的假阴性率。
- 在4个软件产品上应用 SySeVR-enabled BGRU模型,检测出了15个没有在NVD中报告过的漏洞。
模型结构
在图像处理领域有一个非常经典的概念叫做region proposal(候选区域),研究者可以从图像中提取出很多的proposal然后向量化,使用深度学习训练检测。对于程序代码而言,我们也可以模仿图像中的操作,利用proposal的思想来完成漏洞检测。
如果使用函数作为proposal,则粒度太高,无法定位具体漏洞位置;如果使用语句作为proposal则会导致正负样本不均衡,且分割了代码语句之间的语义信息。因此,本文采用语句的集合作为proposal,以其为单位进行代码漏洞检测。
首先,我们定义表示漏洞语法特征的SyVCs。下图展示了由region proposal的灵感产生的SySeVR的框架。简而言之,本文依次生成了SyVCs,SeVCs,对SeVCs向量化后使用深度学习进行训练和检测。
总体而言,SyVCs包含了符合某种漏洞语法特征的代码元素(比如函数调用、指针使用)。SeVCs是由SyVCs生成的,在SyVCs的基础上增加了代码的语义信息,它是一部分相互数据依赖和控制依赖的代码语句的集合。下图是一个直观的示例,SyVCs中的每一个红框代表一个SyVC,不同的SyVC可以相互包含,因为其代表不同的漏洞,比如第18行。
首先说说生成SyVCs。SyVCs包含了符合漏洞语法特征的代码元素,比如上图中18行的data就是一次指针使用,因为第9行出现了’*'号说明了data是指针类型。本文借助抽象语法树来实现SyVCs的生成。对于每一个函数,首先生成函数的抽象语法树,抽象语法树的根节点表示函数,叶子节点表示token,中间节点表示语句或者说连续的token。
可用于漏洞检测的代码元素可能是AST的叶子节点或中间节点,因此遍历抽象语法树的节点,如果该节点满足某一条漏洞规则,则该节点对应的一个或多个token将作为code element加入到SyVCs中,具体的伪代码如下。
接下来是将SyVCs转化为SeVCs。本文借助代码切片技术去定义与SyVCs语义相关的语句,在介绍该过程前首先要了解几个概念。
- CFG:表示控制流图,它的点是函数语句,边为有向边,表示相邻语句间的运行先后关系。
- 数据依赖:如果控制流图中有一条A->B的路径,且在A语句中计算得到的值会在B语句中使用,则称B数据依赖A。
- 控制依赖:如果控制流图中有一条A->B的路径,且B是否执行需要看A执行的结果(即B不是post-dominate A), 则称B控制依赖A。
- PDG:表示程序依赖图,它的点是函数语句,边为有向边,表示相邻语句间的数据依赖或控制依赖。
- 前向切片:一个(SyVC)代码元素的前向切片是由一些语句组成的,这些语句包含了在PDG上从该代码元素出发所有可达的点。
- 过程间前向切片:过程间前向切片比前向切片多了一些语句,多的语句是在PDG中代码元素可以通过函数调用到达的点。
- 后向切片:一个(SyVC)代码元素的后向切片是由一些语句组成的,这些语句包含了在PDG上所有与该代码元素可达的、且以该代码元素为终点的点。
- 过程间后向切片:过程间后向切片比后向切片多了一些语句,多的语句是在PDG中可以通过函数调用到达代码元素的点。
- 程序切片:由过程间前向切片和过程间后向切片的语句融合构成,删掉了其中重复的部分。
有了这些概念后,就可以开始生成SeVCs了,SeVCs其实就是与SyVCs中的代码元素相控制依赖和数据依赖的语句的集合,其具体生成的伪代码如下。
首先生成源代码的PDG图,对于PDG中一个的代码元素,生成前向切片和后向切片。然后,融合前向切片和被该函数调用的函数的前向切片,得到过程间前向切片。融合后向切片、被该函数调用的函数的后向切片、以及调用该函数的函数的后向切片,得到过程间后向切片。融合过程间前向切片和过程间后向切片得到程序切片,至此,对于PDG中的该代码元素,生成了其对应的程序切片。
程序切片中的所有函数语句构成一个集合。对于不同的函数的语句而言,调用者的语句在被调用者的语句之前。调整好顺序后,该集合就是该代码元素(SyVC)对应的SeVC了,对SyVCs中的每一个SyVC这样处理,就可以生成SeVCs。下图是一个以25行的data为SyVC的一个转换示例,体现了从PDG到切片到SeVC的完整过程。
有了SeVCs,之后就是对SeVCs的向量化编码操作了。下图为该过程的详细伪代码,在此不一一赘述。简单来说,对于每个SeVC,首先删除不合法字符,对函数和变量名进行映射标准化(V1,V2,F1,F2之类的)。之后,将每个SeVC的每个单词embedding成固定长度的向量,将单词concate到一起。
此时要求concate后的SeVC的向量长度为固定值theta,如果不够就补全;如果超过theta就看SeVC向量两端到SyVC元素有没有小于 1/21/21/2 theta的,有则删去另一端至总长度为theta。如果都没有小于 1/21/21/2 theta的,则两端一起删到两端到SyVC元素都为1/21/21/2 theta。这样就将SeVCs编码为了长度为theta的向量的集合,此时给该集合加上标记,有漏洞的SeVC标签为1,没有漏洞则为0,之后使用双向GRU网络训练检测即可。
实验部分
本文在NVD和SARD两个数据集上进行实验。对于NVD,本文收集了1591个开源C/C程序,其中874个是有漏洞的。对于SARD,本文收集了14000个C/C程序,其中13906个是有漏洞的,这里的有漏洞包含bad和mix,bad指的是有漏洞,mix指的是漏洞版本和修复好的版本都有。合计,本文收集了15591个程序,其中14780个是有漏洞的,对应126种CWE漏洞类型。
实验过程大体上和前文模型结构讲的类似,其中值得注意的是提取SyVCs的时候如何获取漏洞的语法特征并进行匹配。本文通过checkmarx工具提取出了4中类型的漏洞语法规则。
- API/库函数调用(FC):包含了811个函数调用,对应于106种CWE漏洞。
- 数组使用(AU):包含87种CWE漏洞,比如数组元素访问,地址计算等等。
- 指针使用(PU):包含103种CWE漏洞,比如不合法的指针计算、引用、传参。
- 算术表达式(AE):包含45种CWE漏洞,不合法的算术表达式,比如整数溢出等。
下图为这4类漏洞覆盖的CWE漏洞类型。
有了漏洞特征后重点是如何讲代码元素与漏洞特征进行匹配,下图是一个匹配SyVC的示例。
- FC判定需要满足该代码元素是一个函数调用,且属于811种函数调用之中。
- AU判定需要满足这是一个标识符声明语句且包含’[‘和’]’。
- PU判定需要满足这是一个标识符声明语句且包含’*’。
- AE判定需要满足这是一个表达式语句且包含’=’,并且等号右端有两个以上的元素。
从SyVCs转化为SeVCs按算法做就好,本文最终生成SeVCs的结果如下表所示:
在编码阶段使用word2vec进行编码(gensim),每个单词向量长度为30,一个SeVC最多500个单词,theta为15000。
最后说说生成标签,该过程分为两步。第一步是生成初始的标签,针对NVD数据集,如果一个SeVC包含的删除或修改的语句前面有’-’,则被标记为1(有漏洞);而如果一个SeVC包含的移动的语句前面有’-’,且这个文件包含一个已知的漏洞,则被标记为1;其他情况都标记为0。针对SARD数据集,如果一个SeVC提取自一个good程序,则被标记为0;如果提取自bad或mix程序,则分情况,若该SeVC包含至少一个漏洞语句则标记为1,否则标记为0。
接下来第二步,使用交叉验证的方式来修正标签。比如将数据集分为5份,4份训练,1份测试。在测试过程中发现的假阴性样本(有漏洞的样本检测为无漏洞)将会被考虑是否标错了,对于这类样本手动检查修正标签。
接下来就是最后的模型结果。本文的实验结果旨在说明四个问题:
- 1:SySeVR搭配BLSTM可以检测多种类型的漏洞吗?
- 2:SySeVR可以搭配各种各样的神经网络来进行漏洞检测吗,检测效果如何?
- 3:考虑控制依赖是否使得SySeVR更加有效,结果好了多少?
- 4:SySeVR相比现有的SOTA方法如何?
对于第一个问题,作者将SySeVR-BLSTM模型分别检测4种类型的漏洞,于VulDeepecker模型的结果进行比较,结果如下图所示,可见SySeVR-BLSTM模型的结果明显优于VulDeepecker模型,证明了该模型可以有效的检测多种不同类型的漏洞。
对于第二个问题,本文比较了使用LR,MLP,DBN,CNN,LSTM,GRU,BLSTM,BGRU等神经网络进行训练和检测的效果,发现使用BGRU对SeVCs进行训练检测的效果最好。RNNs比CNN更好,CNN比浅层网络更好,假阴性率高于假阳性率(没检测出来的偏多)。
对于第三个问题,本文分别列举了在各个模型上只使用数据依赖(DD)和同时使用数据依赖控制依赖(DDCD)的模型结果,结果如下所示,可见控制依赖可以很大程度上提升模型的效果。
对于第四个问题,将本文的SySeVR-BGRU模型与Flawfinder,RATS,checkmarx,VUDDY,VulDeepecker进行比较,可见SySeVR-BGRU模型显著强于之前的所有模型。
总结
本文提出了一个叫做SySeVR的基于深度学习的代码漏洞检测框架,该模型提取待检测代码的语法和语义特征并应用于漏洞检测。大量的实验证明了SySeVR模型的有效性,本文使用SySeVR模型检测出NVD中15个未被报道过的漏洞。