首先需要找到适用于深度学习的软件程序的表示, 论文还有code gadgets来表示程序并将其转换成向量(code gadgets在向量中是许多行(不一定连续)的, 语义相关的代码). 并据此设计实现了VulDeePecker检测系统. 评估结果表明, 在使用第一类漏洞数据集时在误报率合理的条件下有着更小的漏报率.

论文的主要主要贡献

  • 开启了使用深度学习检测漏洞的先河
  • 初步确定了: 软件程序的表示, 检测粒度, 特定神经网络的选用
  • 设计并实现了VulDeePecker. 并根据以下方面进行评估
    • VulDeePecker可以同时处理多类漏洞吗? -> VulDeePecker使用漏洞模式(vulnerability patterns, 由神经网络训练学得)
    • 人类的专业知识可以帮助提高VulDeePecker表现吗?
    • VulDeePecker同其他漏洞检测方法相比效率如何?
  • 提出了第一个用于评估VulDeePecker和其他漏洞检测系统的数据集

论文结构:

  • 第二节讲述一些初步的原则
  • 第三节讨论VulDeePecker的设计
  • 第四节描述VulDeePecker的实验评估结果
  • 第五节讨论VulDeePecker的局限以及未来研究的一些问题
  • 第六节讲述前人相关工作
  • 第七节对论文进行总结

第三节 VulDeePecker的设计原理

A. 定义code gadget

(Code gadget)一个code gadget由大量数据或控制上的语义相关的语句组成.

为了生成code gadget我们提出key point的启发式概念, 它是一个”镜头”, 可以从特定角度来表示程序. 直观上看, key point的启发式概念, 从某种程度上说, 就是漏洞的中心, 或者就是说暗示有漏洞存在的代码片段. 对于由库/API调用的不当使用而造成的漏洞, key point就是指这些库/API函数调用. 对于数组不当使用造成的漏洞, key point就是这些数组.

值得注意的是, 一种类型的漏洞, 也许会包含有多种类型的key points. 例如, buffer error漏洞可对应以下key point: 库/API调用, 数组和指针. 一个key point也可以存在于多种类型的漏洞.

论文中仅关注特定的key point - 库/API函数调用. 对应于库/API函数调用, code gadget可以通过程序的数据流/控制流分析进行生成, 这也已经有许多著名的算法[23], [50]以及可用的商业产品如CheckMarx可以用来生成.

B. 概览 VulDeePecker

VulDeePecker有两个阶段: 训练阶段和检测阶段. 训练阶段给的输入是大量的训练程序, (一些是存在一个或多个已知漏洞的, 而另一些则是安全的程序.)

训练阶段包含4个步骤

步骤1 提取库/API函数调用以及对应的程序切片

这也包含两个子步骤

  • 步骤 1.1: 从训练用的程序中提取库/API函数调用. (VulDeePecker目前仅关注与key point相关的漏洞)
  • 步骤 1.2: 从步骤1.1中提取出的库/API函数中, 为每一个参数(或变量)提取一个或多个程序切片. 本论文中, 一个程序切片表示程序的语句(例: 各行代码), 这些语句在是跟库/API函数调用的参数语义相关的. 注意, 程序切片这个概念最初只是为了表示程序中有关程序点或变量的语句而引入的. [55]

步骤2 生成训练用程序的code gadget和标记对应的真实值

  • 步骤 2.1: 汇编步骤1.2获取到的程序切片为code gadgets, 每个code gadget对应一个库/API函数调用. 一个code gadget并一定对应于一系列连续的代码行. 相反, 它包含的是语义相关的多行代码(例如, 继承了编码在那些程序切片中的语义关系)
  • 步骤 2.2: 标记code gadget的真实值. 标记为1表示存在漏洞, 0表示安全. 标记是可行的, 因为我们知道训练用的程序哪些是存在漏洞的哪些不是. 如果存在漏洞, 我们也知道漏洞所在的位置.

步骤3 将code gadget转变为向量表示

  • 步骤 3.1: 转换code gadget为确定的符号表示. 该步骤目的在于保留训练用程序的一些语义信息
  • 步骤 3.2: 对步骤3.1获得的符号表示, 将code gadget编码为向量, 作为训练BLSTM神经网络的输入. (通常情况下想要使用神经网络都得这么干)

步骤4 训练BLSTM神经网络

将code gadget编码为向量并标记好真实值后, 就是标准的训练BLSTM神经网络的过程

检测阶段

给定一个或多个目标程序, 我们提取它的库/API函数调用以及对应的程序切片, 并将程序切片汇编为code gadget. code gadget转化成符号表示再编码为向量输入给训练好的BLSTM神经网络. 神经网络会输出哪些向量, 也就是哪些code gadget是存在漏洞的(“1”)或不存在漏洞(“0”). 如果一个code gadget存在漏洞, 也就能确定下来目标程序中的漏洞位置.

步骤5 转化目标程序为code gadget和向量

  • 步骤 5.1: 从目标程序提取库/API函数调用
  • 步骤 5.2: 根据库/API函数调用的参数提取出程序切片
  • 步骤 5.3: 将程序切片汇编为code gadget
  • 步骤 5.4: 转化code gadget对应的符号表示
  • 步骤 5.5: 将code gadget的符号表示编码为向量

