angr-doc 1: Top Level Interfaces

顶层接口 Top Level Interfaces

开始使用angr的第一步是将一个二进制文件加载到一个项目中。我们使用/bin/true为例。

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

在angr中,我们通过项目控制一切。你可以使用项目在你刚刚加载的可执行文件上进行分析和模拟。你在angr中使用的几乎每一个对象都会依赖一个以某种形式存在的项目。


基本属性 Basic properties

首先,我们有关于项目的一些基本属性,包括它的CPU架构,它的文件名,以及它的入口地址。

>>> import monkeyhex # this will format numerical results in hexadecimal
>>> proj.arch
<Arch AMD64 (LE)>
>>> proj.entry
0x401670
>>> proj.filename
'/bin/true'
  • archarchinfo.Arch对象的一个实例,无论程序是被编译成什么架构的,在这个例子中是小端存储的amd64架构。它包含许多有关程序运行架构的信息,可以根据需要仔细阅读。通常需要关注的是arch.bitsarch.bytes(这个用来声明Arch类的@property),arch.namearch.memory_endness
  • entry是程序的入口。
  • filename是程序的绝对文件名称。

装载器 The loader

把一个二进制文件映射到它所表示的虚拟地址空间是很复杂的。我们使用一个叫CLE的模块去处理。CLE的结果,被称为装载器,可以通过.loader属性访问。我们很快会仔细介绍如何使用它,但是现在我们只需要知道可以使用它查看angr和程序一起加载的共享库,并且执行一些有关加载地址的简单查询。

>>> proj.loader
<Loaded true, maps [0x400000:0x5004000]>

>>> proj.loader.shared_objects # may look a little different for you!
{'ld-linux-x86-64.so.2': <ELF Object ld-2.24.so, maps [0x2000000:0x2227167]>,
 'libc.so.6': <ELF Object libc-2.24.so, maps [0x1000000:0x13c699f]>}

>>> 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!
<ELF Object true, maps [0x400000:0x60721f]>

>>> 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

工厂 The factory

angr中有很多类,它们大多数需要实例化一个项目。为了不到处传递项目,我们提供了project.factory,它有几个方便的建造器来提供常用的一些对象。

这一节也会对angr的几个基本概念进行介绍。

块 Blocks

首先,我们有project.factory.block(),它用于从给定地址提取出一个基本的代码块。这说明来一个基本的事实——angr以基本块为单位分析代码。我们会得到一个对象,它含有代码块的信息。

>>> block = proj.factory.block(proj.entry) # lift a block of code from the program's entry point
<Block for 0x401670, 42 bytes>

>>> 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.capstone                       # capstone disassembly
<CapstoneBlock for 0x401670>
>>> block.vex                            # VEX IRSB (that's a python internal address, not a program address)
<pyvex.block.IRSB at 0x7706330>

状态 States

在angr中,Project对象只表示程序的“初始化镜像”。当你使用angr进行符号执行时,你会遇到专门用来表示一个符号执行的程序的状态的对象——一个符号执行状态Simsate

>>> state = proj.factory.entry_state()
<SimState @ 0x401670>

一个符号执行状态包含程序的内存、寄存器、文件系统数据等信息。任何能在符号执行过程中改变的“存活数据”都保存在状态中。我们之后会介绍如何与状态深度交互,但是现在我们就只使用state.regsstate.mem来访问这个状态的寄存器和内存信息。

>>> state.regs.rip        # get the current instruction pointer
<BV64 0x401670>
>>> state.regs.rax
<BV64 0x1c>
>>> state.mem[proj.entry].int.resolved  # interpret the memory at the entry point as a C int
<BV32 0x8949ed31>

返回的数据不是python的int类型。它们是位向量bitvector。python整数类型和CPU上的单字语意并不一致。比如,包装溢出时。所以我们使用位向量,你可以认为在angr中它将一个整数表示为一系列的位来表示CPU的数据。我们可以注意到每个位向量都有一个.length属性来描述它的位宽。

我们会很快学到如何使用它们,但是现在,我们先看看如何在python的整数类型和位向量之间相互转换。

>>> state.regs.rip        # get the current instruction pointer
<BV64 0x401670>
>>> state.regs.rax
<BV64 0x1c>
>>> state.mem[proj.entry].int.resolved  # interpret the memory at the entry point as a C int
<BV32 0x8949ed31>

我们可以把这些位向量存储回寄存器和内存,或者你可以直接存储一个python的整数类型,它会转换成合适大小的位向量

>>> state.regs.rsi = state.solver.BVV(3, 64)
>>> state.regs.rsi
<BV64 0x3>

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

mem接口可能初看上去让人迷惑,因为它利用了python的一些技巧。简单说,我们可以这样使用它:

  • 使用数组[下标]的标记来指定一个地址。
  • 使用.来指定内存应该被转译为(通常使用的类型包括:char, short, int, long, size_t, uint8_t, uint16_t……)
  • 之后你可以:

    • 往里面保存一个值,这个值可以是一个位向量或者是一个python的整数值。
    • 使用.resolved来获取位向量形式的值
    • 使用.concrete来获取python整型形式的值

之后会介绍更多高级的使用方法。

最后,如果你试图读取更多的一些寄存器,你可能遇到一个看上去很奇怪的值:

>>> state.regs.rdi
<BV64 reg_48_11_64{UNINITIALIZED}>

这个仍然是一个64位的位向量,但是它不包含一个数值量,而是有一个名字。它叫做符号变量并且它是符号执行的基础。不用紧张。我们会在两章后讨论这些细节。

符号执行管理器 Simulation Managers

如果我们使用一个状态来表示在某个给定的时间的程序,那必然有能够在一定时间内使它进入下一个状态的方法。一个符号执行管理器是angr对状态进行执行,模拟,或者无论你把它叫做什么,的基本接口。

首先,我们创建将要使用的符号执行管理器建造器可以获取一个状态或者一个状态的列表。

>>> simgr = proj.factory.simulation_manager(state)
<SimulationManager with 1 active>
>>> simgr.active
[<SimState @ 0x401670>]

一个符号执行管理器可以包含多个状态的存储stash。默认的存储active,和我们传递进去的状态一起加载。我们可以通过simgr.active[0]来看更多有关我们的状态。

现在,准备好,我们将开始进行一些符号执行。

>>> simgr.step()

我们刚刚执行了一个符号执行中的一个基本块!我们可以再次查看active存储,注意到它刚刚被更新了,不仅如此,它没有改变我们原来的状态。符号执行状态对象在执行中是无法被改变的——你可以安全的使用单个的状态作为多轮符号执行的“基础”。

>>> simgr.active
[<SimState @ 0x1020300>]
>>> simgr.active[0].regs.rip                 # new and exciting!
<BV64 0x1020300>
>>> state.regs.rip                           # still the same!
<BV64 0x401670>

/bin/true不是一个用来描述如何使用符号执行来做有趣事情的好例子,所以我们从现在开始不再使用它了。


分析 Analyses

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()
<CFGFast Analysis Result at 0x2d85130>

# 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
<networkx.classes.digraph.DiGraph at 0x2da43a0>
>>> 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

接下来干什么呢? Now what?

读完这页之后,你应该已经了解了angr的几个重要的概念:基本块位向量符号执行管理器、以及代码分析。但是,除了把angr当作一个漂亮的调试器以外,你还不能用它做一些真正有趣的事情。如果你继续向下阅读,你就可以解锁更深层次的能力。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