自动化二进制分析技术 (State of) The Art of War: Offensive Techniques in Binary Analysis

本篇文章翻译自:(State of) The Art of War: Offensive Techniques in Binary Analysis

论文下载链接:http://www.cs.ucsb.edu/~chris/research/doc/oakland16_sokbin.pdf

文章主要讲解的是常见的二进制分析技术,作者对各类技术进行了实现与评估,开发了angr二进制检测框架。里面对各类技术有着非常好的解释与总结,有助于我们理解angr的实现原理。注:本文并不是对文章完整的翻译,只是对比较重要的技术知识点部分进行翻译总结。

A.权衡
需要权衡可重放性和覆盖性。这是非常明显的:因为产生可重放输入的分析技术必须了解如何到达任何想要分析的代码,所以它将不能分析尽可能多的代码。另一方面,如果不能重放“触发输入”来验证错误,那么会产生高误报率(也就是说,并不代表存在实际漏洞)。在没有可重放性的情况下,这些误报必须通过启发式方法进行过滤,反过来又会引入漏报率。同样的,为了实现对程序的语义分析,需要存储和处理大量的数据。
对可重放性和高度语义理解的分析会遇到可伸缩性方面的问题。 保留整个应用程序的语义信息,从入口点到可能采取的所有动作,都需要处理能力在概念上与在所有可能的条件下执行程序所需的资源相同。这样的分析不是规模化的,为了适用,必须丢弃信息,牺牲健全性(也就是保证所有潜在的漏洞都会被发现)。
除了上述的根本问题,还有一些实现上的挑战。最大的困难就是执行环境模拟。任何高层语义分析必须模拟应用的交互。在已有的现代操作系统中,这些交互是极其复杂的。例如,Linux包括了超过300个系统调用,因此一个完整的分析系统,就必须模拟所有系统调用的效果。

例子:


对于fuzzing测试来说,通过随机数测试可生成满足31行的触发输入,但是生成满足8行的特定值却是困难的。
而单纯的静态分析,可能将3处均检测出漏洞,却无法生成触发输入。

对于动态符号执行,可以计算并判断8行和13行的触发输入,但是无法发现31行存在的漏洞,因为有太多潜在路径不会触发31行的代码。(为什么???)

个人总结:上述说明,在使用分析技术分析一个程序时,我们需要权衡是要牺牲可重放性而找到所有潜在漏洞,还是牺牲部分信息对局部代码进行高语义的分析。同时各类检测技术具有其优缺点,在使用分析技术时也需要权衡。

B.测试集
文中测试的二进制文件来自于The DARPA Cyber Grand Challenge。然而文中指出,为了解决执行环境模拟的问题,DARPA开发了一个仅有7个系统调用的操作系统,也就是说测试集的二进制文件使用的指令也就仅为7个,这与实际的二进制文件是不同的。虽然文中主要是为了评估不同的二进制检测技术的性能,但是指令集增多与减少会造成什么问题呢?
C.CGC二进制文件的比较分析

二进制漏洞分析使用不同的基础技术来推理正在处理的应用程序。 例如,可以分析不同领域的数据,或利用与被测试应用程序的不同级别的交互进行分析。在接下来的两节中,我们将调查当前分析技术状况,并在本文的其余部分选择几个分析技术进行深入评估。我们专注于利用二进制分析技术识别软件中的缺陷(例如,基于符号执行的内存漏洞识别),而不是一般的二进制分析技术(如符号执行技术本身)。

静态漏洞检测技术

静态检测是指不执行程序的情况下对程序进行分析。它通常将程序模式化为图或数据。其缺点有:结果不可重现,需要手工验证。其二,静态分析倾向于更简单的数据域分析,而降低了语义观察力。总之,它们虽然通常权威的推断某些程序属性,但是在声明存在漏洞时,会有很高的误报率。

A.控制流图
控制流图是静态技术检测漏洞的首要条件。控制流图恢复是使用 递归算法实现的。首先分解和分析一个基本块Ba,识别它可能的出口点如Bb、Bc,并将它们加入到CFG(控制流图中),然后重复分析Bb和Bc的出口点,直到找不到新的出口点为止。控制流图有一个根本性困难:间接跳转。间接跳转发生在控制流的目标是一个值,一个寄存器或是一个内存地址。而直接跳转的目标通常是指令本身,因此可以简单的识别出来。间接跳转的目标可以根据许多因素变化,大体上,可以分为以下几类:
计算跳转:计算跳转的目标是由应用程序通过执行代码指定的计算来确定的。这个计算可能进一步依赖于寄存器或内存中的值。一个常见的例子就是跳转表:程序根据寄存器或内存中的值来确定在内存中的跳转表的索引,从中读取目标地址,然后跳转到目标地址处。
上下文相关跳转:一个间接跳转可能取决于应用程序的上下文。常见的例子就是在标准C库中的qsort()函数,这个函数使用了一个回调函数来比较传入的值。因此qsort()内的基本块的跳转目标取决于调用者,因为调用者提供了回调函数。
对象跳转:上下文的一个特例是对象。在面向对象的语言中,对象多态性需要使用虚函数,这些函数通常作为函数指针的虚表来实现,在运行时查询函数指针来确定跳转目标。跳转目标取决于传入对象的类型。
不同的技术被设计解决不同类型的间接跳转,本文将在后门讨论几个技术的实现。最后,控制流恢复的目的是解决尽可能多的间接跳转的目标,以便创建一个CFG。给定的间接跳转可能会解析为一组值(即跳转表中的所有地址,如果存在可以触发其使用的条件),并且该组值可能基于对象和上下文而改变。根据跳转目标的解决方式,CFG恢复分析有两个属性:
可靠性(sound):一个控制流恢复技术具备可靠性(sound)是指,所有潜在控制流集合都被生成图所呈现出来。当一个间接跳转被解析为程序实际上可能的目标地址的一个子集时,图的可靠性就会降低。如果一基本块潜在的目标被错过,那么它的目标块可能永远不会出现在CFG恢复算法中。并且该快的任何直接和间接跳转也将会被错过。这具有累积效应:未能解决的间接跳转可能会严重降低图形的完整性。可靠性可以认为是二进制间接跳转目标识别的真正的正确率。
完整性(Completeness):一个完整的控制流恢复构建的控制流图的所有边应该代表实际可能的控制流转移。如果CFG分析在完整性方面存在错误,则可能包含了在实际中不存在的边。完整性可以认为是间接跳转识别误报率的倒数。
一个控制流恢复分析产生一个空图可以认为是完整的,一个分析产生了每一个指令指向其他指令的图被认为是可靠的。虽然理想介于两者之间,但是使用可伸缩算法很难实现。因此不同的分析需要两者之间不同的折中(又是权衡)。

控制流图的另一个难点是精确的测量代码覆盖率,这是衡量控制流图发现多少代码的度量。然而通常由于死代码的存在而变的复杂,因为这些代码是不可跳转的。

个人总结:主要说明了控制流图的基本属性:完整性与可靠性。同时,详细描述了控制流图的主要难点:间接跳转

B.利用Flow建模的漏洞检测
一些程序中的漏洞可以通过对程序属性图分析来发现。基于图的漏洞检测,程序属性图(例如,控制流图CFG,数据流图DFG,控制依赖图CDG)可以用来识别软件中的漏洞。最初应用于源码分析中,后来被拓展到二进制分析里。这些技术依赖于对bug建模,通常为控制流或数据依赖图中的节点集合,并且在应用程序中识别这个模型的出现。然而,这样的技术适用于搜索易受攻击的代码副本,并且受益于对已有漏洞的获知。

C.利用Data建模的漏洞检测
静态分析也可以从应用程序运行的数据的抽象来推断漏洞。
值集分析。一个常见的静态分析方法是值集分析(VSA)。在高层,VSA试图识别一个任何给定程序点中紧密过度近似的程序状态(即内存或寄存器中的值)。这个可以用来了解间接跳转可能的目标或内存写操作的可能目标值。虽然这些近似缺乏准确性,但它们是健全的。也就是说它们可能会过度近似,但是从不会不近似。
通过分析内存读写的近似访问模式,可以在二进制总识别变量和缓冲区的位置。一旦完成,可以分析恢复的变量和缓冲区位置来发现重叠的缓冲区。这类重叠的缓冲区可能是被缓冲区溢出漏洞导致的,所以每一个检测都是一个潜在的漏洞。

个人总结:B和C主要讲解了静态检测时建模基于的信息:图和抽象数据。以往我常见到的是分析控制流图或数据流图来检测恶意代码,但是对抽象数据的分析原理还不是很熟悉,以后可以看下这篇文章:WYSINWYX: What You See Is Not What You eXecute. Lecture Notes in Computer Science (including subseries Lecture Notes in Artificial Intelligence and Lecture Notes in Bioinformatics)。

动态漏洞检测技术

动态检测方法是当程序在实际或模拟的环境下运行时进行检测,因为它的作用是给定一个特定的输入。动态技术可以分为两个主要类别:具体执行和符号执行。这些技术可以产生高度可重放的输入,但是在语言角度却不同。

A.动态的具体执行
动态具体执行是在最小化的环境中执行一个程序。程序正常工作在一个它通常运行的同一个数据域上(即,0和1???)。这些分析通常推断单一的路径(即,当给定特定输入时,程序将运行什么路径)。因此,动态具体执行要求用户提供测试案例。这时一个问题,因为对于大的或者未知的数据集来说,这样的测试案例不是现成的。
个人总结:具体执行就是指执行真实值,然而执行真实值需要用户提供输入进行测试。

1)Fuzzing:动态具体执行在漏洞检测上的应用最相关的就是fuzzing测试。fuzzing测试也称为模糊测试。fuzzing是一种动态技术,为了引发程序奔溃,向应用程序提供格式错误的输入。最初,这样的输入是由硬编码规则生成的,并在执行过程中几乎没有深入监控提供给应用程序的。如果应用在给定一个特定的输入时奔溃了,那么这个输入被认为触发了一个bug。否则,输入将会进一步随机异变。不幸的是,模糊测试需要测试用例。如果没有经过精心设计的测试用例进行变异,模糊测试器就会遇到一些问题。
基于覆盖的fuzzing。随着基于代码覆盖的模糊化的出现,对精心设计的测试用例的要求的到部分缓解。基于代码覆盖的模糊器试图产生最大化代码执行量的输入,其基于执行的代码越多,执行漏洞代码的机会就越高。American Fuzzy Lop(AFL)是一种负责发现最近漏洞的最先进的模糊程序,它使用代码覆盖率作为唯一指导准则,近年来发现漏洞成果推动了模糊测试的兴起。
基于覆盖的模糊测试的缺点为缺乏对目标程序的语义理解。这就意味着,它能够发现某部分代码没有被执行,但是不能了解改变输入的哪部分能够导致这块代码被执行。
基于污点的fuzzing。另一个改进fuzzing的方法就是基于污点的模糊测试。这类模糊测试分析应用程序处理输入的方式,从而了解如何修改输入促进进一步的执行。这些fuzzers中结合了污点跟踪等静态分析技术,例如数据依赖恢复。虽然基于污点的fuzzer可以理解输入哪一部分修改后可以进一步驱动程序的执行,但是它仍然不知道如何修改这个输入。

2)动态符号执行。符号执行技术弥补了静态和动态分析的缺陷,并提供了一个解决办法来改进fuzzing测试的语义理解。动态符号执行是符号执行的一个子集,它是一种动态技术,它在模拟环境中执行程序。但是,这个执行发生在符号变量的抽象域中。当这些系统模拟应用程序时,它们会跟踪整个程序过程中寄存器和内存的状态,以及这些变量的约束条件。每当到达分支条件时,执行分支分开并跟随两条路径,将分支条件作为约束条件保存在分支被采用的路径上,并将分支条件的逆作为未采用分支的路径上的约束条件。
不同于fuzzing,动态符号执行对目标程序有高语义的理解,这个技术可以推断出如何触发特定程序状态通过计算路径的约束条件,从而产生了一个合理的输入来触发应用程序中我们感兴趣的逻辑。这使它成为一个识别漏洞的非常强大的工具,也是研究中非常活跃的领域。
经典动态符号执行。动态符号执行可直接用于发现软件中的漏洞。最初应用于测试时源代码,后来由Mayhem和S2E拓展到二进制代码分析。这些引擎分析应用程序通过执行路径探索直到一个漏洞状态(例如,指令指针被攻击者的输入所覆盖重写)被识别。
然而,在前面所讨论的权衡问题再次出现:所有当前提出的符号执行技术都受到路径爆炸的限制。因为新的路径可以在每个分支被创建,所以一个程序路径的数据随着每个路径中分支指令的数量而呈现指数增长。已有有尝试通过优先排序来缓解路径爆炸的问题,并在合适情况下合并路径。然而,总的来说,对于纯动态符号执行分析引擎这个挑战还没有被克服,并且大多数的基于这些系统的漏洞发现都是很肤浅的。
符号辅助fuzzing。解决路径爆炸的一种方式是卸载大量的处理到更快的技术上,例如fuzzing。这个方法利用了fuzzing的优势,即它的速度,同时试图减轻fuzzing的主要弱点,即对应用程序缺乏语义洞察力。因此,研究人员已经将fuzzing和符号执行结合使用。这样符号辅助的fuzzer通过在动态符号执行引擎中处理由fuzzing组件标识的输入,并作出修改。动态符号执行使用对分析程序更深入的理解来恰当地改变输入,提供额外的测试用例,触发先前未探测的代码并允许模糊组件继续进行(即,在代码覆盖方面)。
非约束性符号执行。增加动态符号执行的可处理性的另一种方式是仅执行应用程序的部分。这个方法被称为非约束性符号执行(不确定翻译的对不对),能够有效识别潜在的错误,但也有两个缺点。首先,它不能确保执行应用程序部分的合适环境,这导致结果中存在许多误报。其次,类似于静态漏洞检测技术,非约束性符号执行方放弃它检测漏洞的可重放性,作为交换实现了可伸缩性。

