VulnDbGen代码解析/工作原理

目录

Config.py文件 2

get_cvepatch_from_git.py 2

parseutility.py 5

get_source_from_cvepatch.py 9

 

 

Config.py文件

配置github仓库的路径(gitStoragePath),还有命令行中使用git命令、diff命令的环境变量配置(gitBinary、diffBinary、javaBinary)

 

 

get_cvepatch_from_git.py

init函数

在diff文件夹里新建一个github仓库对应的文件夹

 

 

 

callGitLog(gitDir):

输入某个git仓库的路径,使用git log命令搜索带有关键词  cve-20  的git commit,返回commitList

Git --no-pager log --all --pretty=fuller --grep=”CVE-20” 

 

 

filterCommitMessage(commitMessage)

输入前面生成的commitList

remove 'Merge', 'Revert', 'Upgrade' commit log

使用正则表达式  如 \Wmerge\W | \Wmerges\W

 

 

process(commitsList, subRepoName)

 

使用多进程将commitList中的每条commit log输入parallel_process函数

 

parallel_process(subRepoName, commitMessage)

调用了callGitShow、updateCveInfo函数

 

callGitShow(gitBinary, commitHashValue)

使用某条commit 的hash值结合git show来生成diff信息

 

 

Parallel-process使用callGitShow的返回信息写入diff文件,使用updateCveInfo的返回信息来生成文件名

 

 

使用了filterCommitMessage函数来去除掉含有merge、update等关键词的commit

直接搜索commit信息中出现的cve号,使用list(set(cvePattern.findall(commitMessage)))来去重

对于一条commit有多个cveId时,选出minCve值,并新建一个dependency文件,写入

minCve + '_' + commitHashValue + '\t' + cveIdFull + '\n'

 

 

 

InfoStruct类的构造函数中利用pickle库将cvedata.pkl文件加载成一个二维数组

第一个下标是cve-id,cveDict[cveId][0]存储了该cve的cvss分数,cveDict[cveId][1]存储了cwe-id

 

Parallel-process将mivCve输入updateCveInfo函数

updateCveInfo(cveDict, cveId)

根据cveDict、cveid信息生成字符串并返回    

cveId + '_' + str(cvss) + '_' + cwe + '_'

用于生成的diff文件的命名

 

 

 

 

 

get_source_from_cvepatch.py文件调用了parseutility.py文件

parseutility.py

get_platform()

配置全局变量osName(操作系统类型,win 、linux 、osx) and bits(64、86)

 

setEnvironment(caller)

调用get_platform(),

配置了变量javaCallCommand,供后面parseFile_shallow函数执行java命令解析临时文件时使用

 

 

class function

抽象出一个类,函数类

其中的removeListDup(self)方法,用于将函数对象的成员,如参数列表、本地变量列表、数据类型列表的去重(将重复的去掉)

 

parseFile_shallow(srcFileName, caller)

 

 

get_source_from_cvepatch.py文件中调用上面的解析函数,传入临时新旧文件,使用java命令来解析它们(调用了java函数库),解析结果返回给astString变量

如一个临时文件tmp.txt,解析成如下

每一块是包含了一个函数的信息

delimiter = "\r\0?\r?\0\r"

 

 

一个文件解析后的结果有多条函数的信息,分割好后利用每条函数的信息初始化functionInstance实例,每个函数对应一个实例,返回实例的列表

 

因此,一个function对象的parentFile属性值为临时文件的名字,上例为tmp.txt

ParentNumLoc是父文件的位置数字?什么意思?上例为1425

Name为函数名,上例为Open、Demux 等

Lines是一个元组,标记函数的起始行和终止行,如上例open函数为149 183,从第149行到183行

funId是这个临时文件解析出来的第几个函数,从1递增

ParameterList是函数的参数列表,上例open函数为p_this

ParameterList、variableList、datatypeList几个属性都没初始化

FuncBody属性的值是函数的代码

 

 

 

 

 

 

 

get_source_from_cvepatch.py

Init()

新建了文件夹tmp、vul,配置了全局变量total,其值为指定git仓库对应的diff文件夹里的diff文件个数

 

Main()

使用多进程pool.map,将diff文件夹里的文件名传入source_from_cvepatch函数,多进程结束后删除tmp文件夹的内容

 

 

source_from_cvepatch(ctr, diffFileName)

输出每一行前面的这个序号

 

太大的diff文件不处理,视为merges、upgrade等情况

 

从diff文件名中读取出cveid、commit hash值等信息,输出每一行后面的部分

 

pat_src = '[\n](?=diff --git a/)'

将一个diff文件分成两大部分,分别赋给commitLog、affectedFilesList(一个列表,每个成员对应一条diff信息,一个diff文件可有多条diff信息,一个commit可能有多个文件发生了变动,使用git show commitId输出的信息可有多条diff信息),将一个diff文件内diff信息的条数赋给numAffectedFiles,如下例

 

对每一条diff信息(affectedFile),处理第一行信息,配置变量affectedFileName、codePath

如例

diff --git a/contrib/src/png/bins.patch b/contrib/src/png/bins.patch

affectedFileName为bins.patch

codePath为/contrib/src/png/bins.patch

 

 

 

到第二行,如果不是以index开头(或者以 100644结尾),则输出报错信息,函数执行结束

 

 

配置变量indexHashOld、indexHashNew,去上图为例

indexHashOld=1c073fd756

indexHashNew=2aa9013d7e

 

根据

pat_chunk = '[\n](?=@@\s[^a-zA-Z]*\s[^a-zA-Z]*\s@@)'

如@@ -251,11 +251,8 @@ static void Close( vlc_object_t *p_this )来分块,得到块的列表

 

 

 

 

如下例

下图就是一个chunk

 

 

切换路径到git仓库里,执行命令行命令

git show indexHashOld > tmpOldFileName

将git show的输出信息存储到临时旧文件中

 

 

两个临时文件分别是该条diff信息对应的commit前后的两个文件内容

 

 

 

再调用parseutility.py文件中的parseFile_shallow(srcFileName, caller)函数,解析两个临时文件

得到每个临时文件里面的函数对象列表,但不是每个函数都被修改了,因此接下来还要选出修改过的函数

 

 

 

对每一块进行操作

pat_linenum = r"-(\d+,\d+) \+(\d+,\d+) "

pat_linenum = re.compile(pat_linenum)

如下例,oldLines=[‘251’,’11’],newLines=[‘251’,’8’]

 

接着上例,offset=251,将每一行(非空白行)的第一个字符放入pmList,再处理后放入lnList

如果第一个字符是空白或者减号,则将 offset+i加入lnList,为加号则加入offset+i-1,offset再减1

因为后面要用旧临时文件的function对象的起始行、终止行属性结合这里的行数来确定function对象是否为将要修改的函数,因此这里把diff信息的代码行数(在旧临时文件中的行数)都搜集起来。为空白或者减号,则说明在旧临时文件中存在,直接加进去,为加号说明在旧临时文件不存在,因此把offset-1(因为i加了1).

lnList.append(offset + i - 1) 不可以删除,虽然这行代码只会把加号行代码块前一行的行数重复添加进去,但是后面代码利用了这个

如,假如第9行是减号行,上面代码把9加入了lnList,第10、11、12行都是加号行,那么后面三次都会把9加进去,即连续4个9加入了lnList

 

 

搜集命中的旧临时文件函数,得到hitOldFunctionList

再对hitOldFunctionList进行筛选得到finalOldFunctionList,寻找有意义的加减号行,只要有一个,就说明这个函数的修改是有意义的(只添加一些注销就认为没有意义),就纳入finalOldFunctionList

 

 

查找一个命中行在lnList中的位置

如果出现次数大于1,说明后面紧接着就是加号行代码,把listindex加1,考察后面的加号行是否有意义

Lnlist为一个chunk的旧代码行数列表,listIndex为某一行代码在Lnlist中的位置,chunkLines为一个chunk的代码行列表,因此chunkLines[listIndex]为某一行代码的内容,

[1:].lstrip().startswith(commentKeyword) 忽略第一个字符,再把左边的空白去掉,如果代码以注释的一些关键词开头,说明是注释,不认为是有意义的修改

 

这个筛选很粗糙,如对于多行注释中间部分的修改,没法判断出是无意义的修改,后面还有一次针对修改注释的筛选

最后再去重

把筛选过的旧函数对应的新函数加入列表finalNewFunctionList

dummyFunction = parseutility.function(None)

Dummy 假

 

 

 

打开旧临时文件读取函数代码行,并成一个字符串

打开新临时文件读取函数代码行,并成一个字符串

(当然也可以使用函数对象的funcBody属性)

 

 

将FinalOldFunction中{} 中的内容赋给finalOldBody(大括号也不留)

再使用removeComment去掉函数代码中的注释,使用normalize来使代码的格式统一标准

 

 

(?P<comment>//.*?$|[{}]+)

.*? 使用*?来匹配任何字符串(非贪婪模式)(使用了dotall模式,因此 . 能匹配换行符), $结合multiline模式匹配一个行结束

[{}]+  匹配一个或多个 { 或 } 字符(为什么当做注释?把函数最外层的{} 去掉了?)

 

(?P<multilinecomment>/\*.*?\*/)

匹配/* ... */  

即匹配多行注释

 

(?P<noncomment>\'(\\.|[^\\\'])*\'|"(\\.|[^\\"])*"|.[^/\'"]*)

先看左边\'(\\.|[^\\\'])*\'

子表达(\\.|[^\\\'])表示匹配 \. 或者 任何不属于 \或' 的字符

则\'(\\.|[^\\\'])*\'表示匹配 0或任意多个子表达式,且两边有单引号包围

中间的"(\\.|[^\\"])*" 与左边类似,只不过单引号变成双引号

右边的 .[^/\'"]* 匹配任意一个字符 和任意多个不属于  / 或 ' 或 "  的字符

 

 

 

去掉\n \r \t { } ,再根据空格将字符串切割,再使用join合并(即把空格也去掉了)

最后lower方法全部变成小写

 

 

 

如果是一样的说明只修改了注释,没有意义的修改(修补了前面粗糙的筛选方法)

 

vulFileNameBase = diffFileName.split('.diff')[0] + '_' + affectedFileName

把finalOldFunction、finalNewFunction的内容分别写入vul文件

再使用linux的diff命令输出新旧vul文件的unified diff信息到patch文件

完结!

猜你喜欢

转载自blog.csdn.net/m0_43406494/article/details/109090316