论文中文翻译——SySeVR A Framework for Using Deep Learning to Detect Software Vulnerabilities

本论文相关内容

  1. 论文下载地址——Web Of Science
  2. 论文中文翻译——SySeVR A Framework for Using Deep Learning to Detect Software Vulnerabilities
  3. 论文阅读笔记——SySeVR A Framework for Using Deep Learning to Detect Software Vulnerabilities


前言

  此博客为SySeVR A Framework for Using Deep Learning to Detect Software Vulnerabilities论文的中文翻译,本篇论文的翻译真的耗费了我的好大心血,其中有些专业名词的翻译一直感觉翻译不到点上,所以干脆就用的原文,而且整篇文章真的很长,也不好理解,断断续续翻译了一个多星期,但是整篇文章的质量是相当不错的。下面就是整篇文章的翻译内容!


SySeVR:一种使用深度学习检测软件漏洞的框架

作者信息

Zhen Li, Deqing Zou, Shouhuai Xu, Hai Jin, Fellow, IEEE, Yawei Zhu, and Zhaoxuan Chen

摘要

  软件漏洞(或简称漏洞)的检测是一个尚未解决的重要问题,每天报告的许多漏洞就表明了这一点。这需要机器学习方法来检测漏洞。深度学习对此很有吸引力,因为它减轻了手动定义特征的要求。尽管深度学习在其他应用领域取得了巨大成功,但它对漏洞检测的适用性尚未得到系统的理解。为了填补这一空白,我们提出了第一个使用深度学习来检测带有源代码的C/C++程序漏洞的系统框架。该框架被称为基于语法、基于语义和矢量表示(SySeVR),其重点是获得能够容纳与漏洞相关的语法和语义信息的程序表示。我们对4种软件产品的实验证明了该框架的有用性:我们检测到了国家漏洞数据库中未报告的15个漏洞。在这15个漏洞中,有7个是未知的,已向供应商报告,另外8个是在发布相关软件产品的更新版本时由供应商“悄悄地”修补的。

索引术语

   漏洞检测、安全、深度学习、程序分析、程序表示。

1 引言

  软件漏洞(或简称漏洞)是网络攻击盛行的根本原因。尽管学术界和工业界努力提高软件质量,但漏洞仍然是一个大问题。这可以通过以下事实来证明,即每年都会在通用漏洞和风险(CVE)中报告许多漏洞。

   鉴于漏洞是不可避免的,因此尽早发现它们很重要。基于源代码的静态分析是检测漏洞的重要方法,包括基于代码相似性的方法和基于模式的方法。基于代码相似性的方法可以检测由代码克隆引起的漏洞,但当漏洞不是由代码克隆造成时,这种方法具有很高的假阴性。基于模式的方法可能需要人类专家定义用于表示漏洞的漏洞特征,这使得它们容易出错且费力。因此,理想的方法应该能够有效地检测由各种原因引起的漏洞,同时尽可能少地依赖人类专家。

  深度学习——包括递归神经网络(RNNs)、卷积神经网络(CNNs)和深度信念网络(DBNs)——在图像和自然语言处理中取得了成功。虽然使用深度学习来检测漏洞很有诱惑力,但我们发现存在“领域差距”:深度学习天生就是为了处理具有自然矢量表示的数据(例如,图像像素);相比之下,软件程序没有这种矢量表示。最近,我们提出了第一个基于深度学习的漏洞检测系统,称为VulDeePecker,用于在切片级别(即语义上相互关联的多行代码)检测漏洞。在演示使用深度学习检测漏洞的可行性时,VulDeePecker有四个弱点:(i)它只考虑与库/API函数调用相关的漏洞;(ii)它仅利用由数据依赖性引起的语义信息;(iii)它只考虑被称为双向长短期记忆(BLSTM)的特定RNN;以及(iv)它不擅长解释假阳性和假阴性的原因。

  我们的贡献。在本文中,我们提出了第一个使用深度学习检测带有源代码的C/C++程序漏洞的系统框架。该框架以回答以下问题为中心:我们如何将程序表示为向量,以适应适合漏洞检测的语法和语义信息?为了回答这个问题,我们引入了基于语法的候选漏洞(SyVCs)和基于语义的候选漏洞的概念。直观地说,SyVCs反映了漏洞语法特征,SeVCs扩展了SyVCs以适应数据依赖性和控制依赖性引起的语义信息。此外,我们设计了自动提取SyVCs和SeVCs的算法。这解释了为什么我们将框架称为基于语法、基于语义和矢量表示,简称SySeVR。正如我们将看到的,SySeVR克服了VulDeePecker的上述缺点(i)-(iv)。

  为了评估SySeVR的有效性,我们提供了126种类型的漏洞的数据集,这些数据集从国家漏洞数据库(NVD)和软件保障参考数据集(SARD)中收集。该数据集应具有独立的价值,并可在https://github.com/SySeVR/SySeVR公开获取。值得一提的是,我们之前发布的与VulDeePecker相关的数据集不足以满足本文的目的,因为与VulDeePecker关联的数据集仅包含2种类型的漏洞。

   使用了新的数据集后,我们证明SySeVR实现了以下目标。

  • SySeVR使多种神经网络能够检测各种漏洞。在SySeVR框架中,双向RNNs,尤其是双向门控递归单元(BGRU)比单向RNNs和CNNs更有效,单向RNNs比DBNs和浅层学习模型更有效。此外,SySeVR使深度神经网络(尤其是BGRU)比最先进的漏洞检测方法更有效。

  • BGRU的有效性在很大程度上受到训练数据的影响。如果某些语法元素(例如标记)经常出现在易受攻击(与不易受攻击)的代码段中,那么这些语法元素可能会导致高假阳性率(相应地,假阴性率)。这意味着我们可以在一定程度上解释假阳性和假阴性的原因。

  • 容纳更多语义信息(即控制依赖性和数据依赖性)可以提高启用SySeVR的漏洞检测器的有效性。例如,由数据依赖和控制依赖诱导的语义信息可以平均降低30.4%的假阴性率。

  • 通过将支持SySeVR的BGRU应用于4种软件产品(Libav、Seamonkey、Thunderbird和Xen),我们检测到NVD中未报告的15个漏洞。在这15个漏洞中,有7个未知存在于这些软件产品中;出于道德原因,我们没有公布这些漏洞的确切位置,但我们已经将它们报告给了各自的供应商。其他8个漏洞已由供应商在发布相关软件产品的更新版本时“默默”修补。

  论文大纲。第2节介绍SySeVR框架。第3节描述实验和结果。第四部分讨论了本研究的局限性。第5节回顾了相关的前期工作。第六部分是全文的总结。

2 SYSEVR框架

2.1 基本思想和框架概述

2.1.1 基本思想

  深度学习在图像处理和其他应用中取得了成功。特别是图像处理中的region proposal(候选框)的概念启发我们将其应用于漏洞检测的环境中。然而,问题漏洞检测与问题图像处理有很大的不同,因为后者具有自然的结构表示。为了看到区别,让我们考虑一个使用深度学习在图像中检测人类的例子。一方面,如图1(a)所示,通过使用区域region proposal的概念和利用图像的结构表示(如纹理、边缘和颜色),可以实现对图像中的人的检测。可以从一张图像中提取多个区域region proposal,每个region proposal都可以被视为一个“单元”,用于训练神经网络检测对象(即本例中的人类)。

请添加图片描述

图1 (a)图像处理中region proposal的概念。

  另一方面,在使用深度学习检测漏洞时,我们需要以一种能够充分容纳漏洞相关的语法和语义信息的方式表示程序。乍一看,人们可能会认为将程序中的每个函数视为图像处理中的一个region proposal。然而,这太粗粒度了,因为漏洞检测器不仅需要告诉函数是否容易受到攻击,还需要确定漏洞的位置。也就是说,我们需要用于漏洞检测的程序的细粒度表示。还可以认为将每一行代码或语句(即这两个术语可以互换使用)作为漏洞检测的一个单元。然而,这种处理有两个缺点:(i)程序中的大多数语句不包含任何漏洞,这意味着很少有样本是受攻击的;和(ii)语义上相互关联的多个语句不被视为一个整体。

  通过前面的讨论我们认为应将一个程序划分为更小的代码段(即许多语句),这些代码段对应于“region proposal”,并显示漏洞的语法和语义特征。

2.1.2 框架概述

  我们观察到漏洞表现出一些语法特征,例如函数调用或指针使用。因此,我们建议使用语法特征来识别SyVCs,作为漏洞检测的起点(即SyVCs不足以训练深度学习模型,因为它们不包含漏洞的语义信息)。图1(b)显示了受region proposal概念启发的SySeVR框架。从本质上说,该框架寻找适合于漏洞检测程序的SyVC、SeVC和向量表示。

请添加图片描述

图1 (b) SySeVR框架的灵感来自于region proposal的概念,并以获取程序的SyVC、SeVC和矢量表示为中心。

  为了帮助理解SySeVR,我们使用图2中描述的运行示例来强调SySeVR如何提取SyVC、SeVC和程序的向量表示。在高层次上,SyVC(图2中的方框突出显示)是一个与某些漏洞的语法特征匹配的代码元素。SeVC对SyVC进行了扩展,以包含语义上与SyVC相关的语句(即代码行),其中语义信息由控制依赖和/或数据依赖引起;这种“SyVC到SeVC”(或SyVC→SeVC)的转换相当复杂,稍后将详细说明(在图3中)。最后,每个SeVC被编码为输入到深度神经网络的向量。

请添加图片描述

图2 说明程序的SyVC、SeVC和向量表示的示例,其中SyVCs用方框突出显示,一个SyVC可能是另一个SyVC的一部分。SyVC→SeVC的转换在图3中详细说明。

2.2 提取SyVCs

2.2.1 提取漏洞语法特征

  我们建议使用漏洞语法特征来识别代码片段作为漏洞检测的初始候选。例如,与指针使用相关的漏洞会显示标识符的声明包含字符`*`。在图2中,程序源代码第18行中的标识符“data”是指针的用法,第9行中“data”的声明包含字符`*`。

  鉴于存在许多漏洞,我们预计定义和提取它们的语法特征将非常耗时,因为这需要从易受攻击的程序中提取易受攻击的代码行。虽然这本身是一个重要的研究问题,但在3.3.1节中,我们将提出一种提取漏洞语法特征的具体方法;我们注意到这种方法还远远不够完美,因为它只覆盖了我们收集到的 93.6 % 93.6\% 93.6%的易受攻击程序,但足以证明SySeVR的有用性。在我们的方法中,我们使用程序抽象语法树(AST)上的节点属性来描述漏洞语法特征。

  不考虑漏洞语法特征的具体描述,我们可以用 H = { h k } 1 ≤ k ≤ β H=\left\{h_{k}\right\}_{1 \leq k \leq \beta} H={hk}1kβ表示一组漏洞语法特征,其中 h k h_k hk代表一个漏洞语法特征, β β β为漏洞语法特征的个数。给定 H H H,我们需要确定一段代码是否匹配语法特征 h k h_k hk。由于这些匹配操作特定于漏洞语法特征的表示,我们将其描述推迟到我们的案例研究中,该案例研究具有漏洞语法特征的特定表示。

2.2.2 定义和提取SyVCs

  我们从程序、函数、语句和标记的定义开始,这些将在本文中使用。

  定义1(程序、函数、语句、标记)。程序 P P P是一组函数 f 1 , … , f η f_1,…, f_η f1fη,表示为 P = { f 1 , … , f η } P = \{f_1,…,f_η\} P={f1fη}。函数 f i f_i fi(其中 1 ≤ i ≤ η 1≤i≤η 1iη)是语句 s i , 1 , … , s i , m i s_{i, 1}, \ldots, s_{i, m_{i}} si,1,,si,mi的有序集合,用 f i = { s i , 1 , … , s i , m i } f_i = \left\{s_{i, 1}, \ldots, s_{i, m_{i}}\right\} fi={si,1,,si,mi}表示。语句 s i , j s_{i,j} si,j(其中 1 ≤ i ≤ η 1≤i≤η 1iη并且 1 ≤ j ≤ m i 1≤j≤m_i 1jmi)是标记 t i , j , 1 , … , t i , j , w i , j t_{i, j, 1}, \ldots, t_{i, j, w_{i, j}} ti,j,1,,ti,j,wi,j的有序集合,用 s i , j = { t i , j , 1 , … , t i , j , w i , j } s_{i, j}=\left\{t_{i, j, 1}, \ldots, t_{i, j, w_{i, j}}\right\} si,j={ti,j,1,,ti,j,wi,j}表示。注意标记可以是标识符、操作符、常量和关键字,可以通过词法分析提取。

  给定一个函数 f i f_i fi,有一些标准的例程来生成它的AST。AST的根对应函数 f i f_i fi, AST的叶对应标记 t i , j , g ( 1 ≤ g ≤ w i , j ) t_{i,j,g}(1≤g≤w_{i,j}) ti,j,g(1gwi,j), AST的内部节点对应语句 s i , j s_{i,j} si,j s i , j s_{i,j} si,j的多个连续标记。直观地说,SyVC对应于AST的叶节点(这意味着它是一个标记),或者形式上对应于AST的内部节点(这意味着它是一条语句或由多个连续标记组成)。

  定义2 (SyVC)。考虑一个程序 P = { f 1 , … , f η } P = \{f_1,…,f_η\} P={f1fη},其中 f i = { s i , 1 , … , s i , m i } f_{i}=\left\{s_{i, 1}, \ldots, s_{i, m_{i}}\right\} fi={si,1,,si,mi},并且 s i , j = { t i , j , 1 , … , t i , j , w i , j } s_{i, j}=\left\{t_{i, j,1}, \ldots,t_{i, j,w_{i,j}}\right\} si,j={ti,j,1,,ti,j,wi,j}。代码元素 e i , j , z e_{i,j,z} ei,j,z s i , j s_{i,j} si,j的一个或多个连续标记组成,即 e i , j , z = ( t i , j , u , … , t i , j , v ) e_{i,j,z}=\left(t_{i, j, u}, \ldots, t_{i, j, v}\right) ei,j,z=(ti,j,u,,ti,j,v)其中 1 ≤ u ≤ v ≤ w i , j 1≤u≤v≤w_{i,j} 1uvwi,j。给定一组漏洞语法特征 H = { h k } 1 ≤ k ≤ β H = \{h_k\}_{1≤k≤β} H={hk}1kβ,其中 h k h_k hk表示漏洞语法特征, β β β为前文提到的漏洞语法特征个数,匹配漏洞语法特征 h k h_k hk的代码元素 e i , j , z e_{i,j,z} ei,j,z称为SyVC,其中如上所述的“匹配”操作与漏洞语法特征的具体表示有关。

  算法1给出了从给定程序 P = { f 1 , … , f η } P = \{f_1,…,f_η\} P={f1fη}和一组漏洞语法特征 H = { h k } 1 ≤ k ≤ β H = \{h_k\}_{1≤k≤β} H={hk}1kβ中提取SyVCs的高级描述。具体来说,算法1使用一个标准例程为每个函数 f i f_i fi生成一个AST T i T_i Ti。然后,算法1遍历 T i T_i Ti来识别SyVCs,即“匹配”某些 h k h_k hk的代码元素,其中“匹配”操作与漏洞语法特征的表示有关,因此将在处理特定漏洞语法特征时详细说明(见3.3.1节)。

请添加图片描述

  为了帮助理解这个想法,我们现在考虑一个例子。在图2的第二列中,我们使用方框突出显示从程序源代码中提取的所有SyVCs,这些SyVCs使用漏洞语法特征,将在第3.3.1节中描述。我们将详细说明如何提取这些SyVCs。值得一提的是,一个SyVC可能是另一个SyVC的一部分。例如,从第18行提取了三个SyVCs,因为它们是根据不同的漏洞语法特征提取的。

2.3 将SyVCs转换为SeVCs