漏洞利用

漏洞发现分析实际上是发现造成奔溃的输入。对这些奔溃输入进行分类(也就是了解哪些奔溃代表实际安全问题),是大多数方法范围之外的。然而,在漏洞的复现和分析中也已有一些工作。本文,我们将重现一个已确定的奔溃的程序,自动生成利用代码来验证奔溃对安全的影响,并在现在缓解技术的存在下强化漏洞以使其具有弹性。

A.奔溃重现
大多数的漏洞发现引擎在不符合实际的情况下执行一个测试的应用。例如许多fuzzer将会执行de-randomize(去随机化)。也就是说它们将硬编码随机性的任何来源,例如执行的pid,当前时间等等。这里有两个主要原因:首先大多数现代模糊测试方法,有一个隐含的假设:提供给一个应用程序的两个实例相同输入将同时产生相同的结果。其次,随机性建模在其他技术中(如动态符号执行),不是探索性很强的研究领域。
由于去随机化,导致漏洞检测技术报告的漏洞无法在分析环境外重现。例如一个英语在执行过程中会生成一个随机数token,并且需要在进入不安全的代码前用户提供token。在去随机化分析环境中,生成的token将始终具有相同的值,并且由分析确定的奔溃输入始终采用相同的路径,从而导致程序奔溃。但是在分析环境外,token总是不同的,而之前奔溃的输入可能会采取非奔溃的路径。
奔溃输入不可重放通常分为两类:
数据缺失。漏洞检测技术有时会设法在没有从应用程序接收到正确的响应值的情况下“猜测”正确的响应值。在我们的例子中,令牌在去随机化的环境中总是一个常数值,像fuzzer这样的分析引擎可能会不经意地猜测出来在从程序中检索出来之前。当在分析环境之外重放崩溃输入时,令牌值不匹配,导致崩溃不会发生。
关系缺失。具有低语义洞察力的检测技术,如模糊测试,无法恢复从程序检索的数据与后续提供给它的数据之间的关系。在我们的例子中,即使崩溃的输入可能会导致应用程序向用户提供token,因此稍后可以用来引起崩溃,但是fuzzer缺少输出之间的关系,即应用程序向用户提供的token和用户必须提供给应用程序的token之间的关系。
在数据缺失的例子中,输入在分析环境之外是无法重现的,并且可能会发现新的崩溃输入。存在专门识别数据泄漏的分析[42],但是我们还没有在angr中实施这种分析。
在关系缺失的情况下,随机化的崩溃输入必须被转换成输入规范,该输入规范定义如何根据从应用程序接收到的数据与稍后提供给它的数据之间的关系来与应用程序通信。 其中一种方法是Replayer,它计算程序路径的先决条件,以了解如何在实际条件下重现程序路径。

B.利用生成
利用一种或多种上述提到的方法的漏洞检测引擎可能为测试应用程序产生许多奔溃。但是,并不是所有这些奔溃都会被利用。 一个不可利用的输入的例子是一个NULL指针废弃。 由于现代操作系统不允许在地址0处映射内存,所以这些先前可利用的情况已经被减少为不可利用的崩溃。了解崩溃是否被利用有助于对漏洞进行分类(也就是理解首先要调查和修复哪些漏洞)。
测试是否一个奔溃能被利用就是尝试去利用它。为此,以及有几个系统尝试去生成一个奔溃输入,并自动把它转化为一个利用代码。

C.利用强化
近年来,二进制强化技术,如不可执行的堆栈区域和地址空间布局随机化(ASLR),已经严重降低了传统漏洞攻击的效率,如第一代自动漏洞挖掘引擎产生的漏洞。因此,即使是可以利用的漏洞,也可以通过现代保护来缓解。
目前的自动化利用技术多设计在现代缓解技术得到广泛采用之前,同时现代软件保护使得它们所产生的漏洞不起作用。为了规避这种情况,已经创建了一些方法来自动强化攻击来抵抗这些防御。这种技术通过将传统的,基于shellcode的攻击转化为利用面向返回的编程(ROP)的等效攻击。因此,需要一种自动的方法来构建ROP程序,并且已经提出了几种这样的方法。

angr介绍

angr的特点为:跨架构(ARM, MIPS, 32-bits,64-bites),跨平台(不同操作系统),支持不同的分析范例(前面提到的分析方法),实用性。
我们通过创建一套用于各种分析的模块,小心谨慎地保持它们之间的严格分离,以减少更高级别的部分(例如状态表示)对下级部分做出的假设数量 (如数据模型)。这使我们更容易在即时分析之间进行混合和转换。 我们希望这也会使其他研究人员更容易重用Angr的各个模块。在接下来的几节中,我们将讨论每个子模块的技术设计。

A.设计目标(略)

B.中间表示
将不同架构的机器码转换为中间表示利用的是Valgrind项目中的libVEX,同时Valgrind项目仍然在不断更新,满足更多的架构语言。

C.二进制加载
CLE(CLE Load Everything)模块处理一个给定的二进制文件和它所依赖的任何库,解析动态符号,执行重新定位,并正确地初始化程序状态。CLE支持大多数符合POSIX标准的系统(Linux,FreeBSD等),Windows。

个人总结:是指将二进制加载到一个虚拟的系统中吗?还是加载到某一内存?

D.程序状态表示/修改
SimuVEX模块负责表示程序状态(也就是寄存器,内存值,打开文件等)。程序状态在SimuVEX中被称为SimState,以一个状态插件的集合来实现,这些状态插件由用户指定的状态选项或状态创建时的分析来控制,目前,已有以下状态插件:寄存器,符号内存,抽象内存(被静态分析用来建模内存),POSIX,日志,Inspection检查Solver,架构信息。
这些状态插件提供了可以以各种组合方式的构造块来支持不同的分析。此外,SimuVEX实现了分析的基本单元:用应用程序代码块(在SimuVEX中,称这样的代码块为SimRun)代表对程序状态所做的语义变化。也就是说,SimuVEX提供了通过VEX表示的代码块来处理输入状态的能力,并且当我们遇到可能有多个输出状态是产生输出状态(或一组输出状态,例如条件跳转)。同样,SimuVEX这一部分也是模块化的:除了基本模块的VEX转换外,SimuVEX目前允许用户提供自己实现的python函数作为SimRun。文中也是利用这样的方式实现自己的环境模型,系统调用是利用python函数实现的,能够修改程序状态

