Slice-level程序表示
1. VulDeePecker
目标,识别程序中是否有漏洞。如果有,定位漏洞的位置(这个放到今天都算研究的热门话题)。实验用到的数据集地址:VulDeePecker,包括了61,638个code gadget。漏洞类型包括CWE-119和CWE-399。
1.1.程序表示code gadget
作者提出了一个code gadgets来作为程序中间表示。code gadgets是由一些代码行(一行一行的代码,VulDeePecker是基于code gadget分类的。一个目标程序可能包含多个code gadget,一个code gadget被分类为有漏洞,那么,该code gadget中所有的代码行都有问题)组成的程序声明,这些代码行之间有语义(数据流,控制流)相关。
code gadget的生成和key point有关,key point某种程度上是一个漏洞的中心点。key point可以但不限于是:
- library/API function calls(库函数调用,不当的库函数调用会造成漏洞)
- arrays(数组的不当使用)
- pointer(指针的不当使用)
key point和漏洞之间是多对多的关系,作者这里只研究library/API function calls与相关的code gadget。
示例代码:
void
test(char *str)
{
int MAXSIZE=40;
char buf[MAXSIZE];
if(!buf)
return;
strcpy(buf, str); /*string copy*/
}
int main(int argc, char **argv){
char *userstr;
if(argc > 1) {
userstr = argv[1];
test(userstr);
}
return 0;
}
产生code gadget的步骤如下:
1.1.1.提取library/API function calls和program slice
这里library/API function calls分为
- forward library/API function calls
forward library/API function calls指该函数调用直接从外部(命令行,socket,文件)接收输入。如recv
函数 - backward library/API function calls
backward library/API function calls并不直接从外部接收输入,比如上面示例中的strcpy
forward library/API function calls直接从外部接收输入很容易直接造成漏洞,而backward library/API function calls会受到与其参数有关的传参语句的影响。
program slice最初是表示程序中与一个变量或者程序点相关的语句,这里是与library/API function calls的参数相关的语句。
program slice也分为
- forward slice
forward slice对应于受到参数影响的语句,从forward library/API function calls中生成 - backward slice
backward slice对应于影响参数的语句,从backward library/API function calls中生成
以上面的示例代码为例,library/API调用是strcpy(buf, str);
,参数包括 buf
和 str
,buf
又与MAXSIZE
有关。str
是函数test
形参,在main
函数中userstr
作为实参传入test
,userstr又可以追踪到userstr = argv[1];
char *userstr;
2句。
所以program slice包括
test(char *str)
int MAXSIZE=40;
char buf[MAXSIZE];
strcpy(buf, str); /*string copy*/
main(int argc, char **argv)
char *userstr;
userstr = argv[1];
test(userstr);
2个backword slice,可以看到1个slice对应一个函数。而1个库函数调用可能会生成多个slice。
生成code slice的时候用到了checkmarx生成的数据依赖图(data dependency graph)
1.1.2.提取code gadget并给它们打上标签
将上面针对一个库函数调用提取到的每个slice拼接成code gadget
每个code gadget都必须打上标签。如果有漏洞,标1,反之,标0。
1.1.3.将code gadget符号化
该步骤的目的是在训练神经网络的程序中启发式地获取一些语义信息。
采用以下步骤
- 删除非ascii码字符和注释
- 用户自定义变量名映射
将用户自定义的变量名映射为VAR1, VAR2
这类符号化后的变量名 - 用户自定义函数名映射
将用户自定义的函数名映射为FUNC1, FUNC2
这类符号化后的函数名
1.2.code gadget向量化
首先将符号化后的code gadget用词法分析器解析为token序列,token种类包括identifiers, keywords,operators, symbols
。
比如,一个一行code gadget strcpy(VAR5,VAR2);
词法分析后就成了"strcpy" , "(" , "VAR5" , "," , "VAR2" , ")" , ";"
这会带来一个很大的token词库。这里用word2vec
将token向量化。(感觉fasttext
更好,可以利用符号化变量名的信息)
之后用 Bi-LSTM + Dense
解析token序列并分类。
这里作者将token序列的长度固定为 τ \tau τ
- 当token序列长度小于 τ \tau τ 时需要补0,对于由backword slice组成的code gadget在前端补0。 反之在后端补0。
- 当token序列长度大于 τ \tau τ 时需要截断,对于由backword slice组成的code gadget在前端截断。反之在后端截断。
2. SySeVR
SySeVR也是VulDeePecker团队的研究成果,主要针对VulDeePecker存在的以下问题
- 只考虑与库/API函数调用相关的漏洞
- 只利用由数据依赖(data dependency)引起的语义信息
- 只考虑被称为Bi-LSTM的特定RNN
- 不努力解释假阳性(false-positives)和假阴性(false-negatives)的原因。
作者这里提出了2个概念
- SyVCs:SyVCs反映了漏洞的语法特征
- SeVCs:SeVCs对SyVCs进行扩展,引入数据依赖(data dependency)和控制依赖(control dependency),反映了漏洞的语义特征
该框架合称为SySeVR:基于语法,语义和向量的表示。
在这篇paper中,作者用到了比VulDeePecker更大的数据集:SySeVR,包括了126种漏洞,VulDeePecker选择Bi-LSTM作为网络架构,SySeVR选择Bi-GRU。
2.1.提取SyVCs
该部分输入包括
- 程序源代码 P P P
- 漏洞特征集合 H H H
输出包括
- 一个由SyVC构成的集合 Y Y Y (SyVCs)。
定义1:
一个程序
P
P
P 由一系列的函数
f
1
,
.
.
.
,
f
n
f_1, ..., f_n
f1,...,fn 组成。而函数
f
i
,
1
≤
i
≤
n
f_i, 1 \leq i \leq n
fi,1≤i≤n 由一系列的 statement
s
i
,
1
,
.
.
.
,
s
i
,
m
i
s_{i,1}, ..., s_{i,m_{i}}
si,1,...,si,mi 组成。 而statement
s
i
,
j
,
1
≤
j
≤
m
i
s_{i,j}, 1 \leq j \leq m_i
si,j,1≤j≤mi 由一系列token
t
i
,
j
,
1
,
.
.
,
t
i
,
j
,
w
i
,
j
t_{i,j,1}, .., t_{i,j,w_{i,j}}
ti,j,1,..,ti,j,wi,j 组成。其中token类型包括 identifiers, operators, constants, keywords, 可由词法分析获取。
- P = { f 1 , . . . , f n } P = \{f_1, ..., f_n\} P={f1,...,fn}
- f i = { s i , 1 , . . . , s i , m i } f_i = \{s_{i,1}, ..., s_{i,m_{i}}\} fi={si,1,...,si,mi}
- s i , j = { t i , j , 1 , . . , t i , j , w i , j } s_{i,j} = \{t_{i,j,1}, .., t_{i,j,w_{i,j}}\} si,j={ti,j,1,..,ti,j,wi,j}
给定一个函数 f i f_i fi 的AST(可由其它方式生成)。AST每个叶子结点对应一个token t i , j , g t_{i,j,g} ti,j,g, 非叶子结点对应一个statement或者 s i , j s_{i,j} si,j 或者 s i , j s_{i,j} si,j 内部多个连续的token。
直观上,一个SyVC是一个token(对应于一个叶子结点)或者由多个连续的token(对应于一个非叶子结点)组成,如下图红框标出的部分。
定义2:
给定statement
s
i
,
j
s_{i,j}
si,j, 定义
- code element e i , j , z = { t i , j , u , . . . , t i , j , v } , 1 ≤ u ≤ v ≤ w i , j e_{i,j,z} = \{t_{i,j,u},..., t_{i,j,v}\}, 1 \leq u \leq v \leq w_{i,j} ei,j,z={ti,j,u,...,ti,j,v},1≤u≤v≤wi,j。
- 漏洞语法特征集合 H = { h k } 1 ≤ k ≤ β H = \{h_k\}_{1 \leq k \leq \beta} H={hk}1≤k≤β, β \beta β 为漏洞特征数量。 H H H 的生成用到了checkmarx
提取SyVCs采用以下步骤
可以看到用了一个三层循环
2.2.SyVCs转换为SeVCs
SyVCs转换为SeVCs需要用到program slice技术,这就用到了程序依赖图(PDG),PDG又用到了数据依赖图(data dependency)和控制依赖图(control dependency)。
2.2.1.定义
给定程序源代码 P = { f 1 , . . , f n } P = \{f_1, .., f_n\} P={f1,..,fn}, 对于函数 f i f_i fi。
-
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,1,..,ni,ci} 每个结点 n i , j n_{i,j} ni,j 表示一个statement或者一个control predicate(不太理解这个概念), E i = { ϵ i , 1 , . . . , ϵ i , d i } E_i = \{\epsilon_{i,1},..., \epsilon_{i, d_i}\} Ei={ϵi,1,...,ϵi,di} 中每个边 ϵ i , j \epsilon_{i,j} ϵi,j 表示结点之间的控制流向。
-
f i f_i fi 的数据依赖:给定 f i f_i fi 的CFG G i G_i Gi。 若其中2个结点 n i , j n_{i,j} ni,j 和 n i , l n_{i,l} ni,l 有下面关系, n i , l n_{i,l} ni,l 计算出的值在 n i , j n_{i,j} ni,j 中用到了。那么 n i , j n_{i,j} ni,j 就数据依赖于 n i , l n_{i,l} ni,l。
-
f i f_i fi 的控制依赖:这部分有点绕。给定 f i f_i fi 的CFG G i G_i Gi。若其中2个结点 n i , j n_{i,j} ni,j 和 n i , l n_{i,l} ni,l 有下面关系
- 从 n i , l n_{i,l} ni,l 到程序结尾的所有路径(对任意的)都会经过 n i , j n_{i,j} ni,j, 那么 n i , j n_{i,j} ni,j 后支配(post-dominates) n i , l n_{i,l} ni,l。
- 存在一条从 n i , l n_{i,l} ni,l 到 n i , j n_{i,j} ni,j 的路径,并满足(1) n i , j n_{i,j} ni,j 后支配路径上除了 n i , j n_{i,j} ni,j 和 n i , l n_{i,l} ni,l 的所有结点。(2) n i , j n_{i,j} ni,j 不后支配 n i , l n_{i,l} ni,l,则 n i , j n_{i,j} ni,j 控制依赖于 n i , l n_{i,l} ni,l。
-
f i f_i fi 的程序依赖图(PDG): f i f_i fi 的程序依赖图为 G i ′ ( V i , E i ′ ) G_i^{'}(V_i, E_i^{'}) Gi′(Vi,Ei′), V i V_i Vi 和CFG的 V i V_i Vi 一样。 E i ′ E_i^{'} Ei′ 中每条边为一个控制依赖或者数据依赖。
生成PDG时作者用到了Joern
- f i f_i fi 的SyVC中code element e i , j , z e_{i,j,z} ei,j,z 的前向切片(forward slice) f s i , j , z = { n i , x 1 , . . . , n i , x μ i } ⊆ V i fs_{i,j,z} = \{n_{i,x_1},...,n_{i,x_{μ_i}}\} \subseteq V_i fsi,j,z={ni,x1,...,ni,xμi}⊆Vi。其中的结点来自于从 结点 e i , j , z e_{i,j,z} ei,j,z(也是一个PDG的结点)开始能遍历到的所有结点
- f i f_i fi 的SyVC中code element e i , j , z e_{i,j,z} ei,j,z 的后向切片(backward slice) b s i , j , z = { n i , y 1 , . . . , n i , y v i } ⊆ V i bs_{i,j,z} = \{n_{i,y_1},...,n_{i,y_{v_i}}\} \subseteq V_i bsi,j,z={ni,y1,...,ni,yvi}⊆Vi。其中的结点来自于以结点 e i , j , z e_{i,j,z} ei,j,z(也是一个PDG的结点)结束的路径中的所有结点。
- code element e i , j , z e_{i,j,z} ei,j,z 过程间前向切片(interprocedural forward slice) f s i , j , z ′ fs_{i,j,z}^{'} fsi,j,z′ 通过函数调用可达的一系列结点
- code element e i , j , z e_{i,j,z} ei,j,z 过程间后向切片(interprocedural backward slice) b s i , j , z ′ bs_{i,j,z}^{'} bsi,j,z′ 中的每个结点可通过函数调用到结点 e i , j , z e_{i,j,z} ei,j,z。
SyVC的程序切片(Program Slice)
p
s
i
,
j
,
z
ps_{i,j,z}
psi,j,z 由
f
s
i
,
j
,
z
′
fs_{i,j,z}^{'}
fsi,j,z′ 和
b
s
i
,
j
,
z
′
bs_{i,j,z}^{'}
bsi,j,z′ 合并得到。
定义SeVC:
- 给定函数 f i f_i fi 中 statement s i , j s_{i,j} si,j 的code element e i , j , z e_{i,j,z} ei,j,z的 SeVC为 δ i , j , z = { s a 1 , b 1 , . . . , s a v i , j , z , b v i , j , z } \delta_{i,j,z} = \{s_{a_1,b_1},...,s_{a_{v_{i,j,z}},b_{v_{i,j,z}}}\} δi,j,z={sa1,b1,...,savi,j,z,bvi,j,z}, s a p , b q s_{a_p,b_q} sap,bq 与 e i , j , z e_{i,j,z} ei,j,z 存在控制或数据依赖
SeVC按如下算法产生
示例
2.3.SeVCs向量化
2.3.1.符号化
与VulDeePecker一样,采用以下步骤符号化SeVC
- 删除非ascii码字符和注释
- 用户自定义变量名映射
将用户自定义的变量名映射为V1, V2
这类符号化后的变量名 - 用户自定义函数名映射
将用户自定义的函数名映射为F1, F2
这类符号化后的函数名
2.3.2.向量化
和VulDeePecker一样,作者先用词法分析器将SeVC解析为token序列,之后用Word2Vec向量化token,之后用了多种神经网络模型(CNN,RNN,Bi-LSTM,Bi-GRU等)向量化序列并分类(分类是针对SeVC而不是Program或者function)
3.μVulDeePecker
针对VulDeePecker的改进主要是可以识别出code gadget包含的漏洞种类,并改进了code gadget的提取方式,并同时提取一个code attention。这里作者同样只分析库函数调用引起的漏洞(library/API function call)
相关概念
-
program statement 和 token
一个程序源代码 P = { p 1 , . . . , p ϵ } P = \{p_1, ..., p_\epsilon \} P={p1,...,pϵ}, p i p_i pi 是一个statement
p i = { t i , 1 , t i , 2 , . . . , t i , w } p_i = \{t_{i,1} ,t_{i,2} ,..., t_{i,w}\} pi={ti,1,ti,2,...,ti,w}, t i , j t_{i,j} ti,j 为1个token,token类型包括variable, function, constant, keyword ,operator等等。 -
数据依赖(data dependency,和SySeVR有点不一样)
给定一个程序源代码 P = { p 1 , . . . , p ϵ } P = \{p_1, ..., p_\epsilon \} P={p1,...,pϵ}, 一个variable token t i , j t_{i,j} ti,j 在statement p i p_i pi 中定义,若 t i , j t_{i,j} ti,j 在 p u p_u pu 被使用, 那 p u p_u pu 数据依赖于 t i , j t_{i,j} ti,j -
控制依赖(control dependence)
2个statement p i p j p_i \; p_j pipj。如果 p i p_i pi 的执行结果会影响 p j p_j pj 是否执行, 那么 p j p_j pj 控制依赖于 p i p_i pi -
code gadget
给定一个statement p v p_v pv,假设 p v p_v pv 中有 m m m 个函数调用。 f v i f_{v_i} fvi 是 p v p_v pv 中的一个函数调用。 f v i f_{v_i} fvi 的参数token 包括 { t v i , x 1 , . . . , t v i , x η } \{t_{v_i,x_1} , ..., t_{v_i,x_\eta}\} {tvi,x1,...,tvi,xη}。 statement p v p_v pv 中对应 f v i f_{v_i} fvi 的code gadget表示为 s v i s_{v_i} svi, 为statement集合 P P P 的一个子集合,其中每一个statement 要么数据依赖于一个参数token要么控制依赖于 p v p_v pv。 -
code attention
给定对应statement p v p_v pv 中函数调用 f v i f_{v_i} fvi 的 code gadget s v i s_{v_i} svi。 R = { r k } 1 ≤ k ≤ h R=\{r_k\}_{1 \leq k \leq h} R={rk}1≤k≤h 为描述漏洞语法特征的规则集合。code attention c v i c_{v_i} cvi 为 code gadget s v i s_{v_i} svi 中能够匹配的上 R R R 中一条规则的子集合(元素为statement)。
这里采用的是多分类, l a b e l = { 0 , 1 , 2 , . . . , m } label = \{0,1,2,...,m\} label={0,1,2,...,m}。 type 0 0 0 表示无漏洞, type i i i 表示第 i i i 种漏洞
提取code gadget的过程用到了SDG( System Dependency Graph),这个可由PDG获得
提取code gadget过程如下:
从code gadget 到 code attention
可以看到,code gadget包括了 1,2,3,4,5,6,7,8,9,10,15,16,17
行。而code attention只包括 2,4,5,6,7,8,16,17
行
训练阶段如下
检测阶段
可以看到针对code gadget的向量表示融合了code gadget和code attention
其它的处理过程和 VulDeePecker 大同小异