2.3.1 基本定义

  为了检测漏洞,我们建议将SyVCs转换为SeVCs(即SyVC→SeVC),以适应与所讨论的SyVCs语义相关的语句。为此,我们建议利用程序切片技术来识别与SyVCs语义相关的语句。为了使用程序切片技术,我们需要使用程序依赖图(PDG)。这要求我们使用数据依赖关系和控制依赖关系,它们是在控制流图(CFG)上定义的。下面将回顾这些概念。

  定义3(CFG)。对于程序 P = { f 1 , … , f η } P = \{f_1,…,f_η\} P={f1fη},函数 f i f_i fi的CFG是一个图 G i = ( V i , E i ) G_i = (V_i, E_i) Gi=(Vi,Ei),其中 V i = { n i , 1 , … , n i , c i } V_i = \{n_{i,1},…, n_{i,c_{i}}\} Vi={ni,1ni,ci}是一组节点,每个节点代表一个语句或控制谓词, E i = { ϵ i , 1 , … , ϵ i , d i } E_{i} = \left\{\epsilon_{i, 1}, \ldots, \epsilon_{i, d_{i}}\right\} Ei={ϵi,1,,ϵi,di}是一组直接边,每条边代表一对节点之间可能的控制流。

  定义4(数据依赖)。考虑一个程序 P = { f 1 , … , f η } P = \{f_1,…,f_η\} P={f1fη},函数 f i f_{i} fi和在 G i G_{i} Gi中的两个节点 n i , j n_{i,j} ni,j n i , ℓ n_{i,\ell} ni,的CFG为 G i = ( V i , E i ) G_{i} = (V_{i}, E_{i}) Gi=(Vi,Ei),其中 1 ≤ j , ℓ ≤ c i 1 \leq j, \ell \leq c_{i} 1j,ci,并且 j ≠ ℓ j \neq \ell j=。如果 G i G_{i} Gi中有从 n i , ℓ n_{i,\ell} ni, n i , j n_{i,j} ni,j的路径,并且在节点 n i , j n_{i,j} ni,j处使用在节点 n i , ℓ n_{i,\ell} ni,处计算的值,那么 n i , j n_{i,j} ni,j的数据依赖于 n i , ℓ n_{i,\ell} ni,

  定义5(控制依赖)。考虑一个程序 P = { f 1 , … , f η } P = \{f_1,…,f_η\} P={f1fη},函数 f i f_{i} fi和在 G i G_{i} Gi中的两个节点 n i , j n_{i,j} ni,j n i , ℓ n_{i,\ell} ni,的CFG为 G i = ( V i , E i ) G_{i} = (V_{i}, E_{i}) Gi=(Vi,Ei),其中 1 ≤ j , ℓ ≤ c i 1 \leq j, \ell \leq c_{i} 1j,ci,并且 j ≠ ℓ j \neq \ell j=。其中 n i , j n_{i,j} ni,j后置支配 n i , ℓ n_{i,\ell} ni,,如果从 n i , ℓ n_{i,\ell} ni,到程序末尾的所有路径都穿过 n i , j n_{i,j} ni,j。如果存在从 n i , ℓ n_{i,\ell} ni,开始到 n i , j n_{i,j} ni,j结束的路径,使得(i) n i , j n_{i,j} ni,j后置支配在路径上除 n i , ℓ n_{i,\ell} ni, n i , j n_{i,j} ni,j的所有节点,并且(ii) n i , j n_{i,j} ni,j并不后置支配 n i , ℓ n_{i,\ell} ni,,那么 n i , j n_{i,j} ni,j控制依赖于 n i , ℓ n_{i,\ell} ni,

  根据数据依赖性和控制依赖性,可将PDG定义为:

  定义6(PDG)。对于程序 P = { f 1 , … , f η } P = \{f_1,…,f_η\} P={f1fη},函数 f i f_i fi的PDG记为 G i ′ = ( V i , E i ′ ) G_{i}^{\prime}=\left(V_{i}, E_{i}^{\prime}\right) Gi=(Vi,Ei),其中 V i V_i Vi与CFG中 G i G_i Gi的定义中的 V i V_{i} Vi相同, E i ′ = { ϵ i , 1 ′ , … , ϵ i , d i ′ ′ } E_{i}^{\prime}=\left\{\epsilon_{i, 1}^{\prime}, \ldots, \epsilon_{i, d_{i}^{\prime}}^{\prime}\right\} Ei={ϵi,1,,ϵi,di}是一组直接边,每条边代表一对节点之间的数据或控制依赖关系。

2.3.2 定义程序切片

  给定PDGs,我们可以从SyVCs中提取程序片段。我们考虑前向和后向切片,因为(i)SyVC可能会影响一些后续语句,因此可能包含漏洞;以及(ii)影响SyVC的语句可能使SyVC易受攻击。定义为,

  定义7(SyVC中的前向和后向程序切片)。考虑一个程序 P = { f 1 , … , f η } P = \{f_1,…,f_η\} P={f1fη},各函数 f i ( 1 ≤ i ≤ η ) f_{i}(1 \leq i \leq \eta) fi(1iη)的PDG G i ′ = ( V i , E i ′ ) G_{i}^{\prime}=\left(V_{i}, E_{i}^{\prime}\right) Gi=(Vi,Ei), 并且在 G i ′ G_{i}^{\prime} Gi中语句 s i , j s_{i,j} si,j的SyVC e i , j , z e_{i,j,z} ei,j,z

  • SyVC e i , j , z e_{i,j,z} ei,j,z f i f_i fi中的正向切片用 f s i , j , z fs_{i,j,z} fsi,j,z表示,定义为一个有序的节点集 { n i , x 1 , … n i , x μ i } ⊆ V i \left\{n_{i, x_{1}}, \ldots\right.\left.n_{i, x_{\mu_{i}}}\right\} \subseteq V_{i} {ni,x1,ni,xμi}Vi,其中 n i , x p , 1 ≤ x 1 ≤ x p ≤ x μ i ≤ c i n_{i, x_{p}}, 1 \leq x_{1} \leq x_{p} \leq x_{\mu_{i}} \leq c_{i} ni,xp,1x1xpxμici,可从 G i ′ G_{i}^{\prime} Gi中的 e i , j , z e_{i,j,z} ei,j,z到达。即 f s i , j fs_{i,j} fsi,j中的节点来自 G i ′ G_{i}^{\prime} Gi中从 e i , j , z e_{i,j,z} ei,j,z开始的所有路径。
  • 程序 P P P中SyVC e i , j , z e_{i,j,z} ei,j,z的程序间正向切片,用 f s i , j , z ′ \mathrm{fs}_{i, j, z}^{\prime} fsi,j,z表示,定义为一个有序的节点集,其中(i)一个节点属于一个或多个PDGs, 并且(ii)每个节点从 e i , j , z e_{i,j,z} ei,j,z开始通过一系列函数调用可达。也就是说, f s i , j , z ′ \mathrm{fs}_{i, j, z}^{\prime} fsi,j,z是一个有或没有跨越函数边界(通过函数调用)的正向切片。
  • f i f_i fi中SyVC e i , j , z e_{i,j,z} ei,j,z的后向切片,用 b s i , j , z \mathrm{bs}_{i, j, z} bsi,j,z表示,定义为一个有序的节点集 { n i , y 1 , … , n i , y ν i } ⊆ V i \left\{n_{i, y_{1}}, \ldots, n_{i, y_{\nu_{i}}}\right\} \subseteq V_{i} {ni,y1,,ni,yνi}Vi,其中 n i , y p , 1 ≤ y 1 ≤ y p ≤ y ν i ≤ c i n_{i, y_{p}}, 1 \leq y_{1} \leq y_{p} \leq y_{\nu_{i}} \leq c_{i} ni,yp,1y1ypyνici,其中在 G i ′ G_{i}^{\prime} Gi中可达 e i , j , z e_{i,j,z} ei,j,z。即 b s i , j , z \mathrm{bs}_{i, j, z} bsi,j,z中的节点来自 G i ′ G_{i}^{\prime} Gi中以 e i , j , z e_{i,j,z} ei,j,z结束的所有路径。
  • 程序 P P P中SyVC e i , j , z e_{i,j,z} ei,j,z的程序间后向切片,用 b s i , j , z ′ \mathrm{bs}_{i, j, z}^{\prime} bsi,j,z表示,定义为节点的有序集合,其中(i)一个节点属于一个或多个PDGs,(ii)每个节点可以通过一系列函数调用到达 e i , j , z e_{i,j,z} ei,j,z。也就是说, b s i , j , z ′ \mathrm{bs}_{i, j, z}^{\prime} bsi,j,z是一个反向切片,有或没有跨越函数边界(通过函数调用)。
  • 给定程序间正向切片 f s i , j , z ′ \mathrm{fs}_{i, j, z}^{\prime} fsi,j,z和程序间后向切片 b s i , j , z ′ \mathrm{bs}_{i, j, z}^{\prime} bsi,j,z,通过在SyVC e i , j , z e_{i,j,z} ei,j,z处合并 f s i , j , z ′ \mathrm{fs}_{i, j, z}^{\prime} fsi,j,z b s i , j , z ′ \mathrm{bs}_{i, j, z}^{\prime} bsi,j,z,将SyVC e i , j , z e_{i,j,z} ei,j,z的(程序间)程序切片表示为 p s i , j , z \mathrm{ps}_{i, j, z} psi,j,z,定义为节点的有序集合(属于 P P P中函数的PDGs)。即 p s i , j , z \mathrm{ps}_{i, j, z} psi,j,z是通过连接前向切片 f s i , j , z ′ \mathrm{fs}_{i, j, z}^{\prime} fsi,j,z与后向切片 b s i , j , z ′ \mathrm{bs}_{i, j, z}^{\prime} bsi,j,z的保序方式,同时忽略相邻重复节点(即用一个节点替换同一节点的多个相邻出现)得到的有序集。

  在图3中,第三列显示了程序间的前向切片、程序间的后向切片和SyVC“data”的程序切片(程序源代码第25行)。SyVC“data”的程序间前向切片跨越了函数 f u n c func func p r i n t l n println println。SyVC“data”的程序间后向切片与函数 f u n c func func中的SyVC“data”的后向切片相同,因为没有其他函数调用函数 f u n c func func。SyVC“data”的程序切片是通过连接程序间前向切片和程序间后向切片而获得的,同时省略与SyVC“data”对应的节点的一个(两个中的一个)相邻出现(程序源代码第25行)。

请添加图片描述

图3 在算法2中为SyVC“数据”详细说明SyVC→SeVC转换过程,其中实箭头(即有向边)表示数据依赖,虚线箭头表示控制依赖。注意每个实箭头(即数据依赖)都用在问题中引起相关数据依赖的变量名进行了注释。
2.3.3 定义SeVCs

  在提取了SyVCs的程序片之后,我们现在可以定义SeVCs了。

  定义8 (SeVC)。给定一个程序 P = { f 1 , … , f η } P = \{f_1,…,f_η\} P={f1fη}和在函数 f i f_{i} fi的语句 s i , j s_{i,j} si,j中有一个SyVC e i , j , z e_{i,j,z} ei,j,z,则SyVC e i , j , z e_{i,j,z} ei,j,z对应的SeVC,记为 δ i , j , z \delta_{i, j, z} δi,j,z,定义为 P P P中语句的有序子集,记为 δ i , j , z = { s a 1 , b 1 , … , s a v i , j , z , b v i , j , z } \delta_{i, j, z}=\left\{s_{a_{1}, b_{1}}, \ldots, s_{a_{v_{i, j, z}}}, b_{v_{i, j, z}}\right\} δi,j,z={sa1,b1,,savi,j,z,bvi,j,z},其中语句 s a p , b q ( 1 ≤ p , q ≤ v i , j , z ) s_{a_{p}, b_{q}}\left(1 \leq p, q \leq v_{i, j, z}\right) sap,bq(1p,qvi,j,z)与SyVC e i , j , z e_{i,j,z} ei,j,z之间存在数据依赖或控制依赖。换句话说,SeVC δ i , j , z \delta_{i, j, z} δi,j,z是一个有序的语句集,对应于(程序间)程序片 p s i , j , z \mathrm{ps}_{i, j, z} psi,j,z的节点。

2.3.4 计算SeVCs

  算法2将前面的讨论总结为三个步骤:生成PDGs;用算法1生成SyVCs输出的程序片;并将程序切片转换为SeVCs。在接下来的内容中,我们详细说明这些步骤,并使用图3来说明一个运行示例。具体而言,图3描述了SyVC“数据”(与指针使用有关)的SyVC→SeVC转换,同时兼顾了数据依赖和控制依赖引起的语义信息。

请添加图片描述

  步骤1(算法2中的第2-4行)。该步骤为每个函数生成一个PDG。为此,有一些标准算法。作为一个运行的例子,图3的第二列显示了分别对应于函数 f u n c func func p r i n t l n println println的PDGs,其中每个数字表示语句的行号。

  步骤2(算法2中的第6-9行)。该步骤为每个SyVC e i , j , z e_{i,j,z} ei,j,z生成程序片 p s i , j , z \mathrm{ps}_{i, j, z} psi,j,z。将 f s i , j , z \mathrm{fs}_{i, j, z} fsi,j,z f i f_{i} fi调用的函数的前向切片合并,得到程序间前向切片 f s i , j , z ′ \mathrm{fs}_{i, j, z}^{\prime} fsi,j,z。通过合并 b s i , j , z \mathrm{bs}_{i, j, z} bsi,j,z和来自 f i f_{i} fi调用的函数和 f i f_{i} fi调用函数的后向切片来获得程序间后向切片 b s i , j , z ′ \mathrm{bs}_{i, j, z}^{\prime} bsi,j,z。最后将 f s i , j , z ′ \mathrm{fs}_{i, j, z}^{\prime} fsi,j,z b s i , j , z ′ \mathrm{bs}_{i, j, z}^{\prime} bsi,j,z合并为程序片 p s i , j , z \mathrm{ps}_{i, j, z} psi,j,z

  作为一个运行的例子,图3中的第三列显示了SyVC“data”的程序切片,其中后向切片对应函数 f u n c func func,前向切片对应函数 f u n c func func p r i n t l n println println。值得一提的是,为了获得SyVC的前向切片,我们只利用数据依赖关系,原因有两个:(i)通过控制依赖关系受SyVC影响的语句在大多数情况下不会受到攻击;(ii)利用对SyVC有控制依赖关系的语句将涉及许多与漏洞几乎没有关系的语句。例如,考虑一个“while ”循环的条件表达式中的指针变量SyVC。如果在“while”循环体中没有引用指针变量,“while”循环体中的语句仅通过控制依赖关系受到SyVC的影响,这意味着SyVC不会在“while”循环体中造成任何漏洞。如果上面提到的与SyVC相关的指针变量的前向切片涉及到控制依赖,那么“while”循环主体中的所有依赖于SyVC的控制语句都将包含在SeVC中,尽管它们与漏洞几乎没有关系。另一方面,为了获得SyVC的后向切片,我们同时利用了数据依赖性和控制依赖性。

  步骤3(算法2中的第10-19行)。该步骤将程序切片转换为SeVCs,如下所示。首先,该算法将属于函数 f i f_{i} fi且出现在 p s i , j , z \mathrm{ps}_{i, j, z} psi,j,z中的语句作为节点转换为SeVC,同时保持这些语句在 f i f_{i} fi中的顺序。作为图3所示的运行示例,13条语句属于函数 f u n c func func,3条语句属于函数 p r i n t l n println println。根据这些语句在两个函数中的顺序,我们得到两个有序的语句集:第{7、9、10、11、12、14、16、18、22、23、24、25、26}行和第{1、3、4}行。

  其次,该算法将属于不同函数的语句转换为SeVC。对于在 p s i , j , z \mathrm{ps}_{i, j, z} psi,j,z中作为节点出现的语句 s i , j ∈ f i s_{i, j} \in f_{i} si,jfi s a p , b q ∈ f a p ( i ≠ a p ) s_{a_{p}, b_{q}} \in f_{a_{p}}\left(i \neq a_{p}\right) sap,bqfap(i=ap),如果 f i f_{i} fi调用 f a p f_{a_{p}} fap,则 s i , j s_{i,j} si,j s a p , b q s_{a_{p}, b_{q}} sap,bq的函数调用顺序相同,即 s i , j < s a p , b q s_{i, j}<s_{a_{p}, b_{q}} si,j<sap,bq;否则, s i , j > s a p , b q s_{i, j}>s_{a_{p}, b_{q}} si,j>sap,bq。如图3所示的运行示例,SeVC是第{7,9,10,11,13,14,16,18,22,23,24,25,26,1,3,4}行,其中函数 f u n c func func中的语句出现在函数 p r i n t l n println println的语句之前,因为 f u n c func func调用了 p r i n t l n println println。图3的第四列显示了与SyVC“数据”对应的SeVC,即与SyVC“数据”语义相关的语句有序集。

2.4 将SeVCs编码为向量

  算法3分三步将SeVCs编码为向量。

  步骤1(算法3中的第2-6行)。为了使SeVCs独立于用户定义的变量和函数名,同时捕获程序语义信息,每个SeVC δ i , j , z \delta_{i, j, z} δi,j,z被转换为符号表示。为此,我们建议删除非ASCII字符和注释,然后以一对一的方式将用户定义的变量名映射到符号名(例如“V1”,“V2”),最后以一对一的方式将用户定义的函数名映射到符号名(例如“F1”,“F2”)。注意,不同的SeVCs可能具有相同的符号表示。有关映射过程的详细信息,请参阅[11]。

  步骤2(算法3中的第8-13行)。这一步是将符号表示编码为向量。为此,我们建议通过词法分析(例如,“V1”,“=”,“V2”,“-”,“8”,和“;”)将SeVC δ i , j , z \delta_{i, j, z} δi,j,z的符号表示(例如,“V1=V2-8;”)划分为符号序列。我们把一个符号转换成一个固定长度的向量。通过连接这些向量,我们得到了每个SeVC的一个向量 R i , j , z R_{i,j,z} Ri,j,z

  步骤3(算法3第14-22行)。因为(i)符号的数量(即表示SeVCs的向量)可能不同,(ii)神经网络的向量长度与输入相同,我们使用阈值 θ θ θ作为神经网络输入向量的长度。当一个向量小于 θ θ θ时,零被加到该向量的末尾。当一个向量比 θ θ θ长时,有三种情况,但基本思想是使SyVC出现在结果向量的中间。(i)当SyVC的子向量小于 θ / 2 θ/2 θ/2。在这种情况下,我们删除 R i , j , z R_{i,j,z} Ri,j,z的最右边部分,使得到的向量的长度为 θ θ θ。(ii)当SyVC右边的子向量小于 θ / 2 θ/2 θ/2。在这种情况下,我们删除 R i , j , z R_{i,j,z} Ri,j,z的最左边部分,使得到的向量的长度为 θ θ θ。(iii)反之,则保持长度为 ⌊ ( θ − 1 ) / 2 ⌋ \lfloor(\theta-1) / 2\rfloor (θ1)/2的子向量紧靠SyVC的左侧,并保持长度为 ⌈ ( θ − 1 ) / 2 ⌉ \lceil(\theta-1) / 2\rceil (θ1)/2的子向量紧靠SyVC的右侧。结合SyVC,我们得到一个长度为 θ θ θ的向量。例如,假设 θ = 15000 θ = 15000 θ=15000,每个符号的长度是30,这意味着每个SeVC有500个符号。假设一个SeVC中的符号数是510(因此需要减少到500),而SyVC位于第255个符号的位置(在510个符号中),那么我们保留紧靠SyVC左侧的249个连续符号和紧靠SyVC右侧的250个连续符号。结合SyVC,我们得到了一个500=249+1+250个符号的向量。我们强调前面的操作是很好定义的,因为每个SyVC都转换为SeVC,并且在SeVC中只出现一次。