个人总结:SimRun允许我们模拟交互环境,改变程序状态?应该是这样吧。

E.数据建模(更多介绍符号执行求解)
存储在SimState中的寄存器和内存中的值由另一个模块Claripy抽象表示。Claripy将所有值抽象为一个表达式的内部表示,并跟踪表达式使用的所有操作。也就是说,表达式x加上表达式5就变成了表达式x+5,保持x和5的链接作为它的参数。这些表达式被表示为“表达式树”,值为叶节点,操作为非叶节点。在任何时候,表达式可以通过由Claripy提供的后端转化为数据域。具体来说,Claripy提供了支持特定域(整数和浮点数),符号域(符号整数和符号浮点数,由Z3 SMT求解器提供)的后端,以及值集抽象域用来值集分析。特别的是,实现其他的SMT求解器也是有意义的,因为不同的求解器擅长解决不同类型的约束。
面向用户的操作,例如将由后端提供的结构(例如,Z3后端提供的符号表达式x+1)解释为由前端提供的Python原语(例如作为x+1的约束求解的可能整数解)。前端通过增加不同复杂度的功能来增强后端。 Claripy目前提供了几个前端:
1)FullFrontend:这个前端向用户展示了符号求解,跟踪约束,使用Z3后端来求解它们,并缓存计算结果。
2)CompositeFrontend:正如KLEE和Mayhem所建议的那样,将分解约束为独立的集合可以减轻求解器的负担。 CompositeFrontend为这个功能提供了一个透明的接口。
3)LightFrontend:这个前端不支持约束跟踪,只是简单使用VSA后端来解释VSA域中的表达式。
4)ReplaceFrontend:ReplaceFrontend拓展了LightFrontend以增加对VSA值约束的支持。当引入约束时(即 x+1<10),ReplaceFrontend分析它从而识别出引入变量的边界(即,0<=x<=8)。当ReplaceFrontend随后计算出变量x的可能值时,它将会与先前确定的范围相交,从而获得比VSA更准确的结果。
5)HybridFrontend: HybridFrontend结合了FullFrontend和ReplacementFrontend来为符号约束求解提供快速逼近支持。虽然Mayhem [16]暗示了这种能力,但据我们所知,Angr是第一个向研究界提供这种能力的公共工具。

个人总结:前端是面向用户的接口,后端是实际求解器?Z3,VSA都是求解器吗?

F.完整程序分析
angr提供了完整的程序分析,如控制流图恢复,动态符号执行。这些分析的入口点为“Project”,代表了一个二进制和它相关的库。通过这个对象可以访问其他子模块的所有功能(即创建状态,检查共享对象,检索基本块的中间表示,用Python函数挂钩二进制代码等)。此外有两个主要接口用于完整程序分析:Path Groups和Analyses。
Path Groups。PathGroup是动态符号执行的接口。它跟踪应用程序执行的路径的分离和终止。这个接口的创建源于符号执行期间路径管理的缺点或挫败感。angr开发早期,我们将对每一个使用符号执行的分析实路径的专门管理。我们发现自己重新实现了相同的功能:跟踪路径的分离和合并,分析那一条路径是感兴趣的并优先探索,哪些路径是糟糕的应该及时终止。我们统一了对一组路径采取的常见操作,创建了PathGroup接口。
Analyses。angr为任何完整的程序分析的抽象提供了Analysis类。这个类管理静态分析的生命周期复杂的动态分析
当angr识别出二进制的一些信息(如“地址X处的基本块可跳转到地址Y处的基本块“),将其存储在相应项目的知识库中。 这个共享的知识库允许分析模块协作地发现有关应用程序的信息。

个人总结:SimuVEX与Claripy的差别?SimuVEX可能获取的是程序的各类状态信息,同时支持自己编写SimRun来模拟环境,修改程序状态。而Claripy则更重于表示数据域,也就是内存与寄存器中的值的抽象表示,相关表达式,并求解。总的来说SimuVEX与程序状态有关,而Claripy与符号执行求解有关系。其中接口PathGroup与类Analysis十分重要。一个与符号执行中路径管理有关,一个完全程序分析的过程有关。

G.开源版本
angr具有65000行的代码,可直接利用IPython shell或作为一个python模块来使用,采用pip安装。目前已经开发的模块包括上述说明的A-F,以及控制流恢复,静态分析框架,动态符号执行引擎和非约束的符号执行。目前它们处于原型级代码混合的状态,并积极地应用于DARPA网络挑战大赛。

angr实现

本部分主要描述angr实现过程中采用了哪些算法和技术。

控制流图恢复

本部分描述了angr生成控制流图的过程,包括提高完整性和可靠性的特定技术。
给定一个特定的程序,angr从程序的入口点开始迭代实现CFG恢复,并进行一些必要的优化。 angr利用强制执行,反向切片和符号执行的组合,尽可能的恢复每个间接跳转的所有跳转目标。此外,它还生成并存储大量关于目标应用程序的数据,这些数据稍后可用于数据相关性跟踪等其他分析。
然而这个算法有三个主要的缺点:很慢,不能自动处理死代码,可能遗漏那些只能通过不能恢复的间接跳转来访问的代码。为了解决这个问题,我们创建了第二个算法,该算法使用二进制的快速反汇编,无需执行任何基本块,然后是启发式识别函数,函数间控制流和直接函数间控制流转换。然而,第二个算法不太准确,因为它缺乏关于函数间的可达性信息,不是上下文敏感的,不能恢复复杂的间接跳转。
下面我们将讨论我们的高级算法,CFGAccurate,然后讨论我们的快速算法,CFGFast。

A.假设条件
angr的CFGAccurate在运行这个算法时做出了几个假设。
1)程序中的所有代码可以被划分到不同的函数中;
2)所有的函数都可以通过一个显示指令调用(或者它的等价函数),或者被控制流中的尾部跳转处理(尾部跳转:一个优化操作,通常用于减少递归函数的堆栈空间,在函数的最后一个调用去跳转,以使新的调用函数可以简单地重新使用它调用者的返回地址)。
3)无论从哪里调用,每个函数的堆栈清理行为都是可预测的。 这使得CFGAccurate可以安全地跳过已经分析过的函数,同时分析调用函数并保持堆栈平衡。
这些假设限制了angr分析的二进制文件类型。 假设条件1,2和3要求被分析的二进制文件不被混淆,并且以“正常”方式运行。 在分析混淆的或异常的二进制文件时,我们可以删除这些假设,但是这会导致CFG恢复运行时间更长。相比于已有工作,angr的假设条件更接近实际一些。
其他已有工作的假设条件:
1)所有功能都返回到他们的调用点之后的下一条指令[59]。
2)间接分支的跳转目标总是由控制流程决定,而不是由程序状态决定或上下文[59]。例如,一些现有文献假设间接跳转都是被计算出来的,而不是被作为前一个上下文的函数指针传入。
3)间接跳跃跳跃目标的表达式必须与一组常用语相匹配[21],[58]。与现有的工作不同,我们对可应用于指针的操作类型不作任何假设。
4)进入一个函数之前,堆栈指针是一样的。
5)没有两个函数重叠(换句话说,它们不能共享基本块[34]。)CFGAccurate能处理共享代码的函数。
6)可以获得一些附加信息,如符号表或重定位信息[50]。

