程序图各节点之间的关系需要尽可能包含能体现漏洞特征的信息。
节点cursor
生成抽象语法树,然后再重抽象语法树中选择需要连接进图的节点。
现在有两种解决方案。
- 直接通过clang将源代码转换成抽象语法树。
- Python 的clang 接口。
直接使用clang 命令
# 这两种命令是直接将生成的AST输出在shell中。如果file中只是一个函数片段,其中的某些变量或者函数并没有在函数内部定义,那么就会报错。这时就无法正常输出结果,而是会将error和warning报出来。
clang -Xclang -fsyntax-only -ast-dump file
clang -emit-ast file
# 如果不加 -fsyntax-only,会输出些结果。在这些结果里,会有些statement被忽略掉,
clang -Xclang -ast-dump file
如果直接在终端用clang生成抽象语法树,由于数据过多,批量化处理不方便。所以采用clang的Python接口。
clang 的Python接口配置。都需要两个部分,一个是libclang的Python 第三方库,另一个是libclang库。
-
在windows上。Windows上的libclang是libclang.dll。需要注意几个问题,libclang.dll的位数需要与本机的Python位数一致,32位还是64位要分清楚。然后就是libclang的版本需要和Python库clang的版本相一致。目前clang最新已经有10.0的版本,但是在Python中clang库只目前只更新到6.0的版本,所以如果要最新的最好选择clang 6.0的版本。
在这里推荐直接装 clang 6.0 从 https://pypi.org/project/clang/6.0.0.2/直接下载 然后本地安装。libclang.dll这里我们可以直接从llvm安装exe文件 https://releases.llvm.org/download.html,从中选择6.0的win64.exe版本下载完后直接安装就可以得到libclang.dll。
-
在linux上, libclang是libclang.so.1的共享库的形式。与windows相同,其版本需要与Python库中的clang一致。
clang直接选择源码编译安装可以得到libclang.so.1
然后将路径设置为libclang所在的位置即可。
from clang.cindex import Config libclangpath = r'' Config.set_library_file(libclangPath)
需要的节点类型
用Python接口生成的AST也是用clang生成的,因此其节点的类别是一样的。在经过clang直接输出的信息中,每一行代表一个节点的信息,其中包括了节点的类型,在代码的第几行第几个,以及节点本身的类型信息和自身的字符表示。然后不同的节点通过一定的解析结构连在一起。
对于一个C/C++文件来说,通过clang生成的AST是解析过程是这样的。如果有头文件,且头文件与该文件在同一目录下,则会首先会将头文件进行解析。(这是在调用cursor.get_children()
来对AST进行遍历的情况)然后生成的cursor对象,有几种属性。
cursor.kind
是指此cursor在AST中的位置(函数、类、参数、表达式等)即 Operator(操作符)、Decl(声明)、Stmt(陈述)、Expr(表达式)、Literal(字符)、Reference(引用 )等。例如FunctionDecl、ParaDecl、VarDecl;IF_STMT、WHILE_STMT。在cindex.py
中对cursor.kind
进行了说明总共有大约700种。
在对文件进行解析的时候会出现这种问题。如果函数中有外部声明的变量或者函数类型(可能在头文件里,但是头文件没有),那么clang不会识别出来,因此对这些语句就会忽略。clang -l 参数可以指定链接库,如果将文件的头文件的路径全部放在同一目录下,这样就可以加快批量处理的速度。
Index
AST的生成首先是通过调用cindex.py
中的 Index类来对translation units进行读取和解析。Index主要有以下几种方法。
class Index(ClangObject):
@staticmethod
def create(excludeDecls=False):
return Index(conf.lib.clang_createIndex(excludeDecls, 0))
def __del__(self):
conf.lib.clang_disposeIndex(self)
def read(self, path):
return TranslationUnit.from_ast_file(path, self)
def parse(self, path, args=None, unsaved_files=None, options = 0):
return TranslationUnit.from_source(path, args, unsaved_files, options,self)
首先创建一个新的index对象 Index.create()
,然后用这个实例化后的index调用parse方法对文件进行解析Index.create().parse()
我们从parse()
可以看到实际上是调用 TranslationUnit.from_source()
@classmethod
def from_source(cls, filename, args=None, unsaved_files=None, options=0,
index=None):
...
ptr = conf.lib.clang_parseTranslationUnit(index, filename, args_array,
len(args), unsaved_array,
len(unsaved_files), options)
...
return cls(ptr, index=index)
这是个类方法,最后返回的是一个实例化的 TranslationUnit
。其各参数解释如下:
filename
即需要被解析的文件(既可以是文件系统中的文件,也可以是内存中的内容)
args
是对clang 传入的参数以列表的形式
options
是clang 解析的方式,一共有9种方式。
我们发现在实际解析过程中,如果其中的变量类型或者变量在函数内部或者头文件中没有声明,那么此语句就会解析有误。其次,即使变量的类型在其它地方有声明,那么在进行生成AST的时候,打印出来的变量的类型不是标准类型的形式,这对变量类型的理解就会有偏差。(这里应该对变量的类型进行归一化转换,使得typedef后的变量类型能够映射回原类型)
Token
用clang的词法分析可以将文件中所有的text分成token序列。对于某个节点cursor,只要调用其get_tokens()
方法就能获取此cursor下的所有token。在clang解析过程中将token分成这几种类型,keyword、identifier、literal、punctuation,通过token.kind
可以获取其kind属性。token.cursor
可以获取其属于哪个节点cursor。
graph
将代码转换成图的结构,需要考虑以下几个问题。
-
**我们的目标代码是什么?**是一整个C/C++文件还是其中的某个函数,或者只是一个代码片段。如果不是完整的代码结构(其中所有的变量或者函数都被声明或者在头文件中),那么没有声明的变量就会在语法解析的时候报错,这样生成的AST节点是有问题的,不完整的。其实很多情况下,C/C++头文件并不完整,所以准备从两条路对代码进行解析,仅进行词法分析然后通过token之间的关系构件图;在头文件完整的情况下进行语法分析AST以cursor的方式构建图。
-
以token的方式建立代码图。图中的每个节点就是每个token。对于图神经网络来说,每个节点是有特征向量的,那么以token表示的节点,什么样的特征能够体现体现漏洞特征。对于一个token无非就是其本身的spelling,它的kind。我们在进行静态分析
https://www.sohu.com/a/388834376_115128
现在用joern来构建代码属性图。joern可以生成AST,CFG,PDG,然后将这三者全部融合在一起。对于GNN来说,输入需要各个节点的属性以及节点之间的关系。因此,下面有两种方式来用joern构建,一种是直接用其生成的CPG来进行处理;第二种方式是分别将生成的三种结构进行组合。
用joern生成的这三种图的存储方式都可以通过json对象或者json string的形式进行存储。(那么这三种结构中的节点,是否能够统一联系在一起?)
AST
用joern可以将函数解析出来生成AST,这个AST的数据是以json的格式进行存储的。为了方便批量处理数据,我们用将json用Python转换成字典的形式。
如果直接将一个函数实体通过此命令进行解析 cpg.runScript(graph/ast-for-funcs-dumps.sc)
,那会将此函数以及其内部的函数都生成一个AST,然后这些AST都存在 dict["functions"]
中,这是一个列表,其中每个元素是以字典形式存储的每个函数的AST。但我们只要主函数的AST,因此我们需要知道主函数的函数名,然后
),那会将此函数以及其内部的函数都生成一个AST,然后这些AST都存在
dict[“functions”]`中,这是一个列表,其中每个元素是以字典形式存储的每个函数的AST。但我们只要主函数的AST,因此我们需要知道主函数的函数名,然后