Group of Software Security In Progress

GoSSIP @ LoCCS.Shanghai Jiao Tong University

VulDeePecker: A Deep Learning-Based System for Vulnerability Detection

作者:Zhen Li, Deqing Zou, Shouhuai Xu, Xinyu Ou, Hai Jin, Sujuan Wang, Zhijun Deng, Yuyi Zhong

单位:Huazhong University of Science and Technology (华中科技大学)

出处:NDSS 2018

原文:https://arxiv.org/pdf/1801.01681.pdf

相关/images/2019-03-07:会议GitHub项目地址数据集来源1:NVD数据集来源2:SARD作者整理之后的数据集:CGD后续文章:SySeVR


一、背景

数据表明,截止 2010 年,CVE(Common Vulnerabilities and Exposures)数量接近 4600 个,到 2016 年,CVE 数量增长到 6500 个左右。软件出现漏洞的问题依旧大量存在,因此,如何有效的检测软件中存在的漏洞,是减少软件漏洞的主要解决方案之一。目前,已经存在较多的漏洞检测工具,例如开源项目: - FlawFinder - rough-auditing-tool-for-security - ITS4 - ClangStaticAnalyzer

商业型项目: - Checkmarx - Coverity - HP Fortify

学术型项目: - VDiscover - VUDDY - VulPecker

这些工具都属于静态的漏洞检测工具,存在一些共同的缺点:人力工作密集型 和 普遍的漏报率偏高。

二、提出的方法以及解决的问题

对于目前的漏洞检测解决方案,一方面大多依赖于专家知识(Experts)去定义特征(Features),然后再利用这些预先定义好的特征去检测代码中的漏洞,例如 CPAChecker 利用手工去编写配置文件(写检测规则),再利用这些规则去检测漏洞等。然而,手工去定义特征是一件繁琐而又主观的工作,并且所定义的特征因人而异,容易出错;另一方面容易导致过高的漏报率(相对于测试数据集),可能原因是为了降低检测过程中出现的误报率(因为误报率太高的检测工具其实是不可用的)。因此,基于上述的两个方面的不足,作者通过一些比较新颖的方法对这两个方面做一些改进,提出了一个基于深度学习(Deep Learning-Based)的漏洞检测解决方案 VulDeePecker(Vulnerability Deep Pecker)。该解决方案可以免去手工定义特征等繁杂的手工工作,并且在控制误报率不高的前提下,降低系统的漏报率(相对于测试集)。

三、技术方法

1、三个基本的设计原则(Guidding Principles)

  • (1)在基于深度学习的漏洞检测系统中,如何表示一个程序:

    由于深度学习或者神经网络的输入是以向量(Vectors)为单位的,因此,需要把程序转换为向量的形式保存。由于程序不能直接转换为向量表示,因此,这中间还需要一个过渡,作者使用一种叫做 Code Gadget 的表示方式来建立程序与向量之间的关系。而 Code Gadget 就是由少量的代码行(比如5到10行左右)组成的代码片段,并且所组成的这些代码行可以不是连续的,但是它们在语义上存在一定的关系(例如都是对同一个变量进行操作等),具体情况在后文有说明。

  • (2)程序的表示方式需要以怎么样的一个粒度(Granularity)才是比较合理的:

    由于漏洞检测工具不但需要检测出漏洞的存在,还需要能够告诉用户漏洞所出现的位置,而如果以太粗粒度的方式来表示程序(例如以函数为单位),那将不利于定位漏洞所在的位置,因此作者觉得需要以更加细粒度的方式来表示程序,就如前面所说的使用 Code Gadget 来表示程序就比较合适。

  • (3)选择一个怎么样的神经网络比较适用于漏洞检测:

    由于代码出现问题一般都会伴随一个上下文环境,单纯的一行代码出现问题的概率比较小,一般是有一行主要的代码出现问题,并伴随着前面相关的几行代码或者后面相关联的几行代码构成一个 Context 之后才会导致代码出现漏洞。因此,基于代码漏洞一般出现在一个 Context 中的情况,作者选择了 BLSTM(Bidirectional Long Short-Term Memory,双向长短期记忆网络) 神经网络来作为本文中所使用的网络。BLSTM 神经网络长得大概像图 1 所示,BLSTM 神经网络分为几个层次:输入层(输入向量)、多层次的 BLSTM 层、稀疏层(Dense layer)和 Softmax 层(输出层)。

    图 1

2、关键数据结构:Code Gadget

Code Gadget 是连接程序和向量之间的桥梁,在本文中是一个重要的概念,它的来源是二进制代码中的 Gadget。工程项目以源代码的形式给出,而神经网络需要向量作为输入,因此,程序与向量之间的转换就变得很关键。