B.迭代生成CFG
CFGAccurate基于的是一系列交错的技术来生成满足速度和完整性要求的控制流图。具体来说,使用了四种技术:强制执行,轻量后向切片,符号执行和值集分析。其中,以程序入口点的基本块来初始化图。
在整个CFG恢复期间,CFGAccurate维持一个跳转目标尚未确定的间接跳转列表Lj。当分析识别到这样的一个跳转,就把它加入到列表Lj中。在每个迭代终止后,CFGAccurate触发列表中的下一个。下一个技术可能解决Lj中的跳转,也可能向Lj加入新的未解决跳转,并可能会向CFG的图中添加基本块和边。当所有技术运行后都不会导致Lj和图(C)发生更改时,CFGAccurate将终止,因为这意味着不能通过任何可用的分析来解决这些间接跳转。

C.强制执行
angr的CFGAccurate在CFG恢复第一阶段利用了动态强制执行的概念。强制执行确保每个条件分支点的分支方向都会被执行。
CFGAccurate维护了一个基本块的工作列表,Bw,和一个已分析的基本块的列表Ba。当分析开始时,它以所有的在C中但不在Ba中的基本块来初始化它的工作列表。每当CFGAccurate分析列表中的基本块时,基本块和块中的任何直接跳转都被添加到C中。然而,间接跳转不能以这种方式处理。在强制执行下,间接跳转的目标可能与程序实际执行时不同,因为强制执行会以意外的顺序执行代码。因此,每一个间接跳转将被存储到列表Lj中供后续分析。
因为强制执行不能解决任何间接跳转,因此这种分析用作一个快速的CFG恢复分析,以快速为其他分析提供检测到的基本块和未解决的间接跳转。

D.符号执行
动态强制执行的主要问题是间接跳转的存在,因为无法确定间接跳转的目标是否正确地解决了。一方面,间接跳转可能是完全不可解决的(例如:强制执行导致了一个状态,在这个状态中,跳转目标从未初始化的内存读取),这将在已恢复的CFG中留下一个中断的控制流转换。另一方面,间接跳转也可能是部分可解的(例如,我们的分析只检索所有可能的跳转目标的一部分)。
对于每个跳转J ∈ Lj,CFGAccurate后向遍历CFG直到它找到第一个合并点(也就是说,多条路径收敛到这个间接跳转上)或块的数量达到阈值(本文合理的阈值为8)。从这个点,它执行前向符号执行到间接跳转并使用约束求解器来检索间接跳转目标的可能值。
CFGAccurate认为如果计算出的一组可能的目标小于阈值则认为跳转成功解决。我们使用256的值作为这个阈值,但是我们发现,在实践中,在跳转未被成功解决的条件下,这个值是无约束的(意思是,可能目标的集合仅仅受地址bit数目束缚)。
如果跳转成功解决,则将J从Lj中移除,并且对于跳跃目标的每个可能的值,边和节点都被添加到CFG中。

E.后向切片
angr的强制执行和符号执行分析由于缺少上下文,都不能解决多数的跳转。在遇到函数将指针作为参数,并且指针被用来当做间接跳转的目标时,上述分析无法解决。
为了实现CFG的完整性,我们利用反向切片。CFGAccurate从未解决的跳转开始计算一个反向切片。切片延伸到前一个调用上下文的开始处。也就是说,如果间接跳转在一个函数Fa中被分析,同时Fa被Fb和Fc调用,那么切片将会从Fa反向拓展到两个开始节点:一个开始于Fb,另一个开始于Fc。
CFGAccurate然后使用angr的符号执行引擎执行这个切片,并且使用约束引擎来确定符号跳转的可能目标,对于跳转目标的解决方案的大小阈值为256。如果成功解决了跳转目标,则从Lj中删除跳转,同时将目标基本块添加到恢复的CFG,边代表控制流的转换

F.CFGFast
快速CFG生成算法是生成一个高代码覆盖率的图并且至少识别出二进制中的函数的位置和内容。这个图缺少控制流,所以它是不完整的。然而,这样一个图对于手动和自动二进制分析仍然是有用的。CFGFast执行步骤如下:
函数识别。我们使用类似于ByteWeight技术产生的硬编码的函数签名来识别应用中的函数。如果应用程序包含指定函数地址的符号,那么它们也会被用于生成带有函数起始地址的图。此外,代表程序入口点的基本块也会被加入到图中。
递归反编译。递归反编译用于恢复已识别函数的直接跳转。
间接跳转解析。轻量级别名(alias)分析,数据流跟踪,结合预定义策略,被用来解决函数控制流转移。目前CFGFast包括跳转表识别和间接调用目标识别的策略。
快速CFG恢复算法的目标是快速地恢复一个高覆盖率的控制流图,而不关心函数之间的可达性。
G.使用CFG恢复
angr公开了两个CFG恢复算法:CFGFast和CFGAccurate。这些分析将CFG数据输出到angr的知识库中,后面会介绍。这些数据可以在手动分析或随后的自动分析使用。

集值分析