请添加图片描述

2.5 标记SeVCs和对应的向量

  为了学习深度神经网络,我们将向量(即它们所代表的SeVCs)标记为易受攻击的或不易受攻击的,如下所示:包含已知漏洞的SeVC(即表示它的向量)标记为“1”(即易受攻击的),否则标记为“0”(即不易受攻击的)。一个学习过的深度神经网络编码漏洞性模式,可以检测给定的SeVCs是否易受攻击。

3 实验与结果

3.1 研究问题和数据集

  研究的问题。我们的实验旨在回答以下研究问题(RQs):

  • RQ1:SySeVR能让BLSTM检测到多种(或单一)漏洞吗?
  • RQ2:SySeVR能否构建多种神经网络来检测多种漏洞?我们能解释一下它们的有效性吗?
  • RQ3:适应控制依赖能使SySeVR更有效吗?提高多少?
  • RQ4:与最先进的方法相比,基于SySeVR的方法如何更有效?

  为了回答这些问题,我们使用Tensorflow在Python中实现了深度神经网络。运行实验的计算机使用NVIDIA GeForce GTX 1080 GPU和运行在3.50GHz的Intel Xeon E5-1620 CPU。

  漏洞数据集。我们从两个来源生成漏洞数据集:NVD和SARD。NVD包含软件产品(例如,软件系统)中的漏洞,可能还包含diff文件,描述漏洞代码段与其补丁版本之间的差异。SARD包含生产、合成和学术程序(也称为测试用例),它们被分为“好的”(即没有漏洞)、“坏的”(即有漏洞)和“混合的”(即有其补丁版本也可用的漏洞)。注意,NVD中的程序由一个或多个文件(例如.c或.cpp文件)组成,这些文件包含一些漏洞(对应于CVE ID)或其补丁版本,而SARD中的程序是一个测试用例。

  对于NVD,我们关注19个流行的C/C++开源产品及其伴随diff文件的漏洞,diff文件是提取易受攻击代码段所必需的。结果,我们收集了1591个开源C/C++程序,其中874个是易受攻击的。对于SARD,我们收集了14000个C/C++程序,其中13906个程序是易受攻击的(即“坏的”或“混合的”)。请注意,这些漏洞程序中有大量属于“混合”类,同时带有漏洞函数和它们的补丁版本。这些程序的平均长度是573.5行代码。我们总共收集了15591个程序,其中14780个程序是易受攻击的;这些漏洞程序包含126种类型的漏洞,其中每种类型都由一个通用漏洞枚举标识(CWE ID)唯一标识。126个CWE IDs与我们的数据集一起发布。

3.2 评价指标

  漏洞检测器的有效性可以通过以下广泛使用的度量指标进行评估:假阳性率( F P R F P R FPR)、假阴性率( F N R F N R FNR)、准确度( A A A)、精度( P P P)、 F 1 F1 F1-度量( F 1 F1 F1)和马修斯相关系数( M C C MCC MCC)。设 T P TP TP表示被检测为易受攻击的易受攻击样本的数量, F P FP FP表示不易受攻击但被检测为容易受攻击的样本的数量; T N TN TN表示不易受到攻击(称为非易受攻击)且被检测为不易受攻击的样本的数量; F N FN FN表示被检测到不易受威胁的易受攻击样本的数量。度量 F P R = F P F P + T N F P R=\frac{\mathrm{FP}}{\mathrm{FP}+\mathrm{TN}} FPR=FP+TNFP度量假阳性样本在不易受攻击样本中的比例。度量 F N R = F N T P + F N F N R=\frac{\mathrm{FN}}{\mathrm{TP}+\mathrm{FN}} FNR=TP+FNFN度量易受攻击样本中假阴性样本的比例。度量 A = T P + T N T P + F P + T N + F N A=\frac{\mathrm{TP}+\mathrm{TN}}{\mathrm{TP}+\mathrm{FP}+\mathrm{TN}+\mathrm{FN}} A=TP+FP+TN+FNTP+TN度量所有样本中正确检测样本的比例。度量 P = T P T P + F P P=\frac{\mathrm{TP}}{\mathrm{TP}+\mathrm{FP}} P=TP+FPTP度量真正易受攻击样本在检测(或声称)易受攻击样本中的比例。度量 F 1 = 2 ⋅ P ⋅ ( 1 − F N R ) P + ( 1 − F N R ) F 1=\frac{2 \cdot P \cdot(1-F N R)}{P+(1-F N R)} F1=P+(1FNR)2P(1FNR)通过考虑精度和假阴性率来度量整体有效性。度量 M C C = T P × T N − F P × F N ( T P + F P ) ( T P + F N ) ( T N + F P ) ( T N + F N ) M C C=\frac{\mathrm{TP} \times \mathrm{TN}-\mathrm{FP} \times \mathrm{FN}}{\sqrt{(\mathrm{TP}+\mathrm{FP})(\mathrm{TP}+\mathrm{FN})(\mathrm{TN}+\mathrm{FP})(\mathrm{TN}+\mathrm{FN})}} MCC=(TP+FP)(TP+FN)(TN+FP)(TN+FN) TP×TNFP×FN度量模型预测与基本真实标签匹配的程度;这个度量在处理不平衡数据时尤其有用,这就是本文的情况,因为我们的非易受攻击样本比易受攻击样本多得多。

3.3 实验

  实验遵循SySeVR框架,并在必要时进行详细说明。

3.3.1 提取SyVCs

  接下来,我们将详细介绍算法1中针对不同类型漏洞的两个组件:漏洞语法特征的提取和如何匹配它们。

  提取漏洞语法特征。为了提取已知漏洞的语法特征,从上面提到的漏洞程序中提取易受攻击的代码行,并分析它们的语法特征是很自然的。然而,这是一项非常耗时的任务,这促使我们利用最先进的商业工具Checkmarx的C/C++漏洞规则来分析漏洞语法特征。正如我们将看到的,这种替代方法是有效的,因为它涵盖了从SARD收集到的 93.6 % 93.6\% 93.6%的易受攻击程序。值得一提的是,我们选择Checkmarx而不是开源工具(例如,Flawfinder和RATS),因为后者具有简单的解析器和不完善的规则。

  我们对Checkmarx规则的手工检查得出了以下4种漏洞语法特征(每一种都容纳了许多漏洞)。

  • 库/API函数调用(简称FC):这种语法特性涵盖了811个库/API函数调用,它们与我们的数据集一起发布。这811个函数调用对应106个CWE IDs。
  • 数组用法(简称AU):这种语法特征涵盖了87个与数组相关的CWE IDs(例如,与数组元素访问、数组地址算术相关的问题)。
  • 指针使用(简称PU):这种语法特征涵盖103个与指针相关的CWE IDs(例如,在指针算术、引用、地址传输作为函数参数时的不当使用)。
  • 算术表达式(简称AE):这种语法特征涵盖了与不正确的算术表达式(如整数溢出)相关的45个CWE IDs。

  图4显示,这4种语法特征在它们覆盖的CWE IDs方面的相互重叠。这4种语法特征是由126个CWE IDs对应的程序生成的。请注意,一种语法特征可能涵盖多个CWE IDs,而一个CWE IDs可能涵盖一种或多种类型的语法特征。例如,由图4可以看出,10个CWE IDs对应的漏洞被PU类语法特征覆盖,而不被其他语法特征覆盖,39个CWE IDs对应的漏洞被FC、AU、PU、AE这4种语法特征全部覆盖。

请添加图片描述

图4 FC、AU、PU和AE在它们覆盖的CWE IDs方面的维恩图,其中|FC| = 106, |AU| = 87, |PU| = 103, |AE| = 45, |FC∪AU∪PU∪AE| = 126。

  匹配语法特征。为了使用算法1提取SyVCs,我们需要确定程序 P P P中函数 f i f_{i} fi的抽象语法树 T i T_{i} Ti上的代码元素 e i , j , z e_{i,j,z} ei,j,z是否匹配漏洞语法特征。注意, T i T_{i} Ti可以通过使用Joern生成。下面的方法,如图5所示,通过图2所示的示例程序,可以自动判断代码元素 e i , j , z e_{i,j,z} ei,j,z是否匹配语法特征。

请添加图片描述

图5 说明语法特征匹配的示例,其中高亮显示的节点匹配某些漏洞语法特征,因此是SyVC。
  • 如图5(a)所示,我们说代码元素 e i , j , z e_{i,j,z} ei,j,z(即“memset”)匹配FC语法特征,如果(i) e i , j , z e_{i,j,z} ei,j,z T i T_{i} Ti上是一个“被调用者”(即,函数被调用),并且(ii) e i , j , z e_{i,j,z} ei,j,z是上面提到的811个函数调用之一。
  • 如图5(b)所示,如果(i) e i , j , z e_{i,j,z} ei,j,z是在标识符声明语句(即IdentifierDeclStatement)节点中声明的标识符,且(ii) IdentifierDeclStatement节点包含字符’[‘and’]',则我们认为代码元素 e i , j , z e_{i,j,z} ei,j,z(即" source ")匹配AU语法特征。
  • 如图5(c)所示,如果(i) e i , j , z e_{i,j,z} ei,j,z是在IdentifierDeclStatement节点中声明的标识符,并且(ii) IdentifierDeclStatement节点包含字符“*”,则我们认为代码元素 e i , j , z e_{i,j,z} ei,j,z(即“data”)匹配PU语法特征。
  • 如图5(d)所示,如果(i) e i , j , z e_{i,j,z} ei,j,z 是一个表达式语句(ExpressionStatement)节点,并且(ii) e i , j , z e_{i,j,z} ei,j,z 包含一个字符’=‘并在’='的右侧有一个或多个标识符。我们认为代码元素 e i , j , z e_{i,j,z} ei,j,z ("data=dataBuffer-8 ")匹配AE语法特征。

  提取SyVCs。现在我们可以使用算法1从15591个程序中提取SyVCs。对应这4种语法特征,我们提取了4种SyVCs:

  • FC类SyVCs:我们从NVD中提取6356个,从SARD中提取58047个,总共64403个。
  • AU类SyVCs:我们从NVD中提取9812个,从SARD中提取32417个,总共42229个。
  • PU类SyVCs:我们从NVD中提取73890个,从SARD中提取217951个,总共291841个。
  • AE类SyVCs:我们从NVD中提取5295个,从SARD中提取16859个,总共22154个。

  将它们放在一起,我们提取了420627个SyVCs,涵盖了从SARD收集的13906个易受攻击程序中的13,016个(占 93.6 % 93.6\% 93.6%);这一覆盖验证了我们使用Checkmarx规则派生漏洞语法特征的想法。注意我们可以计算出 93.6 % 93.6\% 93.6%的覆盖率,因为SARD给出了每个漏洞的精确位置;相反,我们不能计算关于NVD的覆盖率,因为它没有给出漏洞的精确位置。提取SyVC的平均时间为270毫秒。

3.3.2 将SyVCs转换为SeVCs

  当使用算法2将SyVCs转换为SeVCs时,我们使用Joern提取PDGs。相对于从算法1中提取的420627个SyVCs,算法2生成420627个SeVCs(同时重调用将一个SyVC转换为一个SeVC)。为了观察语义信息的效果,我们实际上使用算法2生成了两组SeVCs:一组容纳仅由数据依赖引起的语义信息,另一组容纳同时由数据依赖和控制依赖引起的语义信息。在这两种情况下,表1的第二列总结了按所转换的SyVCs类型分类的SeVCs的数量。从SyVC→SeVC转换的效率来看,生成一个容纳数据依赖的SeVC平均需要331毫秒,生成一个容纳数据依赖和控制依赖的SeVC平均需要362毫秒。

表1 15591个项目中SeVCs、易受攻击的SeVCs和非易受攻击的SeVCs的数量

请添加图片描述

3.3.3 将SeVCs编码为向量表示

  我们使用算法3将SeVCs编码为向量。为此,我们采用 w o r d 2 v e c word2vec word2vec将从15591个程序中提取的SeVCs符号编码为定长向量。主要超参数包括:词向量的维数为30,窗口大小为5,训练算法为skip-gram,配置高频词随机下采样的阈值为0.001。然后,每个SeVC由表示其符号的向量的拼接表示。我们将每个SeVC设置为500个符号(必要时填充或截断,如算法3中讨论的那样),每个符号的长度为30,这意味着 θ = 15000 θ = 15000 θ=15000

3.3.4 生成SeVCs的基本真实标签

  我们分两步为SeVCs生成基本真实标签。首先,我们自动生成初步标签。对于从NVD提取的SeVCs,我们检查其diff文件包含行删除的漏洞,同时注意到我们不考虑仅包含行添加的diff文件,因为NVD在这种情况下不会给出漏洞语句。对于包含行删除的diff文件,我们解析它来标记和区分(i)以“-”为前缀并被删除/修改的行(语句)(ii)以“-”为前缀并被移动的行(例如在一处删除并在另一处添加)。如果一个SeVC包含至少一个以“-”为前缀的删除/修改语句,则它被标记为“1”(即易受攻击的);如果SeVC包含至少一个以“-”为前缀的移动语句,并且检测到的文件包含已知的漏洞,则标记为“1”;否则,它被标记为“0”(即不易受攻击的)。对于从SARD中提取的SeVCs,从“好”程序中提取的SeVC被标记为“0”(即不易受攻击的);从“坏”或“混合”程序中提取的SeVC如果包含至少一条易受攻击的语句,则标记为“1”(即易受攻击的);否则,它被标记为“0”。

  其次,为了提高上述初步标签的质量,我们使用分层 k k k-fold ( k k k=5)交叉验证来识别在上一步中可能被错误标记的易受攻击的SeVCs(同时注意到真正的易受攻击的样本永远不会被错误标记为“0”),并手动检查它们,如下所示。(i)数据集分为5个子集。(ii)一个子集作为验证集,其他4个子集放在一起作为训练集。(iii)利用训练过的神经网络对验证集中的样本进行分类。假阴性(即易受攻击的样本没有被检测出是易受攻击的)被认为是可能被错误标记的样本。然后,我们手动检查这些样本,并纠正错误的样本。步骤(ii)和(iii)重复5次,以便每个子集作为验证集使用一次。总共,我们手动检查了2605个可能标记错误的样本(即所有420627个样本中的 0.6 % 0.6\% 0.6%)。在这2605个样本中,我们手动纠正了1641个假阴性(同时注意没有假阳性,因为这2605个样本都是脆弱的)。

  总共有56395个SeVCs被标记为“1”,364232个SeVCs被标记为“0”。表1的第三和第四列总结了与每种SyVCs对应的易受攻击的与非易受攻击的SeVCs的数量。与SeVC相对应的基本真实标签与SeVC的基本真实标签相同。

3.4 实验结果

  对于从NVD和SARD收集的程序,我们分别随机选择其中80%的程序作为训练集(即用于训练和验证),其余20%的程序作为测试集(即用于测试)。

3.4.1 回答RQ1的实验

  在本实验中,我们使用[11]中的BLSTM和容纳由数据和控制依赖引起的语义信息的SeVCs。我们随机选择从训练程序中提取的30000个SeVCs作为训练集,从测试程序中提取的7500个SeVCs作为测试集。两个集合都包含对应4种SyVCs的SeVCs,在每种SyVCs中的易受攻击的与非易受攻击的SeVCs的比例成正比。为了与VulDeePecker进行公平的比较,我们还随机选择从训练程序中提取的FC类SyVCs对应的30000个SeVCs作为训练集,从测试程序中提取的FC类SyVCs对应的7500个SeVCs作为测试集(也与整个FC类SyVCs集合中易受攻击的与非易受攻击的SeVCs的比例成正比)。注意,这些SeVCs只容纳由数据依赖关系引起的语义信息。我们使用分层的5倍交叉验证来训练深度神经网络,并选择导致 F 1 F1 F1-度量(即整体漏洞检测有效性)最高的超参数值。我们用来学习BLSTM的主要超参数描述如下。dropout为0.2;批数为16个;epochs为20;输出维度为256;使用小批随机梯度下降算法结合ADAMAX进行训练,默认学习率为0.002;隐向量维数为500;隐层数是2。

