这是一篇用于检测程序哪里有漏洞的论文,目的在于假阴性率低,很遗憾没有给出源码。
这一项目采用了由国家标准和技术研究所 (NIST) 和软件保证参考数据集 (SARD) 放出的数据集。
最近的两款软件,VUDDY和VulPecker,假阴性率高而假阳性率低,用于检测由代码克隆引发的漏洞。而如果用于非代码克隆引起的漏洞则会出现高误报率。
本文使用深度学习处理程序中的代码片段,不应由专家来手动定义特征,在不产生高假阴性率,假阳性率适当,能够判断是否有漏洞,并定位漏洞位置
VulDeePecker的效果:能够同时检测不止一种漏洞,可以结合人类知识进一步提高有效性(不是定义特征)。
1.程序可以先转为一种中间表示,可以保留 (某些) 程序元素之间的语义关系 (例如,数据依赖和控制依赖关系)。然后再转化为向量表示输入神经。
2.程序片段粒度应当比函数小
3.由于一行代码是否包含漏洞可能取决于上下文,因此可以处理上下文的神经网络可能会更适合。
RNN有效被用于过程序分析(非漏洞检测),且会受消失梯度 (VG) 问题,同理BRNN
长期短期记忆 (LSTM) 细胞和门控轮回单元 (GRU) 细胞,由于 GRU 在语言建模上不超过 LSTM ,
本文选择了LSTM,但没有比较二者。由于LSTM是单向的,使用了双向LSTM(BLSTM)
直观地说,关键点的启发式概念在某种意义上可以看作是漏洞的 “中心” 或暗示漏洞存在的代码片段。对于库/API 函数使用不当造成的漏洞,调用的关键点是库/API 函数调用; 对于由于数组使用不当而导致的漏洞,关键点是数组。重要的是要注意,一种类型的漏洞可能有多种关键点。例如,缓冲区错误漏洞可能对应以下关键点: 库/API 函数调用、数组和指针。此外,在多种类型的漏洞中可能存在相同的关键点。例如,两个缓冲区错误资源管理错误漏洞可能包含库/API 函数调用的关键点。精确地定义关键点的启发式概念超出了本文的研究范围,成为未来研究的一个有趣问题; 我们专注于使用这个启发式概念作为 “镜头”,使用深度学习来学习漏洞模式。
代码片段可通过数据流或控制流分析程序生成,有一些算法和商业产品(如Checkmarx)。这些代码片段不一定是连续的代码行,而是语义上有关联的代码组成的。对每个代码片段标记是否脆弱,
网络的检测:给定程序,进行切片,获得代码片段,网路输出哪些片段是脆弱的,它们的下标会指引漏洞位置。
前向切片和后向切片:程序切成小片段的方法,对于一个库API函数,每个参数生成一个函数切片,然后将这些函数切片组合成代码片段,
等长输入处理:向量小于定长时,如果代码片段是从后向切片生成的,或者通过组合多个后向切片生成的,我们在向量的开头填充零; 否则,我们将零填充到向量的末尾。
向量大于定长时,如果代码片段是从后向切片生成的,或通过组合多个向后切片生成,我们删除向量的开始部分; 否则,我们删除向量的结束部分。
TP 是正确检测到漏洞的样本数,FP 是检测到错误漏洞的样本数,FN 是未检测到真正漏洞的样本数, TN 是没有漏洞未检测到的样本数。假阳性率=FP/(FP+TN),假阴性率FNR=FN/(TP+FN),真阳性率(召回率)TPR = TP/(TP+FN),
### 总结
目前只处理C/C++程序,将来可能处理其他的。设计上只处理API相关漏洞
总的来讲,本工具使用了BLSTM,基于NIST和SARD所放出来的数据集,分类了有漏洞的代码片段和安全的代码片段,并依此来定位漏洞点,从而发现有漏洞的位置
clang是一套结构化编译器的前端,llvm是一个底层虚拟机后端,整体组成了一个编译器架构。clang进行词法分析和语法分析,然后交由llvm进行目标代码的生成。
clang的静态分析很大程度上通过AST(Abstract Syntax Tree ,抽象语法树 )来展现,clang的模块化结构清晰,代码相对简单,所以很适合进行功能的扩展,因此选用了clang来进行源码分析和插桩。
前文提到了clang的源码插桩,本文来对源码插桩做一些辅助的分析。
在使用clang生成函数调用图时曾分析过,clang的函数调用图生成无法识别不同源文件的函数,这在对软件进行插桩的时候就会遇到很大的问题,所以需要自己实现这部分功能。具体思路为在找到函数体的定义时,记录下函数名称,并在之后Stmt块遍历时,如果是CallExpr,即函数调用关系,则记录下调用的函数,形成函数的一对调用关系。当遍历完全部的函数之后,即生成了全部的函数调用关系。
为了之后插桩后能够用的较为方便,需要获取一些函数与插桩点间的关系
首先需要函数与插桩点的对应关系,即获悉哪些插桩点是属于哪些函数的,方便进行粗粒度(函数层面)和细粒度(插桩点层面)的分析。具体思路为在找到函数体定义时记录下当前的插桩点下标。
然后还要获得函数调用的信息,即在程序运行起来后是否成功执行到了函数调用,具体思路为在Stmt块插桩的基础上,添加在函数开始位置的插桩点,并记录产生函数调用时的插桩点下标,标记基本块与函数调用图之间的关系。
插桩点并不是都能够在一次运行中被覆盖到的,比较明显的就是有些插桩点之间有并列的关系,如if-else结构和switch-case结构,只能选择其中的一条路径,而其他的路径就不能达到。因此需要将这些并列的情况分析出来,方便进行基本块间的结构分析。
首先是对于if-else结构的处理:如果If->getElse()是存在的,那证明这是if-else并还没有结束,需要记录并继续访问;如果是else结构或else if的最后一个,则需要标识为结束位置。
Stmt *EL = If->getElse();
if (EL && !isa<IfStmt>(EL)) //else
{
ChooseOne(EL,-1);//end
InstrumentStmt(EL, flag);
}
else if (EL){
ChooseOne(EL,0);
IfStmt *IfEL = cast<IfStmt>(EL);
if(!IfEL->getElse()) ChooseOne(EL,-1);//end
}
然后是对于switch-case结构的处理。原先在找到switch-case结构时使用的是while循环来处理,即找到SwitchStmt的时候就直接对其中的CaseStmt和DefaultStmt进行处理。好处在于这样做在代码上看着比较简洁,但问题在于整体处理结构上与别的模块差别较大,一开始只有插桩时还好,后面添加源码分析就需要每个功能都再针对写一遍,很麻烦,所以将Switch-case结构分为了对SwitchStmt、CaseStmt和DefaultStmt的处理。
clang提供了源码分析器的命令,可以获得一些分析结果通过clang -cc1 -analyze进行分析
-analyzer-checker-help查看分析器,其中可通过analyzer-checker=xxx的方式进行使用, 如=
debug.ViewCFG能够获得程序的控制流图(基本块的), debug.ViewCallGraph能够得到函数调用图(没有实体的函数不在其中), debug.Stats显示基本块的可达情况 debug.DumpCalls,显示函数调用情况,不区分函数的,如果函数间存在调用关系才会以缩进的形式体现 上述的这些View都可以换成dump来获得文字版,View会生成dot结尾的图,可以通过Graphviz 来生成可视化的图,其命令为dot xx.dot -Tpng -o xx.png 。但ubantu下生成的图导到window电脑上就打不开了不知道为什么;后来又打开了,是因为太大了一开始没加载出来
而AST的生成可以不通过分析器而直接使用,如-ast-view可以生成ast的图,
Clang对于函数调用的处理不够完整,对于特殊情况的函数调用处理不好,如
extern 函数调用,无法获知函数体,不会创建到CallGraph中
函数指针调用,无法获知函数体,不会创建到CallGraph中
模板函数调用,在编译期可以获知进行模板实例化,会添加到CallGaph中
且调用图上没有在别的c文件中定义的函数(.h中声明的),即没有正文的函数
CFG控制流图
CallGraph函数调用图
AST图
Stats
warning: main -> Total CFGBlocks: 9 | Unreachable CFGBlocks: 0 | Exhausted Block: no | Empty WorkList: yes
DumpCalls
scanf("%s", buf)
Returning conj_$3{int}
func1(3)
Returning void
func2((char *)buf)
strcpy(s, str)
Returning &SymRegion{conj_$9{char *}}
func(5)
Returning void
Returning void
func1(3)
Returning void
func(2)
Returning void
aa(1)
Returning conj_$13{int}
func(1)
Returning void
func(0)
Returning void
aab(8)
Returning conj_$17{int}
printf("a==4")
Returning conj_$3{int}
printf("a==3")
Returning conj_$6{int}
printf("a==2")
Returning conj_$9{int}
printf("a==1")
Returning conj_$12{int}
ab(a)
printf("in ab")
Returning conj_$15{int}
Returning 0 S32b
所分析的源码
#include <stdio.h>
#include <string.h>
#include "11.h"
int global=1;
void func (int i){
if (i==0) global=0;
else if (i==1){
global +=2;
global *=2;
}
else{
global*=3;
}
}
void func2(char* str){
char s[20];
strcpy(s,str);
global +=10000;
func(5);
}
void func1(int i){
while(i<5){
i++;
global/=2;
}
}
int main()
{
UINT8 buf[0x200];
int mark = 20;
scanf("%s",buf);
switch(buf[0]){
case '0':
func(0);
aab(8);
break;
case '1':
aa(1);
func(1);
break;
case '2':
func(2);
break;
case '3':
func2((char*)buf);
default:
func1(3);
}
return 0;
}
所有生成的相关文件会放到github的clang中,包括clang的分析命令和分析器的一些选项。 https://github.com/ble55ing/clang
在低版本的clang中,可以直接clang -cc1 -analyze -cfg-dump 1.c来获得程序控制流图,但较高版本后将其中一部分提出来放到了分析器中
另外clang -cc1默认仅限当前目录,所以会出现fata error: ‘stdio.h’ file not found 的情况。
解决方法是使用-I添加包含库,
clang -cc1 -I/usr/include -I/home/blessing/clang-llvm/build386/lib/clang/5.0.0/include -analyze -analyzer-checker=debug.DumpCFG 1.c
第一个包含库中含有stdio.h,第二个库中有stdder.h,之后还有需要的库还可以继续添加。
如果想要将结果输出到文件中,可以在终端先输入 script -f CFG.txt ,这样就可以将当前的命令行的输出全写到文件中去了
这题是和pwnable.kr差不多的一道题,主要区别在于没有给syscall。所以需要自己去找。
首先看一下保护
只有read和sleep两个函数。
思路一是首先劫持堆栈到bss段,然后调用read函数将sleep的got表中地址改成syscall(直接找到\x0f\x05),将/bin/sh放入bss中,然后设置rax为59调用syscall可以调用execve了,结果这个方法本地打通了远程打不通,不知道是什么原因。应该是地址不一样,所以决定先用write泄露一下地址然后再定位syscall。
未完待续
没有续了,问题出在服务器程序把我的两次read当成一次了,间隔时间太短,sleep(0.1)挂了,sleep(3)ok
另一种方法是看别人的blog中有提到的,在劫持堆栈到bss段之后,可以先用syscall调用write泄露sleep地址,然后计算出/bin/sh\x00的地址,pop rdi,ret;地址和system地址,rax的值可以通过read函数的返回值来控制,也是一个很可行的思路。
本题考察的是scanf的%d对于‘+’的处理,相当于没有输入
题目为一个排序算法,就如题目名称那样,dubblesort,32位程序。
利用思路为栈溢出,先是栈溢出泄露出栈上libc的相关数据从而获取libc地址,再是栈溢出劫持控制流。第二步时需要注意存在着一个canery需要绕过,可以使用scanf的%d输入为+时为没有输入的方式跳过(返回值会出错但本题没检查),就一个数字的长度,在第25个。