一旦生成了控制流图,就可以进行更高级的分析,例如集值分析。集值分析(VSA)是一个结合了二进制程序的数值分析和指针分析的静态分析技术。它使用一个称为集值抽象域来近似可能的值,这些值可能是寄存器或抽象位置在每个程序点可能保存的值。
VSA分析程序直到达到函数中所有的程序点的固定点。这个固定点表示任何寄存器或抽象内存位置再函数中可能具有所有值的过度近似。例如,向一个计算出的内存地址A写入,计算出的固定点中的A的值将包含所有可能写入目标的完整列表(???)。
创建一组间隔。VSA中的基本数据类型就是间隔,是组值的近似。有助于近似一组普通混合的值。然而,如果这些值当作跳转目标使用,过度近似将导致恢复的控制流图定向到不是跳转目标的地址。为了有效解决这个问题,我们提出了一个新的 数据类型叫做间隔集(strided interval set),这个集合代表未被划分的间隔的集合。一个间隔集能够被划分到一个单独的间隔,仅当它包含超过K个元素,K是一个可以被调整的阈值。更高的K允许我们获得更高的准确率,但是也增加了分析的复杂度。
应用代数求解器到路径预测中。跟踪分支条件能够帮助我们约束那些在条件退出和合并程序的状态中的变量,从而产生更准确的分析结果。Affine-Relation分析已经被提出用来跟踪这些条件。然而,实现起来较复杂,且实际开销比较大。我们的解决办法是实现一个轻量级的代数求解器,该求解器工作在间隔域,基于模运算解决映射关系。当遇到新的路径判断(即,当跟踪到一个条件分支时),我们尝试简化并且求解它,从而获得这个路劲判断上的值的数值范围。然后我们对每个相关变量取新生成的数值范围与原始值得交集。这使我们遇到一个新的分支时不断改变值集分析的结果,增加最终固定点的准确率。
采用signedness-agnostic(符号未知)域。正如最初提出的,VSA在有符号的间隔域上操作,也就是假设所有值都是有符号的。也就是说,对于一个n比特的间隔,l是它的下界,h是它的上界,那么我们总有l并h并l小于等于h。然而,这严重导致无符号计算结果过度近似。事实上,过度近似在实际中更加恶化,因为跳转地址是无符号的,跳转地址通常依赖于无符号值(即,无符号比较的条件下)。为了解决这个问题,我们采用了符号未知域进行分析。Wrapped Interval 分析就是这样的间隔域用于分析LLVM代码,它同时关注有符号和无符号的数值。我们基于我们的符号未知间隔域理论,应用于VSA域。
我们使用VSA分三个阶段进行内存损坏检测。首先,我们收集程序中所有读写访问模式。在这些访问模式之上,我们对栈和堆区域的变量进行恢复。我们的实现类似于TIE中的可变恢复[36]。接下来,我们扫描所有栈和堆区域以查找异常缓冲区,其中包括:a)重叠缓冲区,以及b)超出边界的缓冲区。然后,我们只需将所有异常缓冲区报告为潜在的内存损坏。
A.VSA的使用

Angr提供的完整程序VSA分析的主接口是值流图(VFG)。VFG是一个增强型CFG,其中包含表示每个程序位置的VSA固定点的程序状态。根据传递给VFG分析的参数,这参数可以为单个函数,函数调用树或整个程序。VFG中包含的程序状态以SimuVEX提供的抽象布局(具体为,SimAbstractMemory内存模型)呈现内存,内存值由Claripy提供的值集表示。我们通过分析内存访问可能需要的值的范围,对这些程序状态中包含的数据执行缓冲区重叠分析。

个人总结:值集分析是对内存或寄存器中的值进行近似求解,angr利用它对数值进行近似,然后查找内存溢出或泄露漏洞。算法的细节可以参考文中提及的域以及相关分析。具体如何得到内存中值得近似值没有说明。需要进一步学习,不知道与符号执行是否相关。

动态符号执行

我们动态符号执行模块基于的是Mayhem技术。我们实现采用的是相同的内存模型和路径优先技术。这个模块代码的是angr的核心功能之一,其他的分析例如,无约束符号执行,也是以它为基础。
我们使用Claripy到Z3的接口来构建SimVEX提供的符号内存模型(也就是,SimSymblicMemory)。单独的执行路径是通过程序中Path对象管理的,它可以跟踪路径行为,路径预测,和其他各种路径特定信息。路径组管理是由angr的PathGroup提供的,它提供了一个接口来管理在动态符号执行时路劲的分离、合并和过滤。
angr内置了对Veritesting的支持,将其作为Veritesting分析来实现,并通过传递给PathGroup对象options为其提供透明支持。这种先进的合并技术通过静态(和有选择的)合并路劲还缓解指数状态爆炸的问题。

非约束性符号执行

我们根据UC-KLEE实现了非约束的符号执行,并称它为UC-angr。UCSE是一种每个函数分开执行的动态符号执行技术。因为这种分析无法推断如何得到特定函数,因此UCSE的检测无法重放。因为每个生成的函数,没有上下文(即,实际情况中调用时的参数和全局变量),所以分析是不准确的并且具有高误报率。
UCSE表示状态中缺少上下文,称为无约束。当这种无约束的数据作为指针使用时,一个新的无约束区域则被创建,并且这个指针将直接指向这个新的区域。这种按需内存分配使得管理复杂数据结构的代码得意分析。当发现安全违规时(即,写入堆栈上保存的返回地址)时,将检查相关值是否处于非约束状态。在某些条件下(即,如果所有的数据都是无约束的),这个违规将被过滤为误报率。
我们对UCSE中描述的技术做出了两处更改:
全局内存不足约束。
最初的UCKLEE实现不会将对全局内存的访问视为受到限制。但是,这种内存是程序上下文的一部分,这是UCSE无法预测的,因为在分析给定函数时,全局数据可能已被覆盖。因此,我们将所有全局数据标记为非约束,从而降低我们的误报率。
路径限制
原来的UC-KLEE实现有几个内置的限制,以防止路径爆炸。例如,它们将限制非约束指针解引用的深度,以避免通过非约束性链表进行搜索永不终止。我们添加了一个额外的限制器:当我们发现它负责路径爆炸时,我们放弃对函数的分析。我们通过硬编码限制(在我们的实验中,我们使用了经验确定的64条路径限制)来检测这个问题,并且当一个函数分支数目超过这个限制时,我们用一个立即返回的函数进行替换,并回退分析到该函数的调用点。这样可以避免路径爆炸,从而使分析更容易处理,但会使分析更加不准确。
误报率过滤。
我们引入了几个额外的误报率过滤器,在实现UC-angr的时候。尤其是,当我们检测一个可利用状态时,我们尝试保证这个状态不会因为缺乏约束而被非约束性数据错误地利用。首先,我们使用附加约束E来执行约束求解,该约束表示状态不可被利用(即,如果安全违规是覆盖返回地址,那么我们限定状态,使得返回地址不能已经被覆盖)。然后,我们限制每一个非约束的值为它的可能解决方案从这个不可利用的状态,我们称这些约束为U。最后,我们移除这些约束E,保持约束U,然后检查状态是否仍然可以被利用。如果可以则意味着这个函数可能有一些隐式流,并且这些流并不一定依赖于上下文中遗漏的数据。注意,由于缺少约束,或者由于受限制的上下文数据是非约束的,这个流仍然可能是误报。

UC-angr是作为SimState的插件实现的,它跟踪无约束数据并执行所需的重定位。一旦这个插件被初始化,那么无约束符号执行将会被执行,并与动态符号执行使用相同的PathGroup。

个人总结:对无约束动态符号执行不了解。

符号辅助的Fuzzing