表2 VulDeePecker与启用SySeVR的BLSTM(或SySeVR-BLSTM)在检测与各种SyVCs相关的漏洞方面有效性的对比(度量单位:%)

请添加图片描述

  表2总结了结果。我们发现SySeVR-BLSTM能够以最低的 F N R FNR FNR 17.1 % 17.1\% 17.1%)检测到AU类漏洞,但 F P R FPR FPR高于其他三种类型的漏洞。它检测FC类漏洞的 F 1 F1 F1-度量( 86.8 % 86.8\% 86.8%)和MCC( 83.6 % 83.6\% 83.6%)最高。其他三种漏洞的平均 F P R FPR FPR 1.6 % 1.6\% 1.6% F N R FNR FNR 18.5 % 18.5\% 18.5%。总体而言,在检测同类型(即FC类)漏洞时,SySeVR-BLSTM的 F P R FPR FPR F N R FNR FNR分别比VulDeePecker低 3.4 % 3.4\% 3.4% 5.0 % 5.0\% 5.0%。这可以用SySeVR-BLSTM通过SeVCs容纳更多的语义信息(例如控制依赖)的事实来解释。这导致:

  观点1。SySeVR-BLSTM可以检测到与函数调用、数组使用、指针使用和算术表达式相关的漏洞,在检测库/API函数调用相关的漏洞时,SySeVR-BLSTM的 F P R FPR FPR F N R FNR FNR分别比VulDeePecker低 3.4 % 3.4\% 3.4% 5.0 % 5.0\% 5.0%

3.4.2 回答RQ2的实验

  为了回答RQ2,我们使用分层的5倍交叉验证来训练8个标准模型:一个线性逻辑回归(LR)分类器,一个具有一个隐藏层多层感知(MLP)的神经网络,一个DBN,一个CNN,和4个RNNs(即长短期记忆(LSTM),门控循环单元(GRU), BLSTM和BGRU),使用的数据集(4种SyVCs)如3.4.1节所述。在每种情况下,我们选择导致最高 F 1 F1 F1-度量的超参数值。

表3 启用SySeVR的不同类型模型在检测4种漏洞时的有效性(度量单位:%)

请添加图片描述

  表3总结了每个模型的 F P R FPR FPR设置为 2.0 % 2.0\% 2.0%的结果,之所以选择这个值,是因为模型的 F P R FPR FPR达到了最高的 F 1 F1 F1-度量。我们观察到,与单向RNNs(即LSTM和GRU)相比,双向RNNs(即BLSTM和BGRU)分别平均提高了 4.5 % 4.5\% 4.5% F N R FNR FNR 2.3 % 2.3\% 2.3% F 1 F1 F1-度量。这种改进可能是由以下原因引起的:双向RNNs可以容纳更多关于出现在问题相关语句前后的语句的信息。我们进一步观察到,双向RNNs(特别是BGRU)比CNN更有效,而CNN又比DBN和浅学习模型(即LR和MLP)更有效。此外,这些模型在 M C C MCC MCC F 1 F1 F1-度量中都达到了相似的有效性,这意味着数据不平衡的问题并不显著。总之,

  观点2。支持SySeVR的双向RNN(特别是BGRU)比支持SySeVR的单向RNN和CNN更有效,后者比支持SySeVR的DBN和浅层学习模型(即LR和MLP)更有效。尽管如此,所有这些模型的 F N R FNR FNR始终远高于其 F P R FPR FPR

  上述启用SySeVR的模型采用 w o r d 2 v e c word2vec word2vec来生成向量。为了看看 w o r d 2 v e c word2vec word2vec是否可以用更简单的向量表示代替,比如标记频率,我们使用单词袋将SeVCs编码为固定长度的向量。有了这种向量表示,我们使用两个浅层模型(即LR和MLP)和两个深层神经网络(即CNN和BGRU)进行了实验。实验结果如表4所示。我们观察到 w o r d 2 v e c word2vec word2vec的最佳结果(BGRU达到了 85.8 % 85.8\% 85.8% F 1 F1 F1 83.7 % 83.7\% 83.7% M C C MCC MCC如表3所示)远远好于词汇袋的最佳结果( M L P MLP MLP达到了 76.6 % 76.6\% 76.6% F 1 F1 F1 73.7 % 73.7\% 73.7% M C C MCC MCC)。对于单词袋,我们观察到浅模型比深度神经网络更有效;对于 w o r d 2 v e c word2vec word2vec,深度神经网络比浅层模型更有效。其中,BGRU对 w o r d 2 v e c word2vec word2vec最有效( F 1 F1 F1 85.8 % 85.8\% 85.8% M C C MCC MCC 83.7 % 83.7\% 83.7%),对单词袋最无效( F 1 F1 F1 48.8 % 48.8\% 48.8% M C C MCC MCC 46.9 % 46.9\% 46.9%)。这可以解释为单词袋生成的向量没有上下文信息,导致BGRU无法捕获上下文,效率较低的事实。这导致:

  观点3。使用分布式表示(如 w o r d 2 v e c word2vec word2vec)来捕获上下文信息对SySeVR很重要。特别是,以标记频率为中心的表示是不够的。

  正因为如此,我们总是使用 w o r d 2 v e c word2vec word2vec来生成将在本文其余部分讨论的实验向量。

表4 启用SySeVR的模型且使用从单词袋导出的向量的有效性(度量单位:%)

请添加图片描述

  最后解释了BGRU在漏洞检测中的有效性。解释深度神经网络的有效性很重要,但也是一个突出的开放问题。现在我们报告我们沿着这个方向所做的初步努力。在接下来的内容中,我们关注BGRU,因为它比其他方法更有效。

  为了解释BGRU的有效性,我们在图6中回顾了它的结构。对于每个SeVC和每个时间步,在激活层有一个输出(属于[0,1])。BGRU的输出是激活层最后一个时间步的输出;这个输出越接近1,SeVC越有可能被归类为易受攻击。对于SeVC的分类,我们识别在确定其分类中起关键作用的标记(即表示它们的符号)。这可以通过观察双标记时的步骤 ( t ′ , t ′ + 1 ) \left(t^{\prime}, t^{\prime}+1\right) (t,t+1)。我们发现,如果激活层输出对应的标记时间步 t ′ + 1 t^{\prime}+1 t+1明显(如0.6)(对比小的)大于相对应的激活层输出时间步 t ′ t^{\prime} t的标记,标记在时间步 t ′ + 1 t^{\prime}+1 t+1中扮演着一个关键的角色在分类SeVC是易受攻击的(相应地,不是易受攻击的)。此外,我们发现一些假阴性是由标记“if”或它后面的标记引起的,因为这些标记经常出现在不容易受到攻击的SeVCs中。我们还发现,一些误报是由与库/API函数调用及其参数相关的标记引起的,因为这些标记经常出现在易受攻击的SeVCs中。总之,

请添加图片描述

图6 BGRU的结构

  观点4。如果一个语法元素(例如,token)出现在易受攻击的(相对应,非易受攻击的)的SeVCs比出现在非易受攻击的SeVCs(相对应,易受攻击的)的出现频率更高。语法元素可能会导致假阳性(相对应,假阴性);这意味着语法元素的出现频率很重要。

3.4.3 回答RQ3的实验

  我们通过实验比较了(i)从容纳由数据依赖引起的语义信息的SeVCs中获得的8个模型和(ii)从容纳由数据依赖和控制依赖引起的语义信息的SeVCs中获得的8个模型的有效性。在这两种情况下,我们随机选择从训练程序中提取的30000个SeVCs作为训练集,从测试程序中提取的7500个SeVCs作为测试集。所有这些训练和测试集都对应于4种SyVCs,并与每种SyVCs的易受攻击与非易受攻击的SeVCs的数量成正比。

  表5总结了每个模型的 F P R FPR FPR设置为 2.0 % 2.0\% 2.0%的结果,因为 2.0 % 2.0\% 2.0%是达到最高 F 1 F1 F1-度量的模型的 F P R FPR FPR(即使用数据依赖性和控制依赖性的BGRU)。对于从适应数据依赖性的数据集学习的模型,我们观察到CNN和双向RNNs (即BLSTM和BGRU)比DBN和浅学习模型(即LR和MLP)更有效。与从只考虑数据依赖性的数据集中学习的模型相比,我们观察到,从同时考虑数据依赖性和控制依赖性的数据集中学习的模型平均可以提高 30.4 % 30.4\% 30.4% F N R FNR FNR 24.0 % 24.0\% 24.0% F 1 F1 F1-度量 。这可以用控制依赖性容纳了对漏洞检测有用的额外信息这一事实来解释。

表5 由数据依赖(简称DD)引起的语义信息有效性与由数据依赖和控制依赖(简称DDCD)引起的语义信息有效性的对比(度量单位:%)

