angr 文档翻译(1-1):简介以及核心概念
什么是Angr?我如何使用它?
angr 是一个支持多处理器架构的二进制分析工具包,它具有对二进制程序执行动态符号化执行(像MAyhem,KLEE等工具一样)以及多种静态分析的能力。如果想要学习如何使用它,你算是来对地方了!
Mayhem是一个自动化寻找二进制程序漏洞的系统。相关链接
KLEE和符号执行简述
我们已经尽力使angr的使用不那么费劲——我们的目标是创造一个对用户友好的二进制分析套件,它使用户只需要打开iPython并且输入几条的命令就能对一个二进制文件进行简要的分析。话虽如此,二进制分析是复杂的,这使得angr也很复杂。这个文档提供使用叙述性的解释和对angr及其设计的探索来帮助用户理清复杂的angr。
编程分析二进制文件需要克服许多困难,大致列举如下:
加载一个二进制文件到分析器中。
将二进制转化为中间表示形式(intermediate representation)
-
执行分析,可以是:
- 对二进制文件局部或整体的静态分析(比如依赖分析,程序切片)
- 对程序状态空间的符号化探索(比如“我们可以执行这个程序直到我们找到一个溢出吗?”)
- 上述两种分析在某些程度上的结合(比如“我们只执行程序中对内存写的程序片段来找到一个溢出。”)
angr提供应对上述挑战的组件。这个文档将会解释每个组件是如何工作的,以及如何使用它们来完成你的邪恶目的:)
译者注:关于angr的安装,各个系统不太一样,这里就不翻译了。总体而言,linux安装比windows顺利一些,具体操作在angr的github上有介绍,主要比较坑的是按照angr提供的安装脚本来安装时会有几个python库不符合最低版本要求,找到并更新这些python库即可(好在angr的github上也提供了符合条件的相应python库
核心概念
顶层接口
在开始使用angr之前,你需要对angr的一些基础概念以及如何创建angr的基本对象有个基本的了解。我们将从当你加载了一个二进制文件后所能直接获取的东西开始介绍这些基本概念和操作。
在使用angr时你的第一个操作总会是加载一个二进制到一个“Project”里。我们使用/bin/true
这个二进制程序作为例子进行分析
译者注:这个文件在linux系统下的/bin目录里,读者也可以使用其它二进制文件作为例子
译者注:文档中所有的代码都会在译者的deepin 15.08中实验,并贴出执行结果
一个project是你在angr中控制二进制程序的基础。有了它,你就可以对你刚刚加载的可执行程序进行分析和模拟。你使用angr时使用的几乎每一个对象都会以一定的形式依赖于project的存在。
基本属性
首先,我们介绍一些project的基本属性:它的CPU架构、文件名和入口点地址。
- arch是对象
archinfo.Arch
的实例,表示这个程序编译的目标平台是什么,在这个例子中是小端amd64.这个实例包含大量关于该程序(/bin/ture
)目标CPU(AMD64)的文书资料
译者注:包括寄存器、位宽以及各种变量的位宽等详细数据,这应该是angr模拟目标处理器的基础),你可以在这里看到它的源码。一般情况下你可能比较关心的是
arch.bits
,arch.bytes
,arch.name
和arch.memory_endness
这些属性。
- entry属性是二进制程序的入口点。
- filename属性是二进制程序的绝对路径,无疑是个好东西!
加载器(The Loader)
获取一个二进制文件在虚拟内存空间中的表示是一件十分复杂的事!我们使用一个叫做CLE
的模块来解决它。CLE(也称为加载器)的执行结果可以使用.loader
属性获取。我们很快将会介绍这个属性的细节,但是现在你只需要知道你可以使用它来看angr加载你的二进制程序的同时还加载了哪些依赖库,并执行一些关于已经载入程序的地址空间的基本信息的问询操作。
The Factory
angr里面定义了许多类,大多数类都需要使用project来实例化。为了不使你的代码里到处都是project,我们提供一个project.factory
对象,它为一些你经常使用的对象内置了一些方便的构造函数。
这个部分还将会介绍angr的几个关键概念,请仔细阅读!
块(Blocks)
首先,我们有project.factory.block()
方法,它被用来从给定的地址处提取一个基本块
译者注:一个“基本块”大致是指一个没有分支的代码块,适合作为代码分析的基本单位)。
一个重要的事实是:angr以基本块为代码分析的基本单元。
你将会得到一个Block
对象作为返回值,它可以告诉你许多关于指定代码块的有趣信息:
译者注:经过我的测试,对block()指定地址作为参数之后,将会把从指定的地址开始直到遇到第一个分支(比如call,jmp等指令)为止的所有字节作为block的分析内容,而不会向上回溯这个地址前代码块内容(即使这个地址之前的代码也可能和当前block中的内容在同一个基本块中),下面是我的测试的代码以及几个典型结果的截图:
译者注:可以看到,输入的地址不论是不是正确的指令起始地址,执行pp之后都会进行反汇编到这个block的末尾,值得注意的是,当参数是0x401690、0x401691、0x401692、0x401693时,反汇编工作无法进行(因为字节码非法),因此返回空;当指定地址破坏了原call指令(分支标志)的字节码时(0x401694),会从当前字节码开始进行反汇编,直到发现一个分支指令(在这里是
halt
)为止。由此可以推知,使用block方法产生的block是根据输入的地址动态产生的,因此获取想要的block需要输入正确的指令地址。
block.pp()
中的"pp"是“pretty print”的意思,这个函数将会打印当前代码块的反汇编代码到标准输出。
block.instructions
显示当前代码块有多少条指令。
block.instruction_addrs
显示每条指令的起始地址。
另外,你也可以用一个block对象来获取一个block中的代码的不同表示形式:
状态(states)
这是另一个angr的概念——project
对象只表示一个程序的“初始镜像”。当你用angr执行程序的时候,你实际操作的是一个代表程序的模拟状态(simulated program state)的特定对象——一个Simstate
.现在让我们获取一个(Simstate)看看吧!
一个Simstate包含一个程序的内存,寄存器,文件系统数据…任何在程序执行过程中可能被修改的“实时数据”都会在state中被存储。在之后的章节中我们将会深入展示如何和这些状态交互,但是现在,我们只需要使用state.regs
和state.mem
访问当前状态的寄存器和内存即可:
这些值不是python中的int
类型!而是bitvector
。Python的整型数和CPU的整型数在语义上是不同的,例如,python提供整数溢出上的包装。所以我们转而使用位向量。你可以把它理解为为了在angr中表示CPU中的数据而用一系列比特表示的整型数。需要强调的是每个位向量都有一个.length
属性,描述了这个位向量能够容纳多少个比特。
我们很快将会学习如何使用位向量,但是现在只介绍如何将python的整数类型和位向量的相互转换:
译者注:实际上angr是封装了claripy这个python库的功能来实现位向量的定义和转换,需要的话可以单独使用claripy库来进行位向量的符号运算。
你可以将这些位向量存到寄存器或者内存中,或者直接存储一个python的整型数(它将会被转化为合适大小的位向量来存储):
译者注:上图中稍微测试了一下angr的内存是否和计算机中一样,可以看到angr模拟出的内存和之前
project.arch
中看到的信息一样,是小端顺序且未定义的空间会初始化为“Reverse”的位向量,整个内存空间可以看做是一个超大的位向量,.long
,.int
,.short
这些属性限制读取或写入位向量的长度。
mem
接口乍一看是挺令人困惑的,因为它使用一些python的特性。简要介绍一下如何使用它:
- 使用array[index]符号来指定一个地址。
- 使用
.
来指定一个内存应该被作为什么类型的数据处理(常用类型:char,short,long,size_t,uint8_t,uint16_t……) - 你还可以:
- 存储一个值到内存中,可以是一个位向量也可以是一个python整型
- 使用
.resolved
以位向量的形式获取内存中的值 - 使用
.concrete
以python整型的形式获取内存中的值
还有更多的高级用法,会在后面介绍。
最后,如果你尝试访问更多的寄存器,你将会发现一些奇怪的值:
这还是一个64位的位向量,但是里面并没有存储一个数值,取而代之的是一个名字!这被称为“符号变量”,并且这是符号化执行的基础。不要慌!我们将会在接下来的两章里详细讨论它。
模拟器管理者(Simulation Managers)
如果一个state使我们能够为我们实时呈现一个程序的执行状态,那么就必须有个一方法使我们达到程序执行的下一个状态。一个“模拟器管理者”(哇这个翻译成中文实在难受,下面简称SM)是angr中使用state来实现执行(模拟,或者任何你喜欢的称呼)的基本接口。作为一个简要的介绍,我们展示如何让我们之前创建的state实例“前进”几个基本快。
首先,我们创建一个将会使用到的SM。构造函数接收一个state或一个state的列表作为参数:
一个SM可能包含多个存储state的列表。默认的列表是active
,它和我们传入的state(或state的列表)一起被初始化。如果我们觉得不够的话,可以看看simgr.active[0]
来看看我们当前的状态信息。
现在……准备好,我们将要做一些执行操作了。
我们刚刚演示了执行一个可符号化执行的基本块!我们可以再看看active列表,注意到它已经被更新了,并且不仅如此,它没有修改我们的初始状态。SimState对象在执行时被视为“不可变”的——你可以安全地将一个单独的state作为多轮执行的一个“基点”。
/bin/true
对于描述如何用符号执行做一些有趣的事情来说并不是一个非常好的例子,所以目前对于符号执行我们就讲到这里。
分析器(Analyses)
angr封装了一些可能会被你用来从程序中提取一些有趣信息的内置分析工具。列表如下:
后面的文档会介绍一些分析器的使用方法,但是一般来说如果你想知道如何使用这些分析器,你应该查看(api文档)[http://angr.io/api-doc/angr.html?highlight=cfg#module-angr.analysis])。 作为一个特别简短的例子,下面是如何能够创建并使用一个快速的程序控制流程图的例子:
译者注:好奇的我把这个图用plt画出来了,结果实在差强人意2333(由此可见一个程序的程序流有多么复杂):
译者注:画图操作如下:
现在为止怎么样了?
读完本章后,你现在应该对angr的一些重要概念有所熟悉:基本块,状态,位向量,SM,和分析器。你现在还没有能力使用angr来做任何有趣的事情,除了将angr当做一个调试器之外。尽管如此!请继续阅读,到时你将会解锁更深层的力量……