本篇文章将简要介绍下符号辅助的Fuzzing测试,完整方案称为Driller,可以参考另一篇文章。
我们使用AFL模糊测试器作为符号辅助Fuzzing的基础,并使用angr作为它的符号跟踪器。通过监测AFL的执行,我们能够决定什么时候开始符号跟踪AFL产生的输入。为了做出这个决定,我们按照模糊器发现新状态转换的速率采取行动。如果AFL报告说,在执行了一轮输入变化后没有发现新的状态转换,我们假设模糊器难以取得进展,并且在AFL被认为是唯一的所有路径上引用angr(即,任何路径有一个跳转,由源地址和目标地址的元组标识,没有其他路径),寻找AFL无法找到输入的转换。

Driller的符号组件是通过使用angr的符号执行引擎来实现的,以便根据AFL提供的具体输入来符号性地追踪路径。这避免了符号执行固有的路径爆炸问题,因为每个具体输入对应于单个(追踪)路径,并且这些输入经AFL严格过滤以确保仅追踪有希望的输入。每个具体输入对应于PathGroup中的单个路径。 在PathGroup的每一步中,检查每个分支以确保最新的跳转指令引入先前AFL未知的路径。 当发现这样的跳转时,SMT求解器被查询以创建一个输入来驱动执行到新的跳转。这个输入反馈给AFL,AFL在未来的模糊步骤中进行变异。 这个反馈循环使我们能够将昂贵的符号执行时间与廉价的模糊时间进行平衡,并且减轻了模糊对程序操作的低语义洞察力。

个人总结:Driller也是本文作者开发的,它的原理就是结合了angr的符号执行功能与AFL的fuzzing测试。这里又涉及到了状态的改变,无论是符号执行还是fuzzing测试,我理解测试的单元就是状态。本段又出现的求解器为SMT,这几个求解器Z3,SMT分别都是什么功能,有什么差别,是下一步需要学习的。

奔溃重现

我们实现了Replayer [43]提出的方法来恢复输入值(即攻击者发送的值)和输出值(即攻击者从应用程序泄漏的值)之间的缺失关系。

我们对Replayer的实现建立在我们的符号执行引擎之上。我们可以定义重放崩溃输入的问题,因为搜索输入是将程序从初始状态s带入崩溃状态q。我们的算法将程序P,初始状态sa(即可执行文件入口点的状态),崩溃状态qa和输入ia作为输入,在instrumented(去随机化)环境中可以将程序从sa带到qa,但无法在uninstrumented环境中正确重放。我们的实现使用输入ia符号性地执行从sa到qa的路径。它记录了执行P时生成的所有约束条件。给定约束条件,执行路径,程序P和新的初始状态sb,我们可以用一个无约束符号输入象征性地执行P,跟随先前记录的执行路径,直到新碰撞状态qb已达到。此时,可以分析输入和输出的输入约束条件,并可以恢复它们之间的关系。这种关系数据用于生成输入规范,允许重放崩溃输入。

Replayer提出的实现在应用崩溃复制方面有两个主要限制。首先,正如我们在第V-A节中讨论的那样,给定的崩溃可能不会检索正确重放崩溃所需的所有数据。 Replayer无法处理这些情况,并且必须找到新的崩溃输入。其次,Replayer只使用确切的路径,由处理崩溃输入的应用程序在非随机环境中执行,以生成输入规范。如果基于随机数据的确切值,二进制文件的执行轨迹发生变化,则Replayer无法计算正确的输入。例如,如果随机cookie引入了路径预测,那么通过解码函数执行特定路径,使用该确切路径重播执行将会将cookie限制为可能与最初路径不同的值。发生这种情况时,重放的cookie将不正确,重放尝试将失败。我们将在后面讨论,AEG正面临类似的限制。这表明这方面的研究可以在这两项任务中取得进展。

利用生成

通过实现类似于AXGEN[51],AEG[4]和Mayhem[16]中描述的算法,我们能够评估现有技术在自动生成漏洞利用方面的有效性。我们的实现允许我们创建漏洞利用,允许攻击者通过覆盖保存的指令指针(例如,通过覆盖函数指针或利用堆栈上的缓冲区溢出)来控制程序的执行。

漏洞状态。 

与AEG / Mayhem不同,但与AXGEN类似,我们通过使用angr在崩溃程序输入时执行混合执行来产生漏洞。我们推动concolic执行,迫使它遵循与通过具体执行应用于程序的崩溃输入收集的动态跟踪相同的路径。Concolic执行在程序崩溃的地方停止,我们检查符号状态以确定崩溃的原因并评估可利用性。通过计算某些寄存器中符号位的数量,我们可以将崩溃分为许多类别,例如帧指针覆盖,指令指针覆盖或任意写入等等。

个人总结:在奔溃发生的地方,检查寄存器,观察符号位,找到奔溃原因与类别。

指示指针覆盖技术。 

我们可以遇到的最简单的可利用错误是符号位在崩溃时刻出现在指令指针中的位置。当检测到指令指针中包含符号位时,我们可以将指令指针限制为指向受控序列的指令(如shellcode)或ROP gadget,它将堆栈转移到符号缓冲区,在那里我们可以执行ROP链(由我们的攻击强化步骤产生)。Angr本身处理AEG和AXGEN中讨论的许多实现细节,例如污点跟踪和路径条件构建,使我们能够限制自己找到符号化内存缓冲区并应用约束到寄存器的值以生成利用,正如这些方法所提出的。

利用CGC二进制文件。 

Cyber Grand Challenge将在一个只包含7个系统调用的自定义操作系统上托管比赛。缺乏可执行程序和打开文件的系统调用意味着在Cyber Grand Challenge中进行的开发仅限于演示注册控制以及读写内存的能力。按照DARPA标准,CGC存在两种类型的漏洞:

类型1:攻击者可以控制通用寄存器和指令指针寄存器。
类型2:攻击者可以执行可控读取从进程内存空间。

在我们应用AEG的126个二进制文件中,我们成功地只开发了4个二进制文件。对于这些二进制文件中的两个,我们能够生成“类型2”漏洞。 这两种“类型2”攻击都无法用ROP加固,并跳入shellcode。此外,AEG只能生成2个强化的ROP“类型1”漏洞。我们相信这些结果表明,自动化漏洞生成领域需要做更多的工作,目前的方法并不能很好地适用于现代漏洞。

面临的挑战。