Code Gadget 的定义:

Code Gadget 是若干条程序语句(Program Statements)的集合,例如,若干行的代码可以组成一个 Code Gadget,并且这些语句之间还需要有一定的语义上的联系,这些联系可以是数据流之间的依赖关系,也可以是控制流之间的依赖关系,在本文中使用的是数据流之间的关系。

为了生成 Code Gadget,作者定义了一个叫做 Key point 的概念,而在这篇文章当中,作者主要关注由于不切当使用 Library/API 函数调用所导致的漏洞,在这种漏洞类型中,Key point 就是这个特定的 Library/API 函数调用,通过这个 Key piont,再结合 Checkmarx 的数据流分析功能就可以得到对应的 Code Gadget。

3、VulDeePecker 的设计

图 2

如图 2 所示,VulDeePecker 分为两个核心部分:训练阶段和检测阶段。

训练阶段

在训练阶段,需要给大量的数据(训练程序)输入到 VulDeePecker 当中,这些数据既要有包含漏洞的代码,又要有不包含漏洞的代码,让 VulDeePecker 去学习它们特征。在训练过程中,又可以分为四个子步骤:

  • (1)提取 Library/API 函数调用和与它相关的代码片段。

    如图 3 所示,程序中出现了 strcpy 这个 Library/API 函数调用,然后使用商业软件 Checkmarx 的数据依赖图功能去提取相应的 Code Gadget,strcpy 有两个参数,每个参数会生成一个对应的 Code Gadget。

    图 3

  • (2)以函数调用的参数为单位,生成 Code Gadget 以及对应的标签(“1”:Vulnerable、 “0”:Not Vulnerablle),在测试数据集中是可以知道目标代码是否 Vulnerable 的。
  • (3)把 Code Gadget 转换成向量表示。转换之前还需要把变量名和函数名改写成一种统一的符号表示(例如 VAR1、VAR2、FUN1、FUN2等),不同的 Code Gadget 中可以重用这些符号,如图 4 所示。

    图 4

    在把符号表示的 Code Gadget 转换为向量之前,还需要对 Code Gadget 进行拆分,拆分为一个个 Token,再使用 word2vec 工具转换为向量表示。

  • (4)使用 BLSTM 神经网络训练数据(标准的训练过程)。

检测阶段

在检测阶段,对于一个给定的程序,首先也需要把它转换为向量的表示形式,然后再输入到 BLSTM 神经网络中进行检测,转换程序为向量的过程和训练阶段类似,只是在检测阶段不但需要输入转换之后的向量,还需要把训练阶段学习好的 BLSTM 网络也一起送入到检测模块中,检测结果会把目标 Code Gadget 标记/分类为 “1” (Vulnerable)或 “0” (Not Vulnerable),如果被标记为 “1”,则还会指出该漏洞所在的位置。

四、实验评估

1、实验环境

  • CPU:Intel Xeon E5-1260 @ 3.50GHz
  • 显卡:NVIDIA GTX 1080
  • 语言:Python,依赖包: TheanoKeras

2、本文关注的两种漏洞类型:

  • Buffer Error (BE),例如 CWE-119
  • Resource Management Error (RM),例如 CWE-399

3、实验数据来源:

  • NVD:包括软件产品中出现的漏洞,在这个数据集当中,每一个漏洞都有一个 CVE ID 和 CWE ID(Common Weakness Enumeration IDentifier)。

    最终从 NVD 中选出 520 个包含 Buffer Error 漏洞的开源的软件,320 个包含 Resource Management Error 漏洞的开源的软件。

  • SARD:包括软件产品中出现的漏洞、人工合成的漏洞和 Academic security flaws,这个数据集中的每一个 Test-Case 都有一个或多个 CWE ID 与之对应。

    最终从 SARD 中选出 8122 个包含 Buffer Error 漏洞的 Test-Cases,1729 个包含 Resource Management Error 漏洞的 Test-Cases。

4、数据集划分:

80% 的数据用来训练,20% 的数据用来测试。

5、衡量标准:

  • 误报率 FPR(False Positive Rate), FP(False Positive),误报率越小越好。

  • 漏报率 FNR(False Nagtive Rate), FN(False Nagtive),漏报率越小越好。

  • 真实率 TPR(True Positive Rate)/ 召回率 Recall, TP(True Positive),召回率越大越好。

  • 精度 P(Precision),精度越大越好。

  • F1 值,F1 值越大越好,

6、数据子集划分:

作者从 NVD 和 SARD 数据集中提取出 6045 个C/C++标准API调用(包括 Windows API 和 Linux Kernel API),从这些 API 中提取出 61638 个 Code Gadget 构成一个数据库 CGD (Code Gadget Database),CGD 中包括 6166401 个 Tokens,有 23464 个是不同的,然后再经过符号化之后,只有 10480 个不同的 Tokens。最后,作者使用这个 CGD 衍生出 6 个子数据集,如表 1 所示:

表 1

  • BE-ALL: CGD 中存在 Buffer Error 的子集,并且包含所有的 Library/API 函数调用,通过 Checkmarx 的数据流分析功能自动提取,无需手工干预。
  • RM-ALL: CGD 中存在 Resource Mangement Error 的子集,并且包含所有的 Library/API 函数调用,通过 Checkmarx 的数据流分析功能自动提取,无需手工干预。
  • HY-ALL:CGD 中存在 Buffer Error 和 Resource Mangement Error 的子集,相当于 CGD,并且包含所有的 Library/API 函数调用,通过 Checkmarx 的数据流分析功能自动提取,无需手工干预。
  • BE-SEL: CGD 中存在 Buffer Error 的子集,并且包含部分的 Library/API 函数调用,这些 Library/API 函数调用是通过手工选择出来的。
  • RM-SEL:CGD 中存在 Resource Mangement Error 的子集,并且包含部分的 Library/API 函数调用,这些 Library/API 函数调用是通过手工选择出来的。
  • HY-SEL:CGD 中存在 Buffer Error 和 Resource Mangement Error 的子集,且包含部分的 Library/API 函数调用,这些 Library/API 函数调用是通过手工选择出来的。

7、给 Code Gadget 打标签:

在 NVD 数据集中,由于包含了漏洞的 Patch 信息,作者只关注“通过删除或者修改某一行代码来修复 Patch ”的这类漏洞,并且自动的把这样的漏洞类型标记为 “1”,剩下的标记为 “0”,然后再通过手工确认标记为 “1” 的代码。而在 SARD 数据集中,每一个 Case 都已经被官方标记为 “good”(代码没有漏洞)、“bad”(代码包含漏洞) 和 “mixed”(有漏洞,但是也存在一份对应的 Patch 过的版本),因此,作者就把 “good” 分类 “0”,“bad” 和 “mixed” 分类为 “1”,然后再手工随机抽取 1000 个 Cases 进行确认,发现只有 6 个是标记错误的,但是这种只有少量标签打错的情况,神经网络可以自适应处理,因此,无需手工确认每一个 Case。

8、实验数据:

通过对神经网络的训练,调节好最佳参数之后,开始对数据集进行测试:

  • 首先对三个数据子集(BE-ALL、RM-ALL 和 HY-ALL)进行测试,测试结果如表 2 所示:

    表 2

    从这三个数据集的测试结果中看,误报率都很低;漏报率只有 RM-ALL 这个数据集好一点,BE-ALL 和 HY-ALL 的漏报率不是很理想;对所有数据集,TPR、P 值和 F1 值指标效果都挺好。其次就是说明 VulDeePecker 可以同时测试不同的漏洞类型(HY-ALL,混合漏洞类型)。

  • 然后对自动化生成的数据集和有人工干预生成的数据集进行测试,测试结果如表 3 所示:

    表 3

    从表中可以看出,有人工干预生成的数据集会对测试结构有明显的改善。HY-ALL 中的 Library/API 函数调用是通过自动化方式进行提取出来的,而 HY-SEL 中的 Library/API 函数调用是通过人工编写规则给 Checkmarx 进行提取出来的。(数据集是由 Code Gadget 组成,而 Code Gadget 是从 Library/API 函数调用中提取出来的)。

  • 对比其它工具,包括 FlawFinder、RATS、Checkmarx、VUDDY 和 VulPecker,以及针对不同的数据集的测试结果,如表 4 所示:

    表 4

    在表 4 中,上半部分对比的三个工具(FlawFinder、RATS 和 Checkmarx)都是基于模式匹配(Pattern-Based)实现的,下半部分对比的两个工具(VUDDY 和 VulPecker)是基于代码相似度(Code Similarity-Based)实现的。

    从表中可以看出,相对于 Pattern-Based 的三个工具, VulDeePecker 在各个方面都占据优势,其中,误报率和漏报率分别为:5.7% 和 7.0%。相对于 Code Similarity-Based 的两个工具,在 NVD 数据集中, VulDeePecker 的误报率是最高的,但是漏报率远低于它们二者,并且 F1 值也是远高于它们二者,总体效果比它们二者要好;在 SRAD 数据集中,它们二者无法进行测试,而 VulDeePecker 所取得的效果也很好,各项指标都很接近完美值。(VUDDY 不能测试该数据集的原因是:它需要 diffs 作为输入,也就是既包括有漏洞的代码,又包括对应的 Patch 版本)。

    在测试 NVD 数据集的时候, VulDeePecker 的误报率和漏报率都较高,作者觉得这可能是因为 NVD 的训练数据集太小造成的(只有 SARD 数据集的 1/18),