步骤6 检测

使用训练好的BLSTM神经网络对目标程序提取得到的code gadget对应的向量进行分类. 如果分类结果是1, 表明存在漏洞, 标记为0, 表明不存在漏洞.

C 步骤1 提取库/API函数调用和程序切片

步骤 1.1: 提取库/API函数调用

将库/API函数分为两类: 前向和后向. 前向函数调用指那些直接从外部输入(例如, 命令行, 其他程序, socket, 或文件)中接收一个或多个输入的函数调用. 例如 recv函数调用就是一个前向函数调用, 因为它直接从socket中接收数据. 后向函数调用就是那些不从程序运行所在的环境中直接接收任何外部输入的函数调用. 例如strcpy

对于前向函数调用, 受输入参数影响的语句是危险的, 因为它们有可能因为不合适(精心构造)的参数值而产生漏洞. 对与后向函数调用, 能够影响参数值的语句也是危险的, 因为它可能使得函数调用具有漏洞.

步骤 1.2: 提取程序切片

该步骤生成对应于从目标程序中提取到的库/API函数调用的参数的程序切片. 我们定义两类切片: 前向和后向切片

前向切片对应于受参数影响的语句, 而后向切片对应于能够影响参数的语句. 我们使用商业软件CheckMARX的数据依赖图功能来提取这两类切片. 它的基本思路是如下这样:

  • 对于每个在前向库/API函数调用中的参数, 会生成一个或多个的前向切片, 而这些前向切片则对应于与参数相关的切片在库/API函数调用时或调用之后进行分支的情况.
  • 对于每个在后向库/API函数调用中的参数, 会生成一个或多个的后向切片, 而这些后向切片则对应于有多个与参数相关的切片在库/API函数调用时或之前进行合并的情况

注意. 一个程序切片包含有多个属于不同的用户定义的函数里的语句. 也就是说, 切片可以超出用户定义函数的边界. Checkmarx使用chains来表示切片, 但切片是也可以用树进行表示[23], [50], 因为线性结构仅能表示一个独立的切片, 而一个库/API函数调用常常对应于多个切片.

D 步骤2 提取code gadget并标记真实值

步骤 2.1: 将程序切片汇编成code gadget

首先, 给定一个库/API函数调用以及对应的程序切片, 我们可以将属于同一个用户定义消息函数的语句, 根据它们在用户定义函数中的出现次序组合成一个片段里(会对语句去重)

步骤 2.2: 标记真实值

每一个code gadget都需要标记为1(存在训练数据集中已知的漏洞)或0(不存在)

E 步骤3 转化code gadget为向量

步骤 3.1 转化code gadget为它们的符号表示

这一步目的在于启发式地捕获一些程序中的语义信息用于训练神经网络.

首先, 移除非ASCII字符和注释, 因为这些跟漏洞没有关系.

然后, 一对一地映射用户定义的变量到符号名(比如, “VAR1”, “VAR2”), 这里需要注意的是, 当变量在不同的code gadget中出现时, 多个变量可以映射到同一个符号名中.

其次, 一对一地映射用户定义的函数名到符号名(比如: “FUN1”, “FUN2”). 同样, 在不同的code gadget中出现, 多个函数名也可以映射到同一个符号名中.

步骤 3.2: 将符号表示编码为向量

我们将code gadget的符号表示经过词法分析拆分成token序列, 包括标识符, 关键字, 运算符, 和符号.

例如 strcpy(VAR5, VAR2);可以由7个token进行表示

"strcpy", "(", "VAR5", ",", "VAR2", ")", ";"

这样可以得到大量的token. 为了将这些token转换成向量, 我们使用word2vec(广泛用于文本挖掘)工具[14]. 该工具基于词向量(distributed representation), 这是一种将token映射成一个整型数(这个整型数会在随后转换成固定长度的向量)的想法

因为code gadget也许含有不同数量的token, 对应的向量也就会有这不同的长度. 因为BLSTM值接收等长向量作为输入, 因此我们需要作出调整. 为了达到这个目的, 我们引入了一个参数 $\tau$ 作为对应于code gadget的固定长度的向量

  • 当一个向量短于 $\tau$ , 会有两种情况: 如果code gadget由后向切片生成, 或是由多个后向切片组合而成, 那么我们就会在向量的首部填充0, 否则在向量的尾部填充0
  • 当向量长于 $\tau$ , 也有两种情况: 如果code gadget由后向切片生成, 或由多个后向切片组合而成, 我们会删去向量的首部, 否则删除向量的尾部.

这能够确保每一个由后向切片生成的code gadget的最后一个语句是一个库/API函数调用, 而每一个由前向切片生成的code gadget则也是一个库/API函数调用. 结果就是, 每一个code gadget都由一个 $\tau-bits$的向量表示. 向量的长度与BLSTM每一层的隐藏结点数量有关, 这也是一个可以调整的参数来提高漏洞检测的准确率.