本论文相关内容
文章目录
前言
本文是基于CNN-GAP可解释性模型的软件源码漏洞检测方法论文的阅读笔记,这是读到目前论文中唯一一篇中文论文,内容我个人觉得非常不错,很有借鉴价值,尤其是文中提出的关于源码漏洞的可解释性可视化分析的方法,对学习研究有很大的帮助。下面就随我一起好好欣赏这篇文章!
基于CNN-GAP可解释性模型的软件源码漏洞检测方法
作者信息
关键词
源代码漏洞检测;深度学习;神经网络可解释性
摘要
作者首先总结了近年来对于深度学习应用于源代码漏洞检测相关研究内容的局限性:
- 自定义标识符导致库外词过多
- 嵌入词向量的语义不够准确
- 神经网络模型缺乏可解释性
基于以上局限性,作者提出了一种基于卷积神经网络(CNN)和全局平均池化(GAP)可解释性模型的源代码漏洞检测方法。本文提出的漏洞检测过程如下:
- 对部分自定义标识符进行归一化的源代码预处理
- 采用One-hot编码进行词嵌入以缓解库外词过多的问题
- 构建CNN-GAP神经网络模型
- 识别包含CWE-119缓冲区溢出类型漏洞的函数
- 通过类激活映射(CAM)可解释方法对结果进行可视化输出
- 标识出可能与漏洞相关的代码
最终通过与VulDeePecker模型(第一篇论文精读)进行对比分析, 表明CNN-GAP模型能达到相当甚至更好的性能,且具有一定的可解释性。
1 引言
首先作者介绍了传统的源代码漏洞检测方法:基于静态分析的检测方法和基于动态分析的检测方法。
-
基于静态分析的检测方法:根据人工经验和专家知识构建相应的漏洞模式进行检测。其特点为:
- 代码覆盖率高
- 准确率低
- 漏报率高
其中包括:
- 基于逻辑推理的抽象解释和模型检验方法
- 基于抽象语法树、控制流图、数据流图等中间表示形式的程序分析方法
-
基于动态分析的检测方法:将源代码编译成二进制文件后运行程序进行检测。其特点为:
- 准确率高
- 效率低
- 开销大
- 适用于缺乏源码的二进制软件漏洞检测
其中包括:
- 模糊测试
- 符号执行
- 污点分析
同时作者总结了近期涌现的一些使用深度学习应用于源代码漏洞检测的框架,对这些框架的研习与总结对于之后的学习非常有帮助。
-
基于Bi-LSTM神经网络模型的漏洞检测方法
该方法利用抽象语法树表示函数源码,可实现跨项目函数的迁移表示和漏洞检测。此种LSTM框架可以实现对异构数据源在函数粒度上的漏洞检测。
-
基于深度学习和正样本模糊生成的漏洞检测系统 D e e p B a l a n c e DeepBalance DeepBalance
该系统通过生成“非真实”的正样本以平衡正负样本的数量,然后利用Bi-LSTM框架进行漏洞检测,提高了漏洞检测的准确率。
-
跨域漏洞检测系统 C D − V u l D CD-VulD CD−VulD
该系统利用度量迁移学习框架, 最小化源域与目标域之间的分布差异来学习跨域表示,并通过Bi-LSTM框架进行漏洞检测,大幅提高了漏洞检测的准确率。
-
基于Bi-LSTM神经网络模型的漏洞检测系统 V u l D e e P e c k e r VulDeePecker VulDeePecker
该系统将程序源码表示为语法、语义相关的代码小片段,通过Bi-LSTM神经网络模型实现了代码小片段粒度的漏洞检测。
-
漏洞检测系统$ \mu VulDeePecker 和 和 和VulDeeLocator$
这两种系统在 V u l D e e P e c k e r VulDeePecker VulDeePecker的基础上,分别实现了对多类型漏洞的检测和对漏洞位置的细粒度定位。
-
基于后端分类器-随机森林的漏洞检测系统
该系统通过卷积神经网络提取函数源码中的特征信息,并将提取的信息输入给后端分类器-随机森林以实现漏洞检测。
-
基于图神经网络的漏洞检测系统 D e v i g n Devign Devign
该系统将程序源码表示为代码属性图,然后利用图神经网络学习代码属性图中的语义信息进行漏洞检测。
-
基于注意力机制的漏洞检测工具 V u l S n i p e r VulSniper VulSniper
该工具将程序源码表示为代码属性图,并用144维向量来表示代码属性图中各节点之间的关系,通过注意力机制来捕捉关键的漏洞特征实现函数粒度的漏洞检测。
以上总结的各种基于神经网络的漏洞检测模型虽然可以从样本中学习到漏洞特征,但是仍有以下缺点:
- 难以从神经网络的黑盒模型中提取出分类特征对结果进行可解释性分析
- 难以生成PoC(Proof of Concept)来验证漏洞是否存在
- 无法从神经网络模型中提取出漏洞的分类特征
- 无法通过类激活映射(Class Activation Mapping, CAM)来定位更细粒度的漏洞位置
基于以上分析,作者认为当前针对基于深度学习的源代码漏洞检测存在以下困难和挑战:
- 如何提取程序源码中与漏洞特征相关的信息
- 如何缓解库外词过多的问题,同时保留程序信息的语义相关性
- 如何对神经网络模型的判定结果有一个合理的解释
针对以上困难和挑战,本文提出了一种基于卷积神经网络和全局平均池化的可解释性源代码漏洞检测方法:
- 以token序列作为程序信息的提取方式,对部分自定义标识符进行归一化
- 采用One-hot编码方式生成词向量,构建以卷积神经网络和全局平均池化(Convolution Neural Networks and Global Average Pooling,CNN-GAP)为框架的神经网络进行漏洞检测
- 通过CAM方式对检测结果进行可解释分析
通过对比实验发现,本文提出的CNN-GAP模型有如下优点:
- 精确率高
- 召回率高
- 泛化性能较好
- 可以实现漏洞函数代码行粒度的可解释性分析
2 漏洞检测模型设计
这部分是本文的核心内容,详细阐述基于CNN-GAP可解释性模型漏洞检测方法的设计思路。其中具体包括:
- 函数源码的预处理
- 可解释性神经网络模型的构建方法
2.1 函数源码预处理
对函数源码的预处理主要是处理源码中的自定义标识符,归一化的基本原则为:
- 用占位符代替原来的标识符
- 保留一些特殊的标识符,比如memcpy, strcpy, memset等内存操作和uint8_t, uint16_t等类型名
对源代码中的标识符进行归一化处理的作用如下:
- 可以缓解由于标识符命名多样化而造成语料库爆炸的问题
- 可以保留程序中与内存操作和变量类型相关的语义信息
归一化标识符是对函数名、变量名、字符串等进行归一化,归一化的过程如下:
- 通过正则表达式匹配出程序源代码中所有的函数名、变量名和字符串
- 用FUN+NUM替换函数名,用VAR+NUM替换变量名,用STRING+NUM替换字符串,最终得到归一化后的标识符
对源代码的归一化处理过程示例如下:
对源码的预处理不仅包括对标识符的归一化处理,还包括对分隔符的处理。根据分隔符的不同形式,可以建立3种分隔符的语料库,具体分隔符分布如下图所示:
- 只含有1个字符的分隔符22个
- 只含有2个字符的分隔符21个
- 只含有3个字符的分隔符2个
基于以上内容,可以总结对于函数源码预处理的过程如下:
- 通过定义好的分隔符将函数源码切分为token序列
- 将此token序列用One-hot编码方式进行词向量嵌入,整个One-hot语料库包括:
- 分隔符
- 归一化后的标识符
- 关键字
- 将每个token被编码成225维的词向量,因为语料库共225个词,其中包括:
- 3种类型的分隔符45个
- 归一化后的函数名20个、变量名20个、字符串10个、共50个标识符
- C/C++中的关键字、内存操作库函数名、特殊变量名共160个
- 最终将整个函数源码转换成形状为300×255的向量,为了得到固定形状大小的神经网络输入,需要将切分成token序列的函数源码进行截断或补齐:
- 当token数量超过300个时,丢弃超出的部分,只保留前300个token
- 当token数量不足300个时,用255维的零向量补足300个
2.2 神经网络模型构建
对于神经网络模型的构建应该遵循以下原则:
- 适配前端的输入形式
- 满足后端的输出需求(将输出的结果进行可解释性分析)
其中最重要的就是如何对模型的输出结果进行可行性解释,为了解决这个问题,本文采用卷积神经网络和全局平均池化型结合的模型对输入向量进行处理,如下图所示:
- 首先使用3层的卷积核在原始输入上进行采样,需要注意的是每层卷积核的数量为128个,每个卷积核的大小分别为6,5,4,移动步长均为1
- 然后将卷积层输出的特征图补全到原图的形状大小,目的是使特征图中的激活值能与输入原图对应
- 之后采用全局平均池化层将最后一个卷积层输出的每个特征图的激活值取平均
- 最后将这些均值的加权和作为 S o f t m a x Softmax Softmax层的输入
对于源码漏洞检测问题,我们的输入都是一维的列向量,所以CNN-GAP模型中的卷积模块采用1维卷积(Conv1D) 。在整个过程中,需要注意以下几个参数的含义:
- f k ( x ) f_{k}(x) fk(x)表示最后一个卷积层输出的第 k ( k = 1 , 2 , ⋅ ⋅ ⋅ , 128 ) k(k=1, 2, ···, 128) k(k=1,2,⋅⋅⋅,128)个特征图,
- x ( x = 1 , 2 , ⋅ ⋅ ⋅ , 300 ) x(x=1, 2, ···, 300) x(x=1,2,⋅⋅⋅,300)表示每一个特征图中的某一个经过卷积后的向量的激活值
- F k = ∑ x f k ( x ) F_{k}=\sum_{x} f_{k}(x) Fk=∑xfk(x)表示第 k k k个特征图,通过GAP(全局平均池化)后的结果
- W c , k W_{c,k} Wc,k代表第 k k k个特征图对应类 c c c的权重,也就是 F k F_k Fk对类 c c c的重要性
- S c = ∑ k W c , k F k S_{c}=\sum_{k} W_{c, k} F_{k} Sc=∑kWc,kFk表示对于类别 c ( c = 1 , 2 ) c(c=1,2) c(c=1,2)的 S o f t m a x Softmax Softmax层的输入
- P c = exp ( S c ) / ∑ c exp ( S c ) P_{c}=\exp \left(S_{c}\right) / \sum c \exp \left(S_{c}\right) Pc=exp(Sc)/∑cexp(Sc)表示最后类 c c c的 S o f t m a x Softmax Softmax输出
最后通过
P
c
P_c
Pc概率值的大小判断结果为类
c
c
c的概率,需要注意的是,在
S
o
f
t
m
a
x
Softmax
Softmax层忽略偏差项bias,也就是将
S
o
f
t
m
a
x
Softmax
Softmax层的偏差项设置为0。最后将
F
k
=
∑
x
f
k
(
x
)
F_{k}=\sum_{x} f_{k}(x)
Fk=∑xfk(x)代入
S
c
S_{c}
Sc,得到:
S
c
=
∑
k
W
c
,
k
∑
x
f
k
(
x
)
=
∑
x
∑
k
W
c
,
k
f
k
(
x
)
S_{c}=\sum_{k} W_{c, k} \sum_{x} f_{k}(x)=\sum_{x} \sum_{k} W_{c, k} f_{k}(x)
Sc=k∑Wc,kx∑fk(x)=x∑k∑Wc,kfk(x)
为了使模型的输出结果可解释,我们需要知道以下两点:
- 知道每个特征图对最后分类的重要性
- 知道特征图中每个位置 x x x对最后分类的重要性
所以使用
M
c
(
x
)
(
M
c
(
x
)
∈
R
)
M_{c}(x)\left(M_{c}(x) \in R\right)
Mc(x)(Mc(x)∈R)表示特征图中每个空间位置
x
x
x对类别
c
c
c的重要性,那么输入
S
o
f
t
m
a
x
Softmax
Softmax层的
S
c
S_c
Sc还可以表示为:
S
c
=
∑
x
M
c
(
x
)
S_{c}=\sum_{x} M_{c}(x)
Sc=x∑Mc(x)
由式(1)和式(2),可以得到:
M
c
(
x
)
=
∑
k
W
c
,
k
f
k
(
x
)
M_{c}(x)=\sum_{k} W_{c, k} f_{k}(x)
Mc(x)=k∑Wc,kfk(x)
上式表明了特征图中空间位置
x
x
x对划分为类别
c
c
c的重要性
M
c
(
x
)
M_{c}(x)
Mc(x),可以通过网络模型中的参数
f
k
(
x
)
f_{k}(x)
fk(x)和
W
c
,
k
W_{c,k}
Wc,k计算得出。根据以上内容,我们就可以通过计算每个token对分类的贡献度来实现漏洞的可解释性,这也是本文的最大贡献。同时我们也需要注意以下几个问题:
- 基于卷积神经网络和全局平均池化的可解释性模型可以适用于不同类型的漏洞
- 神经网络模型对漏洞特征的拟合度决定了token贡献度表征的准确性
- 漏洞可解释性的准确性受限于神经网络模型对漏洞检测的准确性,也就是说当模型对漏洞检测的准确性高的时候,漏洞的可解释性的准确性才会高
- 模型的准确性与数据集的大小紧密相关
同时,作者提到当前缓冲区溢出漏洞数据集比其他类型的漏洞数据集更丰富,所以本文主要针对缓冲区溢出类型的漏洞对模型的性能进行实验测试分析。
3 实验测试分析
本文提出的模型测试环境也很有参考价值,当我们复现代码的时候,最好和本文软硬件环境一致。
本文实验测试的硬件环境如下:
- CPU:20核Core™ i9-7900X 3.30 GHz
- 内存:48GB
- GPU:1块Nvidia RTX 2080Ti
本文实验的软件环境如下:
- 操作系统:Ubuntu 18.04 LTS
- 神经网络前端:keras
- 神经网络后端:tensorflow
本模型测试所使用的数据集来自: Russell论文中的数据集。其中包括以下几种类型的漏洞函数:
- CWE-119
- CWE-120
- CWE-469
- CWE-476
主要采用CWE-119缓冲区溢出类型漏洞作为基本数据集,共进行了三个实验。
3.1 实验一
实验1将CNN-GAP模型与Russell模型在CWE-119类型的数据集上进行对比,其中一些细节设置如下:
- batch size设置为128
- 使用binary_crossentropy作为损失函数
- 使用Adam优化器进行优化
在Russell测试集上的结果如表1所示,我们可以得到以下结论:
- 验出率(True Positive Rate, TPR)较低
- 误报率(False Positive Rate, FPR)较低
- 存在一定的漏报率(False Negative Rate, FNR)
- 精确率较高
- 此模型有效
图4为CNN-GAP模型和Russell模型在CWE-119类型漏洞函数进行实验测试的Precision-Recall对比图,我们可以得到以下结论:
- Russell模型在保持较高的检测精确率时,召回率低即漏报率高;当保持较高召回率时,其检测的准确率又低于0.5
- CNN-GAP模型不仅能够保持较高精确率,其召回率也不低
- CNN-GAP模型对CWE-119类型的漏洞检测效果优于Russell模型
同时作者也分析了产生以上结论的原因:
- Russell模型可能会将token替换为同一种占位符
- CNN-GAP模型不会将token替换为同一种占位符
由于本实验数据的正负样本不均衡,所以作者还采用ROC曲线图对CNN-GAP模型的性能进行评估,评估结果如图5所示。需要注意的是,曲线向下覆盖的面积即为AUC值,理想情况下AUC值为1.0,此时该分类器为完美分类器。可以看到CNN-GAP模型采用Russell测试集得到的ROC曲线,AUC值为0.95,接近1.0,说明模型整体性能较好。
3.2 实验二
实验2将已训练好的CNN-GAP模型与VulDeePecker模型,在VulDeePecker测试的CWE-119类型数据集上进行对比,以验证模型的泛化性能。对比实验结果如表2所示。
因为CNN-GAP模型直接在VulDeePecker模型的测试集上进行实验,两种模型的实验结果非常接近,由此可以说明CNN-GAP模型的泛化性能较好。那产生这样结果的原因是什么呢?
主要是因为VulDeePecker模型通过切片只保留了几种易出现漏洞的元素(如API调用、数组变量、指针变量等)及其控制流和数据流依赖,虽然精简了代码量,但是丢失部分代码特征。而CNN-GAP是对整个函数源码进行处理,不仅简化了预处理流程,而且包含了所有代码特征。这也说明了CNN-GAP模型的实用性优于VulDeePecker模型。
3.3 实验三
实验3将CNN-GAP模型输出结果用CAM进行可视化输出,实现对漏洞特征的可解释性分析。需要注意的是,虽然我们函数源码被切分为300个token序列,但是如果我们将这300个token序列全部进行分类结果的重要性表示,在分布图中就会十分拥挤,不便于展示和理解,所以实验3将GAP中的特征映射到函数源码的每条语句,而不是每个token。
3.3.1 堆溢出类型漏洞检测
图6的代码片段是CWE-119类型的堆溢出漏洞。 其中使用strncat函数将source所指向的字符串追加到data所指向的字符串的结尾时,由于strncat函数没有对source的大小进行检查,所以可能会超过data的内容大小,从而造成堆溢出漏洞。
图8为上述代码经过神经网络模型判别后,根据可解释性分析输出的可视化结果。其中数值大于0表示对应的代码行与漏洞相关,且该数值越大,贡献度越大。通过可视化结果图可以很清楚的看到行10—行13与漏洞特征相关,这几行正好对应我们刚才说的strncat漏洞代码、data堆内存、source[]数组。
3.3.2 栈溢出类型漏洞检测
图7的代码片段是CWE-119类型的栈溢出漏洞。其中使用memcpy函数将data内存中的字符串复制到dest内存区域中时,由于没有比较dest的内存大小和复制字符串的大小,所以会造成栈溢出。
通过图9的可视化结果分析来看,也可以很明显的发现第12,13行与漏洞特征相关,这几行代码包含了memcpy漏洞代码。
通过实验3可以在可视化的柱状图得知函数源码中漏洞的大概位置,实现在代码行粒度的漏洞定位,从而实现对源码漏洞特征的可解释性分析。
4 结束语
本文提出了一种基于CNN-GAP可解释性模型的源码漏洞分析方法,并对CWE-119缓冲区溢出类型的漏洞进行检测。本文的主要贡献如下:
- 通过CAM可解释性技术实现了结果的可视化输出
- 实现代码行粒度的漏洞定位
- 通过与其他漏洞检测模型的对比分析验证了CNN-GAP模型的有效性和泛化性
本文所提出模型的局限性如下:
- 虽然通过对自定义标识符进行归一化缓解了库外词过多的问题,但丢失了部分语义信息
- 在可解释性分析中只是将与漏洞相关的代码行进行了高亮显示,并没有对其根本原因进行分析
- 目前模型仅能识别CWE-119缓冲区溢出类型漏洞,还不能识别其他类型漏洞
针对以上局限性,本文提出的模型还可以从以下几个方面进一步完善:
- 采用更为完善的处理方式以减少语义信息的缺失
- 实现更为细粒度的漏洞函数定位分析,达到token粒度的可视化输出
- 使用CWE-120,CWE-469等类型的漏洞函数集对模型进行训练,使模型能实现多种类型漏洞的检测
总结
本文使用CNN(卷积神经网络)和GAP(全局平均池化)方法解决了源码漏洞特征的可解释性分析的问题。基本思路是首先将函数源码进行预处理,将源码中的自定义标识符进行归一化处理,从而缓解库外词过多的问题,然后进行神经网络模型的构建,本文提出的模型中包含了3层的卷积层,每层卷积层包含128个卷积核,最终每个卷积层生成128个特征图,将生成的特征图与其权重相乘输入给
S
o
f
t
m
a
x
Softmax
Softmax层,最终实现有无漏洞的判定。
本文所提出的方法的优点包括:
- 减少了库外词数量
- 可以保留代码相关的语义信息
- 可以实现源码漏洞定位
- 效率较高
- 泛化性较强
本文所提出的方法的缺点包括:
- 丢失部分语义信息
- 没有对和漏洞相关的代码行根本漏洞原因的分析
- 只能识别CWE-119缓冲区溢出类型漏洞,还不能识别其他类型漏洞