本论文相关内容
- 论文下载地址——Engineering Village
- 论文中文翻译——VulDeePecker: A Deep Learning-Based System for Vulnerability Detection
- 论文阅读笔记——VulDeePecker: A Deep Learning-Based System for Vulnerability Detection
文章目录
前言
本文为VulDeePecker: A Deep Learning-Based System for Vulnerability Detection论文的精读笔记,本论文在读完之后学习到了很多知识,同时针对文中的一些步骤或者不易理解的知识点,给出了自己的一些总结。当然,由于本人水平有限,又由于是第一次写论文精读笔记,所以肯定有很多不足,希望读者可以指出,我一定会加以改正!后面还有带来更多的论文精读笔记,目前规划一周一篇英文论文,所以还请读者多多关注这个系列。下面就正式开始阅读今天的论文精读笔记!
VulDeePecker:基于深度学习的漏洞检测系统
摘要
本文在摘要部分首先介绍了现如今对于软件漏洞的自动检测的问题。现在往往是通过人类专家来定义漏洞的特征,然后在去检测软件是否有这些漏洞的特征去判断是否有漏洞。这样的方法不仅仅对人的依赖程度较大,从而导致工作量非常大,而且由于人类专家的主观性等还会遗漏很多漏洞的特征。而本文介绍的方法就是将人类专家从这样繁琐和主观的任务中解脱出来,使用深度学习去代替人类专家从而实现低误报率的漏洞检测系统。在摘要中,还简单介绍了本文所描述方法的原理和过程,最终给出了和其他漏洞检测系统的对比实验结果,发现本文介绍的VulDeePecker都成功的识别了这些漏洞,并且保证了低误报率和低漏报率。
I. 引言
在引言部分,首先介绍了目前的网络攻击现状,指出了许多的网络攻击都源于软件的漏洞,那么也就意味着,我们需要如何检测到这些软件漏洞是当前的重要课题。当然,现在也有很多的漏洞检测方案,但是本文也对这些漏洞检测方案的不足进行了介绍,这些不足主要包括两个主要缺点:
- 繁重的体力劳动
- 较高的误报率
在介绍这两个缺点之前,我对漏洞检测方案的一些评测概念进行了学习和整理。首先定义两个概念,阳性(正)和阴性(负),我们将阳性样例定义为 P P P(Positive),阴性样例定义为 N N N(Negative)。那么我们再进行漏洞检测时,我们做以下几个定义:
- 将正样本检测为正样本定义为:True Positive(TP)
- 将正样本检测为负样本定义为:False Negativ(FN)
- 将负样本预测为正样本定义为:False Positive(FP)
- 将负样本预测为负样本定义为:True Negative(TN)
所以,显然有:
P
=
T
P
+
F
N
N
=
F
P
+
T
N
\begin{array}{l} P=T P+F N \\ N=F P+T N \end{array}
P=TP+FNN=FP+TN
那么根据这几个基础概念,我们给出对于本文漏洞检测方案的一些评价公式,整理如下:
-
准确率(Accuracy)
对于准确率我们又可称为正确率。这个最好理解,也就是字面意思,也就是我们的漏洞检测系统对于整体样本判断正确的能力。即将 阳性(正)样本判定为Positive和阴性(负)样本判定为Negative的正确分类能力 。准确率的值越大,说明此漏洞检测系统的性能越好。准确率的评测公式如下所示:
A c c u r a c y = T P + T N T P + T N + F P + F N Accuracy=\frac{T P+T N}{T P+T N+F P+F N} Accuracy=TP+TN+FP+FNTP+TN
-
精确率(Precision)
精确率容易和准确率搞混,但是这是两个概念。精确率针对的是漏洞检测系统正确检测正样本的能力,即检测的正样本中有多少是真实的正样本。可见准确率是针对全部检测,精确率只是针对正样本检测。精确率的值越大,说明漏洞检测系统的性能越好。精确率的评测公式如下所示:
P r e c i s i o n = P = T P T P + F P Precision=P=\frac{T P}{T P+F P} Precision=P=TP+FPTP -
误报率(FalseAlarm)
误报率又可以称为假阳率、虚警率、误检率。通过名字就能知道,误报率描述的是漏洞检测系统错误预测漏洞的评价指标。即负样本被预测为正样本占总的负样本的比例。所以显然,误报率越小,说明漏洞检测系统的检测能力越强。误报率的评测公式如下所示:
F a l s e A l a r m = F P R = F P F P + T N = T P N FalseAlarm=FPR=\frac{F P}{F P+T N}=\frac{T P}{N} FalseAlarm=FPR=FP+TNFP=NTP -
漏报率(MissRate)
漏报率也可以称为漏警率、漏检率。漏报率即漏洞检测系统将正样本检测为负样本的所占总的正样本的比例。那么很明显,漏报率的值越小,说明漏洞检测系统的检测能力越强。漏报率的评测公式如下所示:
M i s s R a t e = F N R = F N T P + F N = F N P MissRate=FNR=\frac{F N}{T P+F N}=\frac{F N}{P} MissRate=FNR=TP+FNFN=PFN
当我们对这几个基本概念有所了解后,我们来看本文对于目前的漏洞检测方案的一些看法。首先本文认为,现有的漏洞检测解决方案主要依赖于人类专家来定义特征,所以受人类专家主观倾向的制约,并且工作十分繁琐;其次,本文对于目前的漏洞检测系统的主要缺点做了简要概述,本论文认为现在的漏洞检测系统的误报率以及漏报率很高,或者只兼顾了其一,而不能同时达到低误报率和低漏报率。
那么本文的研究方向也基于这两点,首先要考虑的就是将人类专家从繁重的工作中解脱出来,并且到达低误报率和低漏报率。所以就引出了本文所着重介绍的方法:基于深度学习漏洞检测系统的设计和实现,此漏洞检测系统又称为Vulnerability Deep Pecker(VulDeePecker)。
然后本文作者又针对本方法详细的论述了针对此问题做出的三项贡献。
-
首先作者对于如何使用深度学习方法用于漏洞检测给出了一些论断,对于深度学习方法,可以使漏洞检测自动化,并不需要人类专家手动定义特征。但是,深度学习方法并不是为漏洞检测应用而生的,所以要确定一些基本原则以便可以将深度学习方法应用于漏洞检测:其中最重要的就是选择可以应用于漏洞检测的特定神经网络。并且使用代码小工具表示程序,使其在语义上相互关联,还可以作为深度学习的输入从而进行向量化。
这里给出向量化的含义:因为在深度学习中,往往需要处理很多数据并且进行很多轮的迭代,那么为了提高迭代速度,那么自然就使用到了矩阵进行运算,这也是数据向量化的由来。
-
其次,作者对于VulDeePecker的有效性进行了评估。主要从以下三个方面进行了有效性的评估
- VulDeePecker能否同时处理多种类型的数据?
- 人类专家的知识能否提高VulDeePecker的效率?
- 对比其他漏洞检测方法,VulDeePecker的有效性如何?
针对以上问题,作者对VulDeePecker的有效性进行了实验,发现VulDeePecker的效率非常高,可以检测到尚未识别的漏洞,还包括0天漏洞,这足以证明VulDeePecker的有效性。
这里给出0天漏洞的含义: 0day漏洞,又称“零日漏洞”(zero-day),是已经被发现(有可能未被公开),而官方还没有相关补丁的漏洞。通俗地讲就是,除了漏洞发现者,没有其他的人知道这个漏洞的存在,并且可以有效地加以利用,发起的攻击往往具有很大的突发性与破坏性。
-
最后,因为现在没有现成的数据集来对VulDeePecker进行测试,所以作者评估VulDeePecker和其他未来将开发的基于深度学习的漏洞检测系统的数据集。此数据集已经提供在以下位置了:https://github.com/CGCL-codes/VulDeePecker。
在引言的最后,本文给出了论文其余部分的组织结构,如下所示:
- 第二节:介绍了基于深度学习的漏洞检测的一些初步指导原则。
- 第三节:讨论了VulDeePecker的设计。
- 第四节:描述了作者对VulDeePecker的实验评估和结果。
- 第五节:讨论了VulDeePecker的局限性和未来研究的开放问题。
- 第六节:描述了相关的前期工作。
- 第七节:总结了本论文。
II. 基于深度学习的漏洞检测指导原则
在本部分,本文主要给出了使用深度学习应用到漏洞检测系统的一些指导原则,因为深度学习并不是为漏洞检测而生的,所以确定这些指导原则至关重要。本文主要确定了以下三项指导原则:
- 指导原则1:程序可以首先转换为一些中间表示,可以保留程序元素之间的(部分)语义关系(例如,数据依赖性和控制依赖性)。然后,中间表示可以转换为向量表示,即神经网络的实际输入。
- 指导原则2:为了帮助确定漏洞的位置,应该以更细的粒度表示程序,而不是将程序或函数作为一个单元来处理。
- 指导原则3:由于一行代码是否包含漏洞可能取决于上下文,因此可以处理上下文的神经网络可能适合于漏洞检测。
其中,指导原则1和指导原则2主要确定了我们待检测漏洞程序的一些特征,比如保留程序元素之间的语义关系以及将程序以更细粒度表示。这些都是为了提高漏洞检测的效率和准确性。而其中最重要的就是指导原则3。因为我们该如何使用深度学习去进行漏洞检测才是我们要确定的问题,因为现在的深度学习有很多框架,我们应该选择哪种才最适合我们呢?本文的指导原则3指出:由于一行代码是否包含漏洞可能取决于上下文,因此可以处理上下文的神经网络可能适合于漏洞检测。 所以说我们可以使用自然语言处理的神经网络应用于漏洞检测。
但是某些应用与自然语言处理的神经网络由于各种问题也不适合与漏洞检测,比如本文提到的Vanishing Gradient(梯度消失)问题。那么应该选择不受Vanishing Gradient(VG)问题影响的神经网络。
这里给出Vanishing Gradient(梯度消失)问题的含义: 在反向传播过程中需要对激活函数进行求导,如果导数小于1,那么随着网络层数的增加梯度更新信息会朝着指数衰减的方式减少这就是梯度消失。
我们可以借助图片来理解梯度消失问题,比如我们在神经网络中常用的 S i g m o i d Sigmoid Sigmoid 函数:
在神经网络的训练或预测中,因为反向传播的过程,所以要对 S i g m o i d Sigmoid Sigmoid 函数求导,那么 S i g m o i d Sigmoid Sigmoid 函数的导数的图像如下图所示:
我们可以看到当 x x x 趋近于无穷大的时候,导数趋近于0。这样经过链式求导之后很容易造成梯度消失问题。 这样会使训练的模型无效。
为了避免这种情况发生,本文经过比较后,选择了长短期记忆网络(LSTM)来进行漏洞检测**( 注:因为转换转移中使用的是加法,而不是乘法。因为求导后,如果两个数比较小,相乘后会更小。但是加法不会剧烈改变数据返回的大小,适合用来传递状态。这也是因为能够保存状态,缓解梯度消失的原因。)**。但是单向的(LSTM)也不足以胜任现实中的漏洞检测,因为程序函数调用的参数可能会受到程序中前面语句的影响,也可能会受到后面语句的影响。所以基于这个问题,本文最终采用双向长短期记忆网络(BLSTM)来进行漏洞检测,下图为(BLSTM)的示意图:
双向长短期记忆网络(BLSTM)其中最重要的特点就是,(BLSTM)层有两个方向,向前和向后。这样就可以处理上下文信息,便于更准确的识别漏洞。当然(BLSTM)层中包含了很多复杂的(LSTM)核,在本文中被视为黑盒。输入的向量首先经过(BLSTM)层,然后使用密集层减少接收到的矢量维数,最终softmax层接收密集层的低维数据,最后进行格式化分类和表示结果。对于LSTM和BLSTM我会放在附录中进行详细介绍。
III. VulDeePecker的设计
此部分内容主要是作者对于VulDeePecker的设计过程的介绍,这也是本文的核心内容,下面将对以下内容进行精读。
A. 定义代码小工具
有的读者可能不理解代码小工具是什么东西,其实就是将我们的程序分割为一段一段的小程序,也就是说:代码小工具是由许多程序语句(即代码行)组成,这些语句在语义上是相关的,也就是说有联系。
我个人理解这个代码下工具的作用就是:对于同种语义的代码段进行分类(分段),这样我们可以将其进行向量化,作为深度学习的输入数据,经过大量训练后可以得到我们想要的结果。因为我们是按照语义分类的,所以说可以同时检测多种漏洞,这也是为了解决本文之前提出的问题。
B. VulDeePecker概述
在这部分主要介绍了构建VulDeePecker的两个阶段:学习(训练)阶段和检测阶段,说白了就是将数据分为训练集和数据集,经过训练后得到模型,对这个模型的有效性进行评估。
-
学习阶段
学习阶段主要包括四个步骤,如下图所示:
-
第一步:提取库/API函数调用和相应的程序切片。
因为VulDeePecker主要是针对库/API函数的源码漏洞分析,所以我们使用的数据集也来自于库/API函数。当我们提取到这些信息后,我们训练的基础数据也就有了,就可以表示程序关于程序点或变量的语句。
-
第二步:生成训练程序的代码小工具及其基本真实标签。
将第一步获得的程序切片根据语义组装为代码小工具。要注意的是,这里划分的代码小工具不一定是连续的代码行,而是由语义上相互关联的多行代码组成,对于这点前面已有介绍。然后按照即易受攻击标记为“1”或不易受攻击的“0”的规律进行标记,其实也就是标记哪些代码小工具存在漏洞,而哪些代码小工具不存在漏洞。
-
第三步:将代码小工具转换为矢量表示。
这一步的主要作用就是将我们划分好的代码小工具转换为向量表示,作为训练数据喂给神经网络。
-
第四步:训练BLSTM神经网络。
这一步就是使用获取到的训练集,对BLSTM神经网络进行训练以得到高有效性的模型。
-
训练阶段
训练阶段主要包括两个步骤:如下图所示
-
第五步:将目标程序转换为代码小工具和向量。
这一步的主要作用就是将我们待检测的源码按照划分训练集的模式转换为代码小工具以及语义向量。因为训练的时候数据是这种格式,所以测试的时候也要以这种格式输入数据。
-
第六步:检测。
本步也是构建VulDeePecker的最后一步,也就是将我们的真实源码作为VulDeePecker的输入,从而得到检测结果。按照之前的定义:当向量被分类为“1”(即易受攻击)时,这意味着相应的代码小工具易受攻击,并且漏洞的位置被锁定。否则,相应的代码小工具被归类为“0”(即不易受攻击)。
C. 准备神经网络训练的数据
-
提取库/API函数调用和相应的程序切片。
- 步骤I.1:首先将库/API功能调用分为两类:前向库/API调用和后向库/ADI函数调用。目的是利用上下文信息让LSTM核做出是否存在漏洞的判断。比如,recv函数调用是向前库/API函数调用,因为它直接从套接字接收数据。而对于后向库/ADI函数调用,例如向后库/API函数调用strcpy的示例(第9行)如下图所示,它不直接接收任何外部输入。
-
步骤I.2:在这一步我们需要提取程序切片,其中包括向前切片和向后切片,提取切片的过程也可以通过图6来理解。对于提取切片的思路基本如下
- 对于前向库/API函数调用中的每个参数,将生成一个或多个前向切片,后者对应于与参数相关的切片在库/API功能调用处或之后分支的情况。
- 对于后向库/API函数调用中的每个参数,都会生成一个或多个后向切片,后一个切片对应于在库/API功能调用时或之前合并与该参数相关的多个切片的情况。
-
提取库/API函数调用和相应的程序切片。
- 步骤II.1:首先将程序切片组装成代码小工具,在这一步有三个原则要把握好:
- 如果在一个代码片段中,存在重复的语句,会被消除重复。
- 如果某两条语句之间已经存在顺序,则保留此顺序;否则,使用随机顺序。
- 代码小工具中的代码语句不一定是连续的。
- 步骤II.2:在这步我们就要对我们上面组装的代码小工具打标签了,方便后面对神经网络进行训练。每个代码小工具都需要按照以下原则标记:
- 易受攻击标记为1。
- 不易受攻击标记为0。
- 步骤II.1:首先将程序切片组装成代码小工具,在这一步有三个原则要把握好:
-
将代码小工具转换为向量
当我们进行到这步的时候,已经获取到了足够的训练数据,但是如何将训练数据当作神经网络的输入呢?最好的办法就是将代码小工具转换为向量,也就是将代码小工具转换为其对应的有意义的向量,这样不仅可以使训练数据规范化,还能保持代码语句之间的关联关系。在这步主要包括两个子步骤:
-
步骤III.1:将代码小工具转换为符号表示。其意义在于启发性地在训练神经网络的程序中捕获一些语义信息。在转换的过程中要遵循三项原则:
- 删除非ASCII字符和注释,因为它们与漏洞无关。
- 以一对一的方式将用户定义的变量映射到符号名称(例如,“VAR1”、“VAR2”)。
- 以一对一的方式将用户定义的函数映射到符号名称(例如,“FUN1”、“FUN2”)。
这个过程的示意图如下所示:
-
-
步骤III.2:将符号表示编码为向量。这里通过举一个例子说明这个过程。例如,符号表示中的代码小工具,
“ s t r c p y ( V A R 5 , V A R 2 ) ; ” “strcpy(VAR5, VAR2); ” “strcpy(VAR5,VAR2);”
由7个标记序列表示:
“ s t r c p y ” , “ ( ” , “ V A R 5 ” , “ , ” , “ V A R 2 ” , “ ) ” , 和 “ ; ” . “strcpy”, “(”, “VAR5”, “,”, “VAR2”, “)”, 和 “;”. “strcpy”,“(”,“VAR5”,“,”,“VAR2”,“)”,和“;”.
上面的过程就是将代码划分标记的过程,但是这导致了大量的语料库的标记。为了将这些标记转换为向量,作者使用了word2vec工具,因为word2vec工具它广泛用于文本挖掘。该工具基于分布式表示的思想,将令牌映射为整数,然后将其转换为固定长度向量。这里还有一个问题需要注意:由于代码小工具可能具有不同数量的标记,因此相应的向量可能具有不同的长度。由于BLSTM采用等长向量作为输入,所以我们要对其进行调整,作者在这里引入一个参数 τ τ τ作为与代码小工具相对应的向量的固定长度。那么对于向量大于或者小于 τ τ τ时我们该如何处理呢?作者给出了两种处理原则:
- 当向量小于 τ τ τ时,有两种情况:如果代码小工具是从后向切片生成的,或者是通过组合多个后向切片而生成的,我们在向量的开头填充零;否则,我们将零填充到向量的末尾。
- 当一个向量大于 τ τ τ时,也有两种情况:如果代码小工具是由一个反向切片生成的,或由多个反向切片组合生成的,我们删除向量的开始部分;否则,删除向量的结束部分。
这确保了从向后切片生成的每个代码小工具的最后一条语句是库/API函数调用,而从向前切片生成的所有代码小工具中的第一条语句是一个库/API功能调用。因此,每个代码小工具都表示为 τ τ τ位向量。
IV. 实验和结果
在这部分作者主要针对实验过程和实验结果进行了一个总结,但是篇幅太多,作为论文精读,抽取其中有价值的核心部分进行整理。首先作者针对实验过程进行了简要的描述,最后作者针对实验结果回答了论文开篇提出的三个问题,并通过实验测试了VulDeePecker的有效性。
A. 实验过程
-
评估漏洞检测系统的指标
对于这部分内容,在此不再赘述,因为已经被我总结到论文精读笔记的开始部分了。我们只需要知道,在本研究中,作者倾向于实现低 F N R FNR FNR和低 F P R FPR FPR。
-
准备VulDeePecker的输入
- 收集程序:这里也就是收集原始数据集,本文重点将漏洞类型放在缓冲区错误漏洞和资源管理错误漏洞。使用的开源产品都是C/C++语言。数据集主要来自NIST。
- 训练程序对比目标程序:这部分就是正常我们进行深度学习中划分数据集的部分,其中随机选择80%的程序作为训练数据,剩下的20%作为目标(测试)数据。
-
学习BLSTM网络
-
第一步:提取库/API函数调用和相应的程序切片。
在此部分作者主要介绍了提取的数据集,并且说明了提取数据集的类型。主要包括缓冲区错误漏洞和资源管理错误漏洞
-
第二步(1):生成代码小工具
在这部分作者主要介绍了如何将获取的程序数据集划分为代码小工具,其中主要使用了六种数据集,并且将每个代码小工具划分为了存在漏洞的代码小工具和不存在漏洞的代码小工具,这样就可以划分训练集和数据集以便后面神经网络进行训练。对于数据集的划分如下所示:
表I. 回答RQS的数据集
-
-
第二步(2):标记代码小工具
①:如果代码小工具至少包含一条根据修补程序删除或修改的语句,则会自动标记为“1”(即易受攻击),否则会标记为“0”(即不易受到攻击)。
②:手动检查标记为“1”的代码小工具,以便更正错误标签(如果有)。
-
第三步:将代码小工具转换为向量
此部分主要介绍了将上面标记好的代码小工具转换为神经网络进行训练的向量,主要是将其进行符号化表示。
-
第四步:训练BLSTM神经网络
在这部分最重要的内容就是训练模型的参数,以及通过对比具有不同隐藏层个数的神经网络来找到最佳参数。最终发现:当隐藏层个数设置为2或3层时,VulDeePecker的效果最好。当然,还有一些其他参数需要关注,比如代码小工具矢量表示中的令牌数设置为50,dropout设置为0.5,批量大小设置为64,周期数设置为4,使用小批量随机梯度下降和ADAMAX进行训练,默认学习率为1.0,并选择300个隐藏节点。 下图是具有不同隐藏层个数的6个数据集的VulDeePecker的 F 1 F1 F1度量
B. 实验结果
在此部分,作者主要回答了论文开篇提出的三个问题:
- VulDeePecker能否同时处理多种类型的数据?
- 人类专家的知识能否提高VulDeePecker的效率?
- 对比其他漏洞检测方法,VulDeePecker的有效性如何?
-
问题一:VulDeePecker能否同时处理多种类型的数据?
作者在三个数据集上进行了实验:BE-ALL、RM-ALL和HY-ALL。这分别生成三个神经网络,其有效性见下表。
表II. 回答RQ1的结果表明,VULDEEPECKER可以检测多种类型的漏洞。
同时作者针对在HY-ALL和HY-SEL数据集上模型的时间复杂度做了总结,如下表所示。
所以针对问题1,作者回答:VulDeePecker可以同时检测多种类型的漏洞,但其有效性对与漏洞相关的库/API函数调用数量敏感(即越少越好)。
- 人类专家的知识能否提高VulDeePecker的效率?
为了回答问题2,作者使用自动提取的函数调用和使用人类专家编写的漏洞规则进行对比试验,其中HY-SEL数据集使用人类专家编写的漏洞规则,而HY-ALL数据集使用自动提取的函数调用。试验结果如下图所示。
所以,针对问题2,作者回答:可以使用人类专业知识来选择库/API函数调用,以提高VulDeePecker的有效性,尤其是 F 1 F1 F1度量值的整体有效性。
- 对比其他漏洞检测方法,VulDeePecker的有效性如何?
为了回答问题3,作者将VulDeePecker与其他基于模式和基于代码相似性的漏洞检测系统的有效性进行了比较。最终的实验结果如下表所示。可以看到VulDeePecker大大优于其他基于模式的漏洞检测系统,因为既保证了低 F P R FPR FPR又保证了低 F N R FNR FNR。
所以,对于问题3,作者回答:利用数据流分析,基于深度学习的漏洞检测系统可以更加有效。并且VulDeePecker比基于代码相似性的漏洞检测系统更有效,后者无法检测非代码克隆引起的漏洞,因此通常会导致较高的误报率。然而,VulDeePecker的有效性对数据量很敏感,这似乎是深度学习的本质所固有的。
C. 实践测试
由上面的实验结果可以看到,VulDeePecker的性能远远高于传统的漏洞检测系统,但是在实践中并没有进行过实验,所以作者又在真实环境中测试了VulDeePecker。最终的测试结果如下图所示。
可以看到,VulDeePecker不仅在测试环境中展示出了良好的性能,同时在生产环境中也表现出了强劲的竞争力。
V. 局限性
每个软件或系统都有其局限性,那么作者在此处也罗列出了关于VulDeePecker的一些局限性,对于这些局限性,也是未来研究的一个方向,对于如何解决这些问题,仍是一个有趣而有挑战性的问题。
- 第一:VulDeePecker目前的设计只能用于检测源程序代码中是否存在漏洞的情况,而不能检测可执行文件中是否存在漏洞。
- 第二:VulDeePecker目前只能处理C/C++程序,而不适用于其他编程软件。
- 第三:VulDeePecker目前仅能处理与库/API函数调用相关的漏洞,而不能处理其他类型的漏洞。
- 第四:VulDeePecker目前仅适用于数据流分析(即数据依赖),而不适用于控制流分析(如控制依赖)。
- 第五:VulDeePecker目前使用的启发式方式在未来仍需进一步研究对于VulDeePecker有效性的影响。
- 第六:VulDeePecker目前的实现仅限于BLSTM神经网络,未来应使用其他类型的神经网络进行实验。
- 第七:VulDeePecker目前的数据集仅包含缓冲区错误漏洞和资源管理错误漏洞,而没有包含所有的漏洞类型。并且没有检测到0天漏洞,虽然这在原理上可行。
VI. 相关工作
在这部分作者主要对设计VulDeePecker的前期工作做了总结,其中主要包括两点:
A. 漏洞检测方面的前期工作
关于漏洞检测的前期工作包括两点:基于模式的方法和基于代码相似性的方法。下面对这两种方法的概念根据论文内容进行总结。
- 第一种方法为基于模式的方法,也就是根据漏洞的模式进行漏洞检测,具体又分为三类:
- 第一类:由人类专家手动生成的模式,具有较高的假阳性率或者假阴性率。
- 第二类:根据预先分类的漏洞半自动生成模式,模式特定于一种类型的漏洞。
- 第三类:由不可知的漏洞半自动生成模式,使用机器学习技术,依赖于人类专家定义特征来描述模式,并且无法准确确定漏洞的精确位置。
- 第二种方法是基于代码相似性的方法:也就是根据源码之间的上下文关系以及语义上的相似性进行漏洞检测,具体又分为三步:
- 第一步:将程序划分为一些代码片段。
- 第二步:以抽象的方式表示每个代码片段。
- 第三步:通过在第二步中获得的抽象表示来计算代码片段之间的相似性。
B. 与使用深度学习进行程序分析相关的前期工作
因为本文是第一个使用深度学习进行漏洞检测的论文,之所以使用深度学习进行漏洞检测,是受到了深度学习在图像处理、语音识别、和自然语言处理方面的启发。对于如何将程序进行向量化表示以作为深度学习的训练以及验证,本文提到了两种方法:
- 第一种:将程序中提取到的标记(如数据类型、变量名、函数名和关键字)映射到向量
- 第二种:将程序中提取到的抽象语法树的节点(例如函数定义、函数调用、标识符声明和控制流节点)映射到向量。
最后本文在这部分还提到了软件缺陷与软件漏洞是不同的,也就是说检测缺陷的方法通常不能用于检测漏洞。原因有两点:
- 缺陷检测中程序的文件级表示过于粗糙,无法确定漏洞的位置。
- 缺陷检测方法面向代码更改,而不是整个目标程序。
VII. 结论
在本文的最后作者给出了关于VulDeePecker:基于深度学习的漏洞检测系统的总结性概括。首先解决了两个问题:
- 将人类专家从手动定义特征和主观工作中解脱出来。
- 减少漏洞检测的误报。
然后对于VulDeePecker的一些贡献进行了总结:
- 确定了使用深度学习应用于漏洞检测系统的一些指导原则。
- 收集并公开了一个有用的数据集用以评估VulDeePecker和其他漏洞检测系统的有效性。
而后作者通过对比实验发现VulDeePecker的漏洞检测性能远高于其他漏洞检测系统,因为VulDeePecker发现了国家漏洞数据库没有报告的漏洞,而其他漏洞检测系统都漏掉了这些漏洞。最后作者对于未来关于此研究的开放性问题以及当前研究的局限性持乐观的态度。
附录
A. LSTM
在这部分作者主要针对LSTM核进行了简单的介绍,正好借着这个机会,对LSTM和BLSTM进行一个简单的补充说明。
首先介绍LSTM,又称为长短期记忆网络,其中LSTM网络包括三个“门”:输入门、输出门、遗忘门。一个LSTM网络中包括很多LSTM核,在介绍LSTM网络之前,先介绍LSTM核,因为LSTM网络是由LSTM核构成的,关于LSTM核的示意图如下所示:
这里首先介绍几个概念。在上面图中,设 ⊙ \odot ⊙表示以元素方式相乘, t a n h tanh tanh表示双曲正切函数 exp ( x ) − exp ( − x ) exp ( x ) + exp ( − x ) \frac{\exp (x)-\exp (-x)}{\exp (x)+\exp (-x)} exp(x)+exp(−x)exp(x)−exp(−x), σ σ σ表示 s i g m o i d sigmoid sigmoid函数 1 1 + exp ( − x ) \frac{1}{1+\exp (-x)} 1+exp(−x)1。每个LSTM核(用 c c c表示)使用输入门 i i i(即输入数据)、遗忘门 f f f(即单元的状态流)和输出门 o o o(即模块的输出)来控制通过神经网络的数据流。
- 遗忘门 f f f:记忆细胞状态,选择性遗忘记忆细胞中的信息。也就是选择对训练有用的数据留下,对训练没用的数据丢弃。
- 输入门 i i i:将新的信息选择性的记录到新的细胞状态中。
- 输出门 o o o:将运算结果更新到下一个隐层。
在每次运算中,对于如何计算每一次的状态和输入输出结果公式一一列举如下,这个可以结合上面的示意图理解:
-
l l l层在 t t t时刻的输出 h t l h_{t}^{l} htl为:
h t l = o t l ⊙ tanh ( c t l ) , h_{t}^{l}=o_{t}^{l} \odot \tanh \left(c_{t}^{l}\right), htl=otl⊙tanh(ctl), -
时间 t t t时层 l l l的输出门 o t l o_{t}^{l} otl为:
o t l = σ ( W x o l x t l + W h o l h t − 1 l + W c o l c t l + b o l ) o_{t}^{l}=\sigma\left(\boldsymbol{W}_{x o}^{l} x_{t}^{l}+\boldsymbol{W}_{h o}^{l} h_{t-1}^{l}+\boldsymbol{W}_{c o}^{l} c_{t}^{l}+b_{o}^{l}\right) otl=σ(Wxolxtl+Wholht−1l+Wcolctl+bol) -
时间 t t t时层 l l l的LSTM核 c t l c_{t}^{l} ctl的状态为:
c t l = f t l ⊙ c t − 1 l + i t l ⊙ tanh ( W x c l x t l + W h c l h t − 1 l + b c l ) . c_{t}^{l}=f_{t}^{l} \odot c_{t-1}^{l}+i_{t}^{l} \odot \tanh \left(\boldsymbol{W}_{\boldsymbol{x c}}^{l} \boldsymbol{x}_{t}^{l}+\boldsymbol{W}_{\boldsymbol{h c}}^{l} \boldsymbol{h}_{t-1}^{l}+\boldsymbol{b}_{c}^{l}\right). ctl=ftl⊙ct−1l+itl⊙tanh(Wxclxtl+Whclht−1l+bcl). -
时间 t t t时层 l l l的遗忘门 f t l f_{t}^{l} ftl和输入门 i t l i_{t}^{l} itl计算如下:
f t l = σ ( W x f l x t l + W h f l h t − 1 l + W c f l c t − 1 l + b f l ) , i t l = σ ( W x i l x t l + W h i l h t − 1 l + W c i l c t − 1 l + b i l ) , \begin{aligned} f_{t}^{l} &=\sigma\left(\boldsymbol{W}_{x f}^{l} x_{t}^{l}+W_{h f}^{l} h_{t-1}^{l}+W_{c f}^{l} c_{t-1}^{l}+b_{f}^{l}\right), \\ i_{t}^{l} &=\sigma\left(W_{x i}^{l} x_{t}^{l}+W_{h i}^{l} h_{t-1}^{l}+W_{c i}^{l} c_{t-1}^{l}+b_{i}^{l}\right), \end{aligned} ftlitl=σ(Wxflxtl+Whflht−1l+Wcflct−1l+bfl),=σ(Wxilxtl+Whilht−1l+Wcilct−1l+bil),
其中 x t l x_{t}^{l} xtl是 l − 1 l− 1 l−1层 ( l > 1 ) (l>1) (l>1)的输入或网络输入 ( l = 1 ) (l=1) (l=1), W x i l W_{x i}^{l} Wxil, W x f l W_{x f}^{l} Wxfl, W x o l W_{x o}^{l} Wxol, W x c l W_{x c}^{l} Wxcl是连接 x t l x_{t}^{l} xtl与输入门、遗忘门、输出门和LSTM核输入的权重矩阵, W h i l W_{h i}^{l} Whil, W h f l W_{h f}^{l} Whfl, W h o l W_{h o}^{l} Whol, W h c l W_{h c}^{l} Whcl是连接 h t − 1 l h_{t-1}^{l} ht−1l的带有输入门、遗忘门、输出门和LSTM核输入的权重矩阵, b i l b_{i}^{l} bil, b f l b_{f}^{l} bfl, b o l b_{o}^{l} bol, b c l b_{c}^{l} bcl是输入门、遗忘门、输出门和LSTM核输入的偏置项。
以上就是一个简单LSTM核的运算过程。通过以上公式,我们就能利用上下文信息进行自然语言处理,以及对于漏洞的检测。若干LSTM核就构成了整个LSTM网络,关于LSTM网络的简图如下所示:
B. BLSTM
虽然LSTM可以帮我们完成一部分工作,但是对于我们漏洞检测的实际应用情况,我们不仅需要上文的信息,还需要下文的信息进行辅助分析。所以我们要使用BLSTM进行上下文分析,从而使漏洞检测的有效性更高。如果理解了LSTM,那么对于BLSTM也就好理解了,因为BLSTM也是由若干个LSTM核组成的,只是多了一个反向传播的过程,下面为BLSTM的网络结构,整个网络构成了一个无环图,每次的输出都需要考虑前后的因素,这样更适合利用上下文信息进行漏洞检测,同样使结果更具有鲁棒性。
C. Checkmarx选择的库/API函数调用
在这部分,作者总结了与两类漏洞(缓冲区错误(CWE119)和资源管理错误(CWE-399))相关的C/C++库/API函数调用,其中“ ∗ * ∗”表示通配符。这些库/API函数调用由商业产品Checkmarx生成。如下图所示:
总结
本文用深度学习方法解决了如今的漏洞检测系统由于人类专家定义在漏洞特征时繁重的体力劳动和较高的误报率问题。其中VulDeePecker漏洞检测系统设计的基本思路是将源代码划分为代码小工具,这些代码小工具不一定是空间上连续的代码块,而是语义上连续的代码块,然后将这些代码小工具进行标记,易受攻击标记为1,不易受攻击标记为0,最后这些代码小工具转换为向量,作为BLSTM神经网络的输入,再经过对BLSTM网络的训练后得到漏洞检测系统的模型,从而可以进行源代码的漏洞分析。
本论文提出的VulDeePecker漏洞检测系统优点如下:
- VulDeePecker可以同时处理多种类型的数据
- VulDeePecker比其他类型的漏洞检测系统更有效
- 将人类专家从手动定义特征和主观工作中解脱出来
- 减少漏洞检测的误报。同时本文也有一些局限性,
本论文提出的VulDeePecker漏洞检测系统缺点如下:
- VulDeePecker目前的设计只能用于检测源程序代码中是否存在漏洞的情况,而不能检测可执行文件中是否存在漏洞。
- VulDeePecker目前只能处理C/C++程序,而不适用于其他编程软件。
- VulDeePecker目前仅能处理与库/API函数调用相关的漏洞,而不能处理其他类型的漏洞。
- VulDeePecker目前仅适用于数据流分析(即数据依赖),而不适用于控制流分析(如控制依赖)。
- VulDeePecker目前使用的启发式方式在未来仍需进一步研究对于VulDeePecker有效性的影响。
- VulDeePecker目前的实现仅限于BLSTM神经网络,未来应使用其他类型的神经网络进行实验。
- VulDeePecker目前的数据集仅包含缓冲区错误漏洞和资源管理错误漏洞,而没有包含所有的漏洞类型。并且没有检测到0天漏洞,虽然这在原理上可行。