请添加图片描述

  观点5。能够容纳更多语义信息(即控制依赖和数据依赖)的模型能够获得更高的漏洞检测能力。

3.4.4 回答RQ4的实验

  我们考虑从训练程序中提取的4种SyVCs对应的341536个SeVCs和从测试程序中提取的79091个SeVCs中学习BGRU,同时考虑由数据依赖和控制依赖引起的语义信息。我们将我们最有效的模型BGRU与商业静态漏洞检测工具Checkmarx和开源静态分析工具Flawfinder和RATS进行比较,因为(i)这些工具可以证明代表了最先进的漏洞检测静态分析;(ii)它们被广泛用于检测C/C++源代码中的漏洞;(iii)直接对源代码进行操作(即不需要编译源代码);并且(iv)我们可以使用它们。我们还考虑了最先进的系统VUDDY,它特别适合检测由代码克隆引起的漏洞。我们进一步考虑了VulDeePecker,我们考虑了所有4种SyVCs和SySeVR的数据和控制依赖关系。

表6 比较SySeVR框架中的BGRU和最先进的漏洞检测器(度量单位:%)

请添加图片描述

  表6总结了实验结果。我们观察到,启用SySeVR的BGRU在性能上大大优于最先进的漏洞检测方法。开源的Flawfinder和RATS具有较高的 F P R s FPRs FPRs F N R s FNRs FNRs。Checkmarx优于Flawfinder和RATS,但仍然有较高的 F P R s FPRs FPRs F N R s FNRs FNRs。众所周知,VUDDY会用高 F N R FNR FNR换低 F P R FPR FPR,因为它只能检测到与训练程序中的漏洞几乎相同的漏洞。启用SySeVR的BGRU比VulDeePecker更有效,因为VulDeePecker不能处理其他类型的SyVCs(例如FC),而且不能容纳由控制依赖引起的语义信息。此外,从更大的训练集(即341536个SeVCs)中学习的BGRU比从更小的训练集(30000 个SeVCs;见表3)更有效,特别是 F N R FNR FNR降低9.1%。总之

  观点6。启用SySeVR的BGRU比最先进的漏洞检测方法更有效。

3.4.5 BGRU在软件产品漏洞检测中的应用

  为了展示SySeVR在检测真实软件产品中的软件漏洞方面的有用性,我们应用3.4.4节中训练的SySeVR-BGRU来检测4种软件产品中的漏洞:Libav、Seamonkey、Thunderbird和Xen。每个产品都包含多个目标程序,我们从中提取它们的SyVCs、SeVCs和向量。对于每个产品,我们将启用SySeVR的BGRU应用到它的20个版本中,这样我们就可以知道在发布新版本时,供应商是否已经“悄悄地”修补了一些漏洞。

  如表7所示,我们检测到15个没有在NVD中报告的漏洞。其中,7个是未知的(即它们在这些产品中的存在直到现在还不知道),并且确实(根据我们的手动检查)与表7中提到的CVE标识符(CVE IDs)相似。出于道德考虑,我们没有给出这些漏洞的全部细节,但我们已经向供应商报告了这7个漏洞。其他8个漏洞在发布相关产品的新版本时已被供应商“悄悄地”修补。我们用来提取漏洞语法特征的Checkmarx除了Seamonkey 2.35和Thunderbird 38.0.1中的两个漏洞外,遗漏了所有这些漏洞,证明了它的无效。

表7 这15个被BGRU检测到但NVD没有报告的漏洞,包括7个未知的漏洞和8个已经被“悄悄地”修补的漏洞。

请添加图片描述

4 局限性

  目前的研究有几个局限性。首先,我们专注于检测C/C++程序源代码中的漏洞,这意味着可能需要调整框架以应对其他编程语言或可执行程序。其次,我们的实验聚焦了4种漏洞语法特征,覆盖了从SARD收集到的 93.6 % 93.6\% 93.6%的漏洞程序。这种覆盖并不完美,同时注意到SARD数据可能不能代表真实世界的软件产品。未来的研究需要识别更完整的漏洞语法特征。第三,可以改进生成SyVCs和SeVCs的算法,以容纳更多用于漏洞检测的语法/语义信息。第四,我们的实验使用单一模型来检测多种类型的漏洞。未来的研究应该研究以下哪一种更有效:是使用多个分别定制的模型来检测多种类型的漏洞,还是使用单一模型来检测多种类型的漏洞。第五,我们在切片级别检测漏洞(即语义上相互关联的多行代码),这可以改进为更精确地确定包含漏洞的代码行。第六,我们通过手动检查 0.6 % 0.6\% 0.6%的所有样本来生成基本真实标签,这些样本可能被我们使用的自动方法错误标记(由于缺乏基本真实数据集)。未来的研究应该研究更有效的自动标记方法;为此,可以利用联合训练的思想。第七,我们的实验表明,一些深度神经网络比最先进的漏洞检测方法更有效。虽然我们已经对解释“为什么”部分有了一些见解,但还需要更多的研究来解释深度学习在这一背景下以及其他背景下的成功。

5 相关工作

  先前的研究与漏洞检测相关。基于源代码的静态漏洞检测有两种方法:基于代码相似性和基于模式。由于基于代码相似度的检测方法只能检测到由代码克隆引起的漏洞,而SySeVR又是基于模式的方法,因此我们只回顾了基于模式的方法的研究,而基于模式的方法又可分为基于规则的方法和基于机器学习的方法。

  基于规则的方法使用漏洞模式来检测漏洞,其中的模式由人类专家手动生成(例如Flawfinder,RATS, Checkmarx)。这些工具通常会产生高的假阳性率和高的假阴性率,这也被我们的实验所证实(第3.4.4节)。例如,可以使用代码属性图来定义漏洞模式。相比之下,SySeVR使用的漏洞模式是自动学习的,并由深度神经网络表示。

  基于机器学习的方法,正如在其他地方所讨论的,可以进一步分为以下三个子类别。(i)基于软件度量的漏洞预测方法:这些方法建立在软件度量的基础上(例如导入和函数调用,复杂度,代码变动和开发人员活动),但在粗粒度上预测漏洞(例如组件级或文件级),这意味着它们不能确定漏洞的位置。(ii)异常检测方法:这些方法通过异常模式(例如API使用或缺失检查)发现漏洞,但无法应对很少使用但正常的模式。(iii)漏洞代码模式识别方法:这些方法提取与(例如)ASTs、代码属性图或系统调用相关的漏洞模式,并使用这些模式检测漏洞。这些方法需要人类专家定义特征,并使用传统的机器学习模型(如支持向量机和 k k k近邻)来检测漏洞。最近,深度学习被用于漏洞检测,同时减轻了手工定义特征的问题。Lin等人提出了一种自动学习函数的高级表示(即粗粒度)的方法。VulDeePecker是第一个展示了在切片级使用深度学习检测漏洞的可行性的系统,这比函数级要精细得多。最近的一个开发是 μ V u l D e e P e c k e r \mu VulDeePecker μVulDeePecker,它扩展了VulDeePecker,以检测多类漏洞。SySeVR克服了第1节中讨论的VulDeePecker的弱点,是第一个使用深度学习检测漏洞的系统框架。

  先前的研究与深度学习相关。深度学习已被用于程序分析。CNN已用于软件缺陷预测和定位源代码中的bug;DBN用于软件缺陷预测;RNN已用于漏洞检测软件溯源、代码克隆检测和二进制文件中的识别功能。目前的研究提供了第一个使用深度学习检测漏洞的框架。

6 结论

  我们提出了使用深度学习检测漏洞的SySeVR框架。基于我们收集的大量漏洞数据集,我们得出了一些观点,包括对深度学习在漏洞检测中有效性的解释。此外,我们检测到15个没有在NVD中报告的漏洞。在这15个漏洞中,有7个是未知的,并且已经报告给了供应商,另外8个在发布新版本时已经被供应商“悄悄地”修补了。未来的研究还有很多待解决的问题。除了解决第4节中讨论的局限性之外,研究代码复制对启用SySeVR的模型的影响也很重要。

致谢

  我们感谢审稿人提出的建设性意见,这些意见指导了我们对论文的改进。我们感谢Wang Sujuan和Jialai从NVD和SARD收集易受攻击的程序。来自华中科技大学和河北大学的作者得到了国家自然科学基金的部分资助。河北省自然科学基金(No. 61802106)部分资助项目(No. 61802106)。F2020201016.S.Xu获得了ARO基金#W911NF-17-1-0566和NSF基金#1814825和#1736209的部分资助。在这项工作中表达的任何意见、发现、结论或建议都是作者的观点,在任何意义上不反映资助机构的观点。


总结

  这篇论文的翻译就告一段落了,一共将近3万字,累死我了,希望读者可以从中获取到有用的知识。下篇论文翻译见,加油!

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IronmanJay

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值