Top Level Interfaces

原文

在开始之前

在IPython(或其他Python命令行解释器)中使用和探索angr是我们设计angr的一个主要用例。当你不确定哪些接口可用时,tab补全是你的好帮手!
有时,IPython中的tab补全会很慢。我们发现在不降低完成结果有效性的情况下,以下解决方案是有用的:

# Drop this file in IPython profile's startup directory to avoid running it every time.
import IPython
py = IPython.get_ipython()
py.Completer.use_jedi = False

核心概念

在开始使用angr之前,你需要对一些基本的angr概念和如何构建一些基本的angr对象有一个基本的概述。我们将通过检查加载二进制文件后哪些内容可以直接提供给您来了解这个问题!
你使用angr的第一个动作总是将二进制文件加载到项目中。在这些例子中,我们将使用/bin/true。

>>> import angr
>>> proj = angr.Project('/bin/true')

在angr中,项目是你的控制基础。有了它,您将能够对刚刚加载的可执行文件分派分析和模拟。几乎你在angr中使用的每一个对象都依赖于以某种形式存在的项目。

基本属性

首先,我们有一些关于项目的基本属性:它的CPU架构、文件名和入口点的地址。

>>> import monkeyhex # this will format numerical results in hexadecimal
>>> proj.arch

>>> proj.entry
0x401670
>>> proj.filename
'/bin/true'
  • arch是archinfo.Arch的一个实例。Arch对象,无论程序被编译的是哪种体系结构,在这种情况下是小端amd64。它包含了大量关于其运行的CPU的文本数据,您可以在闲暇时细读这些数据。通常你关心的是arch.bits、arch.bytes(它是Arch类上的@property声明)、arch.name和arch .memory_endness。
  • entry是二进制文件的入口点!
  • filename是二进制文件的绝对文件名。引人入胜的东西!

加载程序

从一个二进制文件到它在虚拟地址空间中的表示是相当复杂的!我们有一个叫做CLE的模块来处理这个问题。CLE的结果称为加载程序,可以在.loader属性中找到。我们很快就会详细介绍如何使用它,但现在只需要知道,你可以使用它来查看angr加载在你的程序旁边的共享库,并对加载的地址空间执行基本查询。

>>> proj.loader


>>> proj.loader.shared_objects # may look a little different for you!
{'ld-linux-x86-64.so.2': ,
 'libc.so.6': }

>>> proj.loader.min_addr
0x400000
>>> proj.loader.max_addr
0x5004000

>>> proj.loader.main_object  # we've loaded several binaries into this project. Here's the main one!


>>> proj.loader.main_object.execstack  # sample query: does this binary have an executable stack?
False
>>> proj.loader.main_object.pic  # sample query: is this binary position-independent?
True

工厂

在angr中有很多类,它们中的大多数都需要实例化一个项目。我们提供project.factory,它为您想要经常使用的公共对象提供了几个方便的构造函数,而不是让您到处传递项目,。
本节还将介绍几个基本的angr概念。

Blocks

首先,我们有project.factory.block(),它用于从给定地址提取一个基本的代码块。这是一个重要的事实——angr以基本块为单位分析代码。你将返回一个Block对象,它可以告诉你关于代码块的许多有趣的事情:

>>> block = proj.factory.block(proj.entry) # lift a block of code from the program's entry point


>>> block.pp()                          # pretty-print a disassembly to stdout
0x401670:       xor     ebp, ebp
0x401672:       mov     r9, rdx
0x401675:       pop     rsi
0x401676:       mov     rdx, rsp
0x401679:       and     rsp, 0xfffffffffffffff0
0x40167d:       push    rax
0x40167e:       push    rsp
0x40167f:       lea     r8, [rip + 0x2e2a]
0x401686:       lea     rcx, [rip + 0x2db3]
0x40168d:       lea     rdi, [rip - 0xd4]
0x401694:       call    qword ptr [rip + 0x205866]

>>> block.instructions                  # how many instructions are there?
0xb
>>> block.instruction_addrs             # what are the addresses of the instructions?
[0x401670, 0x401672, 0x401675, 0x401676, 0x401679, 0x40167d, 0x40167e, 0x40167f, 0x401686, 0x40168d, 0x401694]

此外,你可以使用Block对象来获取代码块的其他表示:

>>> block.capstone                       # capstone disassembly

>>> block.vex                            # VEX IRSB (that's a python internal address, not a program address)

States

这是关于angr的另一个事实——Project对象只代表程序的“初始化映像”。当你使用angr执行时,你正在使用一个特定的对象来表示一个模拟的程序状态——一个SimState。让我们现在就抓住一个!

>>> state = proj.factory.entry_state()

一个SimState包含一个程序的内存,寄存器,文件系统数据…任何可以通过执行更改的“实时数据”都有一个home状态。稍后我们将深入讨论如何与状态交互,但现在,我们使用state.regs和state.mem访问寄存器和内存的这种状态:

>>> state.regs.rip        # get the current instruction pointer

>>> state.regs.rax

>>> state.mem[proj.entry].int.resolved  # interpret the memory at the entry point as a C int

那些不是python的int型!那些是bitvectors。Python整数与CPU上的单词没有相同的语义,例如在溢出时进行包装,所以我们使用位向量,你可以将其视为由一系列位表示的整数,以在angr中表示CPU数据。注意,每个位向量都有一个.length属性,用来描述它以位为单位的宽度。
我们将很快学习如何使用它们,但现在,这里是如何从python的int类型转换为位向量,然后再转换回来:

>>> bv = state.solver.BVV(0x1234, 32)       # create a 32-bit-wide bitvector with value 0x1234
                               # BVV stands for bitvector value
>>> state.solver.eval(bv)                # convert to python int
0x1234

你可以将这些位向量存储回寄存器和内存中,或者你可以直接存储一个python整数,它将被转换为一个适当大小的位向量:

>>> state.regs.rsi = state.solver.BVV(3, 64)
>>> state.regs.rsi


>>> state.mem[0x1000].long = 4
>>> state.mem[0x1000].long.resolved

一开始,mem界面有点令人困惑,因为它使用了一些相当强大的python魔法。如何使用它的简短版本是:

  • 使用数组[index]表示法指定地址
  • 使用.指定内存应该被解释为(常用值:char, short, int, long, size_t, uint8_t, uint16_t…)
  • 从那里,你可以:
    • 为它存储一个值,可以是位向量,也可以是python int类型
    • 使用.resolved以获取位向量的值
    • 使用.concrete获取python int类型的值
      后面将介绍更高级的用法!
      最后,如果你尝试读取更多的寄存器,你可能会遇到一个非常奇怪的值:
>>> state.regs.rdi

这仍然是一个64位向量,但它不包含数值。相反,它有一个名字!这被称为符号变量,它是符号执行的基础。别慌!我们将在两章之后详细讨论这些问题。

Simulation Managers

如果一个状态允许我们在一个给定的时间点表示一个程序,那么一定有一种方法可以让它到达下一个时间点。模拟管理器是angr中的主要接口,用于执行,模拟,你想叫它什么都行,带有状态。作为一个简短的介绍,让我们展示如何在前面的几个基本块中勾选我们之前创建的状态。
首先,我们创建将要使用的模拟管理器。构造函数可以接受状态或状态列表。

>>> simgr = proj.factory.simulation_manager(state)

>>> simgr.active
[]

一个模拟管理器可以包含一个状态(state)的许多贮藏(stash)。默认的贮藏,active,是我们输入的内容来初始化的。我们可以通过simgr.active[0]看看我们更多的state信息!
现在…准备好,我们要开始表演了。

>>> simgr.step()

我们刚刚进行了一个基本块的符号执行!我们可以再次查看active的stash,注意到它已被更新,而且,它没有修改我们的原始状态。SimState对象在执行时被视为不可变的—您可以安全地使用单个状态作为多轮执行的“基础”。

>>> simgr.active
[]
>>> simgr.active[0].regs.rip                 # new and exciting!

>>> state.regs.rip                           # still the same!

/bin/true并不是一个很好的例子来描述如何用符号执行来做一些有趣的事情,所以我们现在就到此为止。

分析

angr预先打包了几个内置的分析器,你可以用它们来从程序中提取一些有趣的信息。在这里,他们是:

>>> proj.analyses.            # Press TAB here in ipython to get an autocomplete-listing of everything:
 proj.analyses.BackwardSlice        proj.analyses.CongruencyCheck      proj.analyses.reload_analyses       
 proj.analyses.BinaryOptimizer      proj.analyses.DDG                  proj.analyses.StaticHooker          
 proj.analyses.BinDiff              proj.analyses.DFG                  proj.analyses.VariableRecovery      
 proj.analyses.BoyScout             proj.analyses.Disassembly          proj.analyses.VariableRecoveryFast  
 proj.analyses.CDG                  proj.analyses.GirlScout            proj.analyses.Veritesting           
 proj.analyses.CFG                  proj.analyses.Identifier           proj.analyses.VFG                   
 proj.analyses.CFGEmulated          proj.analyses.LoopFinder           proj.analyses.VSA_DDG               
 proj.analyses.CFGFast              proj.analyses.Reassembler

本书后面会介绍其中的一些内容,但一般来说,如果你想了解如何使用给定的分析,你应该查看api文档。下面是一个非常简单的例子:以下是如何构建和使用一个快速控制流图:

# Originally, when we loaded this binary it also loaded all its dependencies into the same virtual address  space
# This is undesirable for most analysis.
>>> proj = angr.Project('/bin/true', auto_load_libs=False)
>>> cfg = proj.analyses.CFGFast()


# cfg.graph is a networkx DiGraph full of CFGNode instances
# You should go look up the networkx APIs to learn how to use this!
>>> cfg.graph

>>> len(cfg.graph.nodes())
951

# To get the CFGNode for a given address, use cfg.get_any_node
>>> entry_node = cfg.get_any_node(proj.entry)
>>> len(list(cfg.graph.successors(entry_node)))
2

现在怎么办呢?

读完这一页,您现在应该已经熟悉了几个重要的angr概念:基本块、状态、位向量、仿真管理器和分析。不过,除了将angr用作美化的调试器之外,你真的不能做任何有趣的事情!继续读下去,你会释放出更深层次的力量……

你可能感兴趣的:(Top Level Interfaces)