一种静态分析辅助的符号执行漏洞检测方法
技术领域
本发明涉及软件漏洞的识别和检测,特别涉及一种基于静态程序分析、对待检测软件进行符号执行实施软件漏洞识别和检测的方法。
背景技术
软件漏洞是一种程序错误,通过利用该错误,攻击者可以潜在地违反受害者计算机系统的安全策略。随着软件规模的不断增加与应用范围的不断扩大,软件漏洞对人们生活与工作的危害也不断增强。因此,如何高效地检测并验证软件漏洞成为软件开发与测试人员的关注焦点。符号执行技术是常用的漏洞检测技术,可以遍历整个目标程序并为每条程序路径产生一个测试样例以验证路径的可达性及安全性。但是,符号执行会在漏洞无关的路径上浪费大量的计算时间和资源,限制了漏洞检测的效率。而且,在计算时间或者资源有限的情况下,对漏洞无关路径的探索也会降低漏洞检测的精度,使漏报率增加。
为此,本发明提出了一种静态分析辅助的符号执行漏洞检测方法。首先,对目标程序进行快速、高效的静态分析,获得漏洞相关的程序控制流信息;之后,使用获得的控制流信息辅助符号执行漏洞检测以降低符号执行漏洞检测的时间与空间开销,提高漏洞检测的效率。本发明通过将静态程序分析与符号执行技术相结合,提高了漏洞检测效率与精度,在时间和资源有限的情况下,能够检测到更多的漏洞。
发明内容
本发明目的是,为了能够高效快速地检测并验证软件漏洞,提供了一种静态分析辅助的符号执行漏洞检测方法。通过静态分析快速获得漏洞相关的控制流信息并对程序分支按照后续子程序中的漏洞相关路径占总路径的比例进行打分;在之后符号执行过程中,使用静态分析得到的控制流信息将符号执行过程约束在漏洞相关路径上,并按照静态分析得到的分支打分优先分析漏洞相关路径比例更高的程序分支。通过该方法减少浪费在漏洞无关路径上的计算时间和资源,提高符号执行漏洞检测的效率,进一步地,在时间和资源有限的情况下,检测到更多的漏洞。
为实现上述目的,本发明采取如下技术方案:一种静态分析辅助的符号执行漏洞检测方法,包括轻量级符号执行、图生成、静态分析和符号执行等阶段;
1)轻量级符号执行阶段,使用KLEE符号执行工具以简单的符号化策略在很小的时间开销下对目标程序字节码文件进行符号执行以获得完成链接过程后的完整程序的字节码;轻量级符号执行过程使用符号执行工具KLEE对目标程序字节码进行符号执行得到链接后的完整程序字节码,符号化策略为:-sym-args 1 1 2,即1个长度为1个字节的符号作为程序输入;
2)图生成阶段,在完整程序的字节码的基础上,通过使用LLVM 3.1工具集中的opt工具生成完整程序的函数调用图与各个函数的控制流图;生成的函数调用图中包括两类语句:函数描述语句与函数调用关系描述语句;函数描述语句中包括了函数调用图中出现的所有函数的函数名与函数编号;函数调用关系描述语句则描述了所有函数之间的调用关系;函数的控制流图中包含两类语句:基本块描述语句与基本块间关系描述语句;基本块描述语句描述了函数中每个基本块的基本块编号,基本块标签和基本块包含的指令,特别地,如果该基本块的最后一条语句是条件分支指令,基本块描述语句中还会包含跳转到各个分支的条件取值;基本块间关系描述语句则描述了所有基本块之间的前驱与后继关系;
3)静态分析阶段,以完整程序的函数调用图、各个函数的控制流图以及配置文件作为输入,通过程序模块预处理、敏感相关函数和敏感导向函数识别、有效跳转收集与分支打分四个过程对程序进行静态分析,定位敏感函数位置,收集程序分支语句中能够导向敏感操作的有效跳转并以后续子程序中敏感操作相关路径占总路径的比例为标准对程序分支进行打分,得到有效跳转表与分支打分表;其中,函数识别过程包含两个子过程:敏感相关函数识别与敏感导向函数识别;
4)符号执行等阶段,使用有效跳转表与分支打分表辅助漏洞检测符号执行,输入为目标程序的字节码文件、配置文件以及静态分析阶段得到的有效跳转表和分支打分表;在具体实施中,符号执行部分在KLEE符号执行工具的基础上在指令执行过程中添加了静态辅助方法并增加了基于后续子程序中敏感操作相关路径比例的分支选择过程;当检测到敏感操作时,使用约束求解器STP求解当前执行环境下敏感操作能否违反安全约束,如果有解,则表示该敏感操作是可利用的安全漏洞,返回对应的测试样例给用户;
5)本方法的关键操作如下:
(1)程序模块预处理,通过分析目标程序的函数调用图和各个函数的控制流图对静态分析阶段的程序模块进行预处理,为静态分析中的后续过程提供分析的对象;
(2)敏感相关函数识别,将所有包含敏感操作的函数标记为敏感相关函数,并将所有调用敏感相关函数的函数标记为敏感相关函数;
(3)敏感导向函数识别,将所有在执行完成返回后依然存在敏感操作或敏感相关函数调用操作的函数标记为敏感导向函数;
(4)有效跳转收集,在敏感相关且非敏感导向的函数中收集能够导向敏感操作的分支条件取值到有效跳转表中;
(5)分支打分,按照后续子程序中敏感操作相关路径占总路径的比例对程序中的每个分支进行打分,将分数记录在分支打分表中;
(6)漏洞检测符号执行,使用有效跳转和分支打分辅助符号执行,探索敏感操作相关路径检测敏感操作是否能够违反安全约束,并优先探索敏感操作相关路径比例高的程序分支;
6)在符号执行工具KLEE的基础上修改了指令执行部分,添加了基于敏感操作相关路径比例的分支选择部分;在指令执行部分,通过有效跳转表将符号执行过程限制在敏感操作相关路径范围内,并对敏感操作按照配置文件中的安全约束进行检查,如果在执行环境下敏感操作能够违反安全约束,则表示该敏感操作是可利用的安全漏洞,返回对应的测试样例给用户;在分支选择部分,以分支打分表中各个分支的分数为标准,选择敏感操作相关路径比例最高的分支作为下一次符号执行路径探索的目标,进一步加快探索敏感操作的速度。
程序模块预处理流程:以完整程序的函数调用图和控制流图为输入,预处理静态分析中的程序模块,静态分析中后续的函数识别、有效跳转收集和分支打分都是在程序模块的基础上分析得到的;使用程序模块来表示当前正在静态分析的程序;程序模块中包括如下数据结构:函数链表,以链表的形式存储了该程序模块中所有函数的指针;敏感相关函数链表,以链表的形式存储了程序模块中所有被标记为敏感相关的函数的指针;敏感导向函数链表,以链表的形式存储了程序模块中所有被标记为敏感导向的函数的指针;有效跳转表,以集合的形式存储了函数模块中的有效跳转,集合中的元组<function,instruction,condition>表示指令instruction是函数function中的分支指令,且如果分支条件取值为condition就能够导向敏感操作;分支分数表,以哈希表的形式存储了程序模块中分支的分数,表中元组<function,instruction,score>表示指令instruction是函数function中某个基本块的起始指令且以该指令为起始指令的分支分数为score;目标操作集合,以集合的形式存储检测过程中的目标操作;敏感操作及安全约束表,以哈希表的形式存储用户定义的敏感操作和每个敏感操作对应的安全约束,如果在所处的执行环境下敏感操作能够违反安全约束,则该敏感操作被认为是可触发的程序漏洞;对于程序模块中的函数,包含如下数据结构:函数名;函数编号;调用者函数链表,以链表的形式存储所有调用该函数的函数指针;调用函数链表,以链表的形式存储所有被该函数调用的函数的指针;基本块链表,以链表的形式存储该函数中基本块的指针;函数中的基本块,包含如下的数据结构:基本块编号;基本块标签;指令链表,以链表的形式存储基本块中的指令;前驱基本块链表,以链表的形式存储该基本块所有前驱基本块的指针;后继基本块链表,以链表的形式存储该基本块所有的后继基本块指针。
本发明的有益效果是,将静态程序分析与符号执行技术相结合,通过静态程序分析,收集程序分支语句中能够导向敏感操作相关路径的条件取值,并依据后续程序中敏感操作相关路径占总路径的比例对程序分支进行打分。后续的符号执行过程在静态分析结果的辅助下能够减少在敏感操作无关路径上消耗的计算时间和资源,提高符号执行漏洞检测的效率,进一步地,在有限的时间和计算资源情况下检测到更多潜在漏洞。
附图说明
图1为静态分析辅助的符号执行漏洞检测方法总体流程图;
图2为程序模块预处理流程图;
图3为敏感相关函数识别流程图;
图4为敏感导向函数识别流程图;
图5为有效跳转收集流程图;
图6为分支打分流程图;
图7为漏洞检测符号执行流程图;
图8为指令执行流程图。
具体实施方式
图1所示为本方法的总体流程图。本方法的输入分为两部分:目标程序字节码与配置文件。目标程序字节码通过LLVM 3.1编译器体系的前端Clang编译C/C++源程序获得。用户可以在配置文件中定义可能产生软件漏洞的敏感操作作为本次检测的目标,并且为每个敏感操作定义相应的安全约束。如果在所处的执行环境下敏感操作能够违反安全约束,则该敏感操作被认为是可触发的程序漏洞。本方法的输出是程序输入的测试样例,通过执行该测试样例,能够到达当前执行环境并触发敏感操作使其违反安全约束。
首先,轻量级符号执行过程使用符号执行工具KLEE对目标程序字节码进行符号执行得到链接后的完整程序字节码,符号化策略为:-sym-args 1 1 2,即1个长度为1个字节的符号作为程序输入。之后,图生成阶段在完整程序字节码的基础上使用LLVM 3.1工具集的opt工具生成完整程序的函数调用图与各个函数的控制流图,生成的函数调用图中包括两类语句:函数描述语句与函数调用关系描述语句。函数描述语句中包括了函数调用图中出现的所有函数的函数名与函数编号;函数调用关系描述语句则描述了所有函数之间的调用关系。函数的控制流图中包含两类语句:基本块描述语句与基本块间关系描述语句。基本块描述语句描述了函数中每个基本块的基本块编号,基本块标签和基本块包含的指令,特别地,如果该基本块的最后一条语句是条件分支指令,基本块描述语句中还会包含跳转到各个分支的条件取值;基本块间关系描述语句则描述了所有基本块之间的前驱与后继关系。然后,在静态分析过程中,通过程序模块预处理过程得到静态分析的对象,并通过函数标记、有效跳转收集和分支打分过程,获得有效跳转表与分支打分表。最后,在符号执行过程中使用有效跳转表与分支打分表辅助漏洞检测符号执行。在具体实施中,符号执行部分在KLEE符号执行工具的基础上在指令执行过程中添加了静态辅助方法并增加了基于后续子程序中敏感操作相关路径比例的分支选择过程。当检测到敏感操作时,使用约束求解器STP求解当前执行环境下敏感操作能否违反安全约束,如果有解,则表示该敏感操作是可利用的安全漏洞,返回对应的测试样例给用户。
图2为程序模块预处理流程图。以完整程序的函数调用图和控制流图为输入,预处理静态分析中的程序模块,静态分析中后续的函数识别、有效跳转收集和分支打分都是在程序模块的基础上分析得到的。使用程序模块来表示当前正在静态分析的程序。程序模块中包括如下数据结构:函数链表,以链表的形式存储了该程序模块中所有函数的指针;敏感相关函数链表,以链表的形式存储了程序模块中所有被标记为敏感相关的函数的指针;敏感导向函数链表,以链表的形式存储了程序模块中所有被标记为敏感导向的函数的指针;有效跳转表,以集合的形式存储了函数模块中的有效跳转,集合中的元组<function,instruction,condition>表示指令instruction是函数function中的分支指令,且如果分支条件取值为condition就能够导向敏感操作;分支分数表,以哈希表的形式存储了程序模块中分支的分数,表中元组<function,instruction,score>表示指令instruction是函数function中某个基本块的起始指令且以该指令为起始指令的分支分数为score;目标操作集合,以集合的形式存储检测过程中的目标操作;敏感操作及安全约束表,以哈希表的形式存储用户定义的敏感操作和每个敏感操作对应的安全约束,如果在所处的执行环境下敏感操作能够违反安全约束,则该敏感操作被认为是可触发的程序漏洞。对于程序模块中的函数,包含如下数据结构:函数名;函数编号;调用者函数链表,以链表的形式存储所有调用该函数的函数指针;调用函数链表,以链表的形式存储所有被该函数调用的函数的指针;基本块链表,以链表的形式存储该函数中基本块的指针。函数中的基本块,包含如下的数据结构:基本块编号;基本块标签;指令链表,以链表的形式存储基本块中的指令;前驱基本块链表,以链表的形式存储该基本块所有前驱基本块的指针;后继基本块链表,以链表的形式存储该基本块所有的后继基本块指针。
步骤20是初始动作。步骤21模块预处理:设置模块的函数链表、敏感相关函数链表、敏感导向函数链表为空链表,初始化有效跳转表为空集合,初始化分支打分表为空哈希表。步骤22函数预处理:读入函数调用图,按函数描述语句设置函数名、函数编号;按照函数调用关系语句设置调用者函数链表与调用函数链表;设置函数为非敏感相关、非敏感导向并将基本块链表初始化为空链表;将初始化完成的函数添加到函数链表中。步骤23设置循环起始条件,取函数链表中的第一个函数为当前函数。步骤24判断当前函数是否为空,若为空,全部函数基本块预处理完成,跳转到步骤29,循环结束;否则跳转到步骤25,进入循环。步骤25判断图生成阶段生成的各个函数的控制流图集合中是否存在当前函数的控制流图,如果存在,表示当前函数在程序的字节码中有函数定义,跳转到步骤26;否则当前函数在字节码中仅仅是函数声明,跳转到步骤27。步骤26定义函数基本块预处理:将当前函数标记为定义函数,读取当前函数的控制流图,按照控制流图中的基本块描述语句初始化基本块的基本块编号和基本块标签,按照基本块间关系描述语句初始化基本块的前驱基本块链表与后继基本块链表,然后将基本块设置为非敏感操作相关,最后将基本块添加到当前函数的基本块链表中,跳转到步骤28。步骤27声明函数基本块预处理:将函数标记为声明函数,并将对该函数的调用操作添加到目标操作集合中。步骤28取函数链表中的下一个函数为当前函数,跳转到步骤25。29敏感操作预处理:读取配置文件,将用户定义的敏感操作添加到目标操作集合中,将用户定义的敏感操作与对应的安全约束添加到敏感操作及安全约束表中。步骤2a是结束状态。
图3为敏感相关函数识别流程图。步骤30是初始动作。步骤31设置循环起始状态,取函数链表中第一个函数作为当前函数。步骤32判断当前函数是否为空,若是,包含目标操作的敏感相关函数识别完成,跳转到步骤36,循环结束;否则跳转到步骤33,进入循环。步骤33判断当前函数是否包含目标操作集合中的操作,若包含,转步骤34,否则跳转到步骤35。步骤34标记当前函数为敏感相关并将当前函数加入待扩展函数集合中。步骤35取函数链表中下一个函数为当前函数,跳转到步骤32。步骤36判断待扩展函数集合是否为空,若是,敏感相关函数扩展完成,跳转到步骤3d,循环结束;否则转步骤37,进入循环。步骤37从待扩展函数集合中取一个函数为当前函数,并从集合中移除该函数。步骤38判断当前函数是否被扩展过,若是,跳转到步骤36,否则跳转到步骤39。步骤39将当前函数标记为被扩展过,设置循环起始状态,取当前函数的调用者函数链表中第一个函数作为扩展函数。步骤3a判断扩展函数是否为空,若是,转步骤36,结束循环;否则跳转到步骤3b,进入循环。步骤3b判断扩展函数是否被扩展过,若是,转步骤3d;否则跳转到步骤3c。步骤3c设置扩展函数为敏感相关,并将它添加到待扩展函数集合和敏感相关函数链表中。步骤3d取调用者函数链表的下一个函数为扩展函数,跳转到步骤3a。步骤3e是结束状态。
图4为敏感导向函数识别流程图。步骤40是起始动作。步骤41设置循环初始状态,取敏感相关函数链表中第一个函数为当前函数。步骤42判断当前函数是否为空,若为空,跳转到步骤4d;否则跳转到步骤43,进入循环。步骤43在当前函数中收集目标操作集合中目标操作的位置到目标操作位置集合。步骤44判断目标操作位置集合是否为空,若是,转步骤4c,循环结束;否则转步骤45,进入循环。步骤45从目标操作位置集合中取一个目标操作位置,并分析得到所在的目标操作基本块。步骤46标记敏感导向函数:遍历目标操作基本块的入口与目标操作位置之间所有指令将其中被调用的函数标记为敏感导向,并添加到敏感导向函数链表中。步骤47将目标操作基本块的前驱基本块链表中所有基本块添加到待扩展基本块集合中。步骤48判断待扩展基本块集合是否为空,若是,转步骤44,结束循环;否则转步骤49,进入循环。步骤49从待扩展基本块集合中取一个基本块为当前基本块。步骤4a标记敏感导向函数:遍历当前基本块的指令链表,将其中被调用函数标记为敏感导向,并添加到敏感导向函数链表中。4b将当前基本块的前驱基本块链表中的基本块添加到待扩展基本块集合中,跳转到步骤48。步骤4c取敏感相关函数链表中下一个函数为当前函数,跳转到步骤42。步骤4d设置循环初始条件,取敏感导向函数链表中的第一个函数为当前函数。步骤4e判断当前函数是否为空,若为空,跳转到步骤4h,敏感导向函数标记结束;否则跳转到步骤4f,进入循环。步骤4f标记敏感导向函数:将当前函数的调用函数链表中所有调用函数标记为敏感导向并添加到敏感导向函数链表中。步骤4g取敏感导向函数链表中下一个函数为当前函数,跳转到步骤4e。步骤4h是结束状态。
图5为有效跳转收集流程图。步骤50是起始动作。步骤51设置循环初始状态,取函数链表中第一个函数为当前函数。步骤52判断当前函数是否为空,若是,转步骤5c,结束循环;否则跳转步骤53,进入循环。步骤53判断当前函数是否是敏感相关且非敏感导向的,若是,跳转到步骤54,进入循环;否则跳转到步骤5b,不分析当前函数。步骤54将当前函数中所有包含目标操作集合中目标操作的基本块添加到待分析基本块集合中。步骤55判断待分析基本块集合是否为空,若是,当前函数内的有效跳转收集完成,跳转到步骤5b,结束循环;否则跳转到步骤56,进入循环。步骤56从待分析基本块集合中取一个基本块为当前基本块。步骤57设置循环起始状态,取当前基本块的前驱基本块链表中第一个基本块为当前前驱基本块。步骤58判断当前前驱基本块是否为空,若是,跳转到步骤55,循环结束;否则跳转步骤59,进入循环。步骤59构造有效跳转:从当前前驱基本块中获得跳转指令inst及当前前驱基本块到当前基本块的跳转条件取值value,构造有效跳转<fun,inst,value>并添加到有效跳转表中,<fun,inst,value>表示函数fun中的跳转指令inst在条件取值为value时能够导向含有目标操作的基本块。步骤5a取当前基本块中前驱基本块链表中的下一个基本块为当前前驱基本块,跳转到步骤58。步骤5b取函数链表中下一个函数为当前函数,跳转到步骤52。步骤5c为结束状态。
图6为分支打分流程图。步骤60是起始操作。步骤61设置循环起始状态,取函数链表中第一个函数为当前函数。步骤62判断当前函数是否为空,若是,跳转步骤6e,打分结束;否则跳转到步骤63,进入循环。步骤63将当前函数中的所有叶子基本块的路径总数初始化为1,包含目标操作集合中目标操作的叶子基本块的敏感操作路径数初始化为1,不包含目标操作集合中目标操作的叶子基本块的敏感操作路径数初始化为0,将每个叶子基本块的分数初始化为敏感操作相关路径数/路径总数,并添加到待分析基本块集合中。步骤64判断待分析基本块集合是否为空,若是,当前函数内分支打分结束,转步骤6d;否则转步骤65,进入循环。步骤65从待分析基本块集合中取一个基本块作为当前基本块。步骤66设置循环起始状态,取当前基本块的前驱基本块链表中第一个基本块为当前前驱基本块。步骤67判断当前前驱基本块是否为空,若是,转步骤64,否则跳转到步骤68,进入循环。步骤68判断当前前驱基本块是否有分数,若有,转步骤6c,否则跳转到步骤69。步骤69判断当前前驱基本块是否满足打分条件,即是否当前前驱基本块后继基本块链表中的所有后继基本块都有分数,若是,跳转到步骤6a,否则不分析当前前驱基本块,转步骤6c。步骤6a给当前前驱基本块打分:更新当前前驱基本块的路径总数为其后继基本块链表中所有后继基本块的路径总数之和,敏感操作相关路径数为所有后继基本块的敏感操作相关路径数之和,当前前驱基本块的打分为敏感操作相关路径数/路径总数。步骤6b把当前前驱基本块添加到待分析基本块集合与分支打分表中。步骤6c取当前基本块的前驱基本块链表中下一个基本块为当前前驱基本块,转步骤64。步骤6d,取函数链表中下一个函数为当前函数,跳转到步骤62。步骤6e是结束状态。
图7为符号执行流程图。步骤70是起始动作。步骤71初始化执行环境:按照用户提供的参数链接相应的函数库,完成加载时链接过程。步骤72符号化输入:按照用户提供的参数对程序的输入进行符号化,可以指定符号化参数的个数、长度以及符号化文件的个数和长度。步骤73初始化执行状态并将执行状态添加到执行状态池中,包括初始化执行状态的约束集合、指令指针、栈与堆等。步骤74判断执行状态池是否为空,若是,符号执行结束,转步骤79,否则转步骤75,进入循环。步骤75从执行状态池中取一个执行状态作为当前执行状态,选取执行状态的标准为图6中得到的分支打分表,优先选取指令指针所指指令分数更高的执行状态。步骤76执行当前执行状态中指令:从当前执行状态中指令指针所指指令开始模拟执行连续的顺序流指令,收集执行路径上的约束到当前执行状态的约束集合。步骤77判断当前执行状态是否为程序终止状态,若是,跳转到步骤74;否则跳转步骤78。步骤78将当前执行状态放入执行状态池中,跳转到步骤74。步骤79结束。退出完成。
图8为指令执行流程图。该过程在KLEE指令模拟执行的基础上,利用静态分析的结果引导符号执行探索漏洞相关路径并验证是否存在违反安全约束的敏感操作。步骤80是起始操作。步骤81将当前执行状态中指令指针指向的指令作为当前指令。步骤82判断当前指令是否是敏感操作及安全约束表中的敏感操作,若是,转步骤83,否则跳转到步骤85。步骤83将敏感操作及安全约束表中当前指令对应的安全约束取反后与当前执行状态的约束集合一起交由约束求解器STP进行约束求解,如果可解,则存在能够违反安全约束的测试样例,不安全,跳转到步骤8c;否则跳转到步骤85。步骤84判断当前指令是否是Call指令,若是,跳转到步骤85,否则跳转步骤87。步骤85判断当前指令调用的目标函数是否是非敏感相关且非敏感导向的,若是,不执行当前Call指令,跳转步骤8d,结束;否则跳转到步骤86。步骤86按照指令语义模拟执行当前指令,跳转到步骤8d。步骤87判断当前指令是否是Br或者Switch语句,若是,转步骤88,否则跳转到步骤8b。步骤88判断当前指令所属的函数是否为敏感导向函数,若是,转步骤8a,否则跳转到步骤8b。步骤89利用约束求解器对每个分支检查可达性,并为每个可达的分支创建一个对应的执行状态,放入执行状态池中,跳转到步骤8d。步骤8a按照有效跳转表,对表中每个有效分支检查可达性,为每个可达的有效分支创建一个对应的执行状态,放入执行状态池中,不在有效跳转表中的分支则不做处理,跳转到步骤8d。步骤8b模拟执行当前指令,跳转步骤8d。步骤8c将生成的测试样例报告给用户,跳转到步骤8d。步骤8d是结束状态。