9、测试真实软件:

作者除了对数据集中的数据做测试之外,还取了三个实际的项目进行测试,分别是 Xen、Seamonkey 和 Libav,对这三个不同的软件,作者收集了它们各自 20 个不同的版本进行测试,结果发现 4 个漏洞(还没有在 NVD 中公开的),但是这 4 个漏洞在它们的后续版本中都是已经被对应的厂商悄悄(“Silently”)的 Patched 了的。但是对于其它几个工具(作者没说是哪几个。。。。),只有 FlawFinder 找到了 1 个漏洞。

表 5

10、性能测试:

如表 6 所示,作者给出了 VulDeePecker 的训练时间和测试时间,训练时间远大于检测时间,这也是机器学习中出现的很正常的结果。还有一个数据是平均生成一个 Code Gadget 的时间: 354 毫秒(这个时间依赖于数据流分析工具,例如 Checkmarx)。

表 6

五、优缺点

优点:

  • 深度学习本来是被用来处理自然语言、图片以及语音识别等,而作者把深度学习移植到漏洞检测上,宣称是第一个使用深度学习方法来检测代码漏洞的团队,是深度学习检测代码漏洞的开端。
  • 通过深度学习方法,可自动去寻找漏洞的特征,免去繁杂的手工定义特征工作。
  • VulDeePecker 可以同时检测多种漏洞类型,相对于一次检测一种漏洞类型,效果会差一点,但是差别不是特别大。

缺点:

  • 虽然 VulDeePecker 不需要手工去定义特征,但是在训练阶段也需要较多的精力去调节参数,使得训练出来的神经网络效果最优。
  • 在给 NVD 中的数据做标记的时候,由于作者把不是”通过修改一行代码或者是删除一行代码”来修复漏洞的例子全部标记为 “0”,这其实是会造成错误的,也就是说,作者只是保证被标记为 “1” 的代码一定是有漏洞的,而没有保证被标记为 “0” 的代码一定是没有漏洞的。
  • 对测试数据集的说明不是很清楚,似乎是和训练数据集重合的,不知道是不是我没理解清楚作者的意思,因为刚开始的时候作者说了把数据集划分了 80% 的训练集 + 20% 的测试集,然后又说 BE-ALL、RM-ALL等都是从总数据集 CGD 中衍生出来的,最后测试的时候又是在 BE-ALL、RM-ALL 等数据集上做,所以感觉有点迷惑。
  • VulDeePecker 只能从数据流中生成 Code Gadget,而不能从控制流中生成 Code Gadget,如果二者都支持,那就可以生成更多的 Code Gadget,训练生成的神经网络可能会更好。
  • VulDeePecker 只支持 C/C++ 代码中的漏洞检测,不支持其它语言。

六、总结和看法

作者提出了 VulDeePecker,一个基于深度学习的漏洞检测系统,该系统旨在替代手工去定义特征这种繁杂而又容易出错的工作和减少漏洞检测系统的漏报率。由于深度学习本来不是被用来做漏洞检测的,要想利用深度学习来做漏洞检测,就必须要要设计一些基本原则,让目标程序能够被表示成深度学习可以使用的数据(例如:向量表示),因此,作者通过定义一些基本规则,使得程序能够转换成向量的表示形式,并利用这些规则生成一个可用的数据库 CGD,同时还把 CGD 公开发布到 github 上,供大家使用。作者通过对 3 个真实软件进行测试,检测出软件中的 4 个漏洞。与之比较,其它几个工具中,只有 FlawFinder 找到了 1 个漏洞,其它漏洞都被忽略了。

感觉这篇文章在测试真实的那几个软件的时候有意回避了一个问题,作者只说在这 3 个软件中检测出了 4 个漏洞,但是没说 VulDeePecker 找到了多少漏洞,也就是说,它可能会报告多个漏洞(虽然这些报告中也包括那 4 个漏洞,但是可能误报率很高);其次,作者把数据的 80% 划分了训练集,20% 划分为测试集,但是在对子数据集进行实验的时候似乎没有明显区分出训练集和测试集,比如 HY-ALL、HY-SEL 等,我怀疑作者是否是在同一个子集上对数据进行训练和测试的(例如:在 HY-ALL 子集上进行训练,然后又在 HY-ALL 子集上进行测试),因为在测试结果中似乎看不出来作者对训练集和测试集的划分。