根据对程序路径的分析精度:
根据分析程序路径的深度:
代码建模:
源代码–(词法分析)–>词素序列–(语法分析)–>抽象语法树
词法分析读入组成源程序的字符流,并将他们组成有意义的词素(lexeme)序列,对每个词素,词法分析器产生一个词法单元,每个词法单元对应一个词素。词法单元描述抽象符号和符号表的对应,例如<id,3>表示一个变量并且变量名在相应的符号表的序号是 3,而<+>表示一个加法运算符号。
语法分析使用由词法分析器生成的各个词法单元的第一个分量来创建抽象语法树。
利用中间代码生成过程解析 AST 可以生成三地址码(three address code,TAC)
或者分析 AST 来获得控制流图CFG,再进行流敏感或路径敏感分析。
那么通过分析 TAC 来获得 CFG 如何呢?
通过分析过程间的调用关系,还可以构造程序的调用图
控制流图一般是过程内的控制流分析,而在过程之间的调用关系,一般需要使用调用图(call graph,CG)或者过程间控制流图(intra-procedure CFG,ICFG)
程序的中间表示 IR 通常有:AST、TAC、SSA(static single assignment,静态单赋值),IR 可用于程序指令的语义分析。
是程序抽象语法结构的树状表现形式。
每个内部节点代表一个运算符,这个节点的子节点代表该运算符的运算分量
AST 描述了程序语句和句中表达式的语法结构,通过描述控制转移语句的语法结构,在一定程度上也描述了程序的过程内代码的控制流结构。
AST 经过语义分析得到带有语义信息的抽象语法树(称为 decorated AST),其在节点的表示中加入一定的程序语义,更有利于分析指令语义。
类似于汇编语言的指令组成,每个指令具有不多于三个的运算分量,每个三地址码赋值指令右侧,最多有一个运算符。
相比于 AST,TAC 更加简单明了的表示了程序语句的指令语义。
通常指静态单赋值形式的三地址码,除此之外 AST 或源代码也可以表示为 SSA 形式。
SSA 中,所有赋值都是针对具有不同名字的变量,也就是说,若某变量在不同程序点被赋值,那么在不同程序点就应该使用不同的变量名。通常使用下标来区分不同程序点的统一变量。
在控制流的交汇处,SSA 使用 fai 函数将变量的赋值合并起来。

通过 SSA 能够很轻易的分析变量在哪里赋值在哪里使用,有利于通过数据流分析来获取变量的状态。缺点是变量数量大大增加。
如下面代码转换为 SSA:
var = malloc(sizeof(type));
free(var);
var = malloc(sizeof(type));
free(var);
# 转换为 SSA:
var1 = malloc(sizeof(type));
free(var1);
var2 = malloc(sizeof(type));
free(var2);
在上述 SSA 中,就不会出现已经释放的变量重新分配空间这样的情况。
CFG 一般指的是过程内的控制流的有向图,对于存在多个过程的程序,每个过程都用一个 CFG 表示。
涉及过程间调用的 CFG 称为过程间控制流图 ICFG,是 CFG 和调用图 CG 的混合表现形式。
通常情况下,CFG 的节点是由三地址码构成的基本块(basic block,BB)。
描述程序中过程之间的调用于被调用关系的有向图。
CG 构建的原则:
如下面的程序
void func1() {
func2();//调用点 1
func3();//调用点 2
}
void func2() {
func3();//调用点 3
}
void func3() {
func3();//调用点 4
}

func1 也调用了 func2,为啥在简化调用图中没有这条边,难道 func2 调用 func3,把这条边优化了?
一般情况下,都是用简化调用图来描述程序调用结构
利用编译器来实现完成代码解析的前端对于漏洞分析系统的设计是一个较好的选择,即使用编译器来生成分析所需的数据结构
解释型语言或脚本语言(如 python、JS、PHP 等)没有相应的编译器实现对程序代码的基本解析,代码直接被解释器解释执行。对于这些语言要完成代码解析的各个部分
对 JAVA 程序的漏洞分析通常分析其字节码,也就是 .class 字节码文件,因为字节码可以很好的反汇编成 Java 虚拟机的指令,而这种指令简洁明了,便于分析;且 Java 程序通常要调用库方法,而记录库方法的文件是 .class 文件,为了分析目标的一致性,所有使用字节码为分析目标
过程内控制流图 CFG 的构建:
过程调用图 CG 的构建: