VulnDbGen代码解析/工作原理
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]([email protected]@\s[^a-zA-Z]*\s[^a-zA-Z]*\[email protected]@)'
如@@ -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文件
完结!