在这里,我们演示了一些我们的工具在尝试利用CROMU00019[24]攻击Cyber​​ Grand Challenge二进制文件时遇到的挑战。我们将重点讨论在本次挑战的README文件中提到的第二个漏洞(特别是在解码攻击者提供的字符串时存在的堆栈上的缓冲区溢出)。
我们在exploit生成过程中遇到的主要问题是存在 路径谓词,它将溢出数据的每个字节限制为单个值,尽管这些字节的值是基于符号输入进行选择的。 CROMU00019在其解码功能中演示了这一点。有效载荷的每个字节都包含解码中包含的switch语句的一个分支,将限制性谓词放置在表示易受攻击状态的路径上。虽然此switch语句的arms是基于符号数据采集的,但返回的数据是具体的,并且这些arms中的每一个都代表了通过该程序的单独路径。传统的AEG方法假设能够在符号数据上设置适当的约束条件来执行控制流劫持,但是这种行为需要通过解码函数找到单个路径,该路径将我们期望的字节放入输出缓冲区。

解决这个问题的办法是搜索一条单一的路径,在存在易受攻击的条件的许多路径中执行所需的控制流劫持。 然而,现代漏洞利用生成功能没有这种能力,像这样的情况可以防止当前最新的自动漏洞利用生成技术利用CGC Quaifier事件中出现的许多堆栈缓冲区溢出漏洞利用。

个人总结:路径谓词是什么?path predicates,自动化生成利用代码,机器学习解决。

利用强化

为了加强对现代缓解技术的攻击,我们基于Q[48]中的想法实施了ROP链编译器。 这意味着我们可以自动生成ROPpayload以实现最终目标,例如将数据写入内存或调用库中的任意函数。本节重点介绍我们对Q本身的差异和改进。
我们方法由以下几步组成:
1.Gadget发现。
我们在每个字节偏移处扫描应用程序中的所有可执行代码,以识别ROP gadget并根据其影响对它们进行分类。例如,指令序列:mov [ebx],eax; pop ebx; ret将被分类为内存写入和寄存器加载。为了进行分类,我们的分析利用了由Angr的Path对象提供的行动历史和由Claripy提供的符号关系。
2.Gadget安排。
然后,ROP链编译器确定可用于执行高级操作的gadget安排。例如,将数据推送到堆栈的gadget可以与弹出数据的gadget配对,以创建将数据从一个寄存器移动到另一个寄存器的配置。
3.有效负载生成。 
在ROP编译器识别出必需的gadget安排集后,它将这些gadget组合成一个链来执行高级别操作(例如执行具有指定参数的攻击者指定的系统调用)。这是通过将gadget安排写入程序状态中,将其输出限制为所提供的参数,并向SMT求解器查询其输入的解决方案。
我们的实施方式与Q方式不同。首先,Q没有使用堆栈作为临时存储空间。目前尚不清楚这是为什么:一种解释是他们的分析平台不支持堆栈操作的建模,另一种解释是如果我们假设堆栈不一定指向栈指针(并且,因此,在一个未知的位置)。在我们的集成系统中,我们可以识别堆栈指针是否指向堆栈,因为我们从我们实现AEG时生成的漏洞获取了这些元数据。

另一个改进与gadget分类有关。Q使用价值抽样方法来识别特定类别的gadget,由于样本数据的覆盖范围有限,导致一些丢失的gadget链。 在我们的方法中,我们使用仔细缓存技术(careful caching technique)来分析每个gadget,以便快速分析结果。


综合评估

通过利用Angr的设计,我们能够在同一代码库中重现我们已经讨论过的二进制分析技术,从而能够对它们的有效性进行比较评估。据我们所知,这是以前没有做过的事情:先前的比较是针对不同的实现进行的,留下了实现差异导致结果差异的可能性。除了模糊器本身(AFL)之外,我们的分析都是在同一个分析引擎上实现的,并且相互分享超过90%的相同代码库。
我们使用由DARPA发布的用于CGC资格赛的CGC二进制文件的语料库来执行我们的评估。 正如第II-B部分所讨论的那样,这些二进制文件的复杂性差别很大,但是利用了由DARPA设计的简单环境模型来减少分析系统的实施工作。

我们评估了我们为CFG恢复,动态和静态漏洞发现,崩溃重放,利用和攻击强化实施的技术。 表1列出了我们实施和评估的分析总结,以及它们所依据的文献和本文中描述它们的章节。

表1


控制流图(CGC)恢复

由于CFG被用作Angr其他分析的先决条件,因此了解Angr的CFG恢复效果如何很重要。正如我们在第七节中详细讨论的那样,angr有两种CFG恢复算法:CFGAccurate依赖强制执行的基本方法,并提供两种间接跳转解决方法(向后切片和符号反向遍历),而CFGFast主要使用递归反汇编启发式快速识别功能和功能之间的控制流程。
为了理解这些恢复技术的有效性,我们比较了CGC二进制文件中CFGFastCFGAccurate与最先进的商业工具IDA Pro 6.9的CFG恢复情况。尽管关于IDA Pro如何恢复CFG的细节很少,但根据之前工作的描述[59]以及我们的观察,我们相信IDA Pro会递归地分解二进制文件,使用符号和其他启发式来确定函数的位置,然后利用一些轻量级的数据流分析来进一步解决间接跳转的目标。这使得它在概念上更接近于CFGFast而不是CFGAccurate。由于基本事实CFG信息不可用,因此我们根据恢复基本数据块的相对数量以及IDA和CFG恢复结果之间的控制流转移来评估我们的结果。

我们首先评估CFG的完整性,比较由CFGFast标识的块和边以及由IDA Pro生成的图。表2显示了我们的结果。CFGFast比IDA Pro有更好的代码覆盖率,并且可以恢复更多边。我们认为这是因为CFGFast使用的轻量级数据流分析和启发式算法比IDA使用的更先进。手动分析几个二进制文件的恢复结果表明,CFGFast在代码恢复方面更加积极:虽然IDA Pro认为代码的某些部分无法访问并拒绝将其作为代码反汇编,但CFGFast将这些位置标识为代码。对此可能的解释是我们的应用可能过于激进,因此可能会错误识别这些位置。但是,我们在分析CGC二进制文件时没有发现这种情况。

个人总结:CFGFast出现的问题是容易将非代码字段识别为代码字段,使用时要注意这个问题。

由于某些二进制分析需要入口点的可达性信息,因此我们还包含了与IDA Pro生成的CFG的可到达部分的比较(即,包含那些可确定入口点路径的块的CFG) 与angr的CFGAccurate分析恢复的CFG。表2显示了我们的结果。通过改进强制执行技术和反向切片,angr大大提高了重建CFG的能力。然而,由于CFGAccurate没有利用临时启发式,所以CFG的代码覆盖率并不像IDA Pro那么高。为了获得更好的覆盖范围,用户可以提供CFGAccurate和CFGFast的所有恢复功能作为起点。

个人总结:CFGAccurate出现的问题是由于采用的不是启发式识别,所以代码覆盖率不高,文章给出的建议是两个都使用。

表2




猜你喜欢

转载自blog.csdn.net/doudoudouzoule/article/details/79197203