Angr:Top Level Interfaces

原文链接:https://docs.angr.io/core-concepts/toplevel


在开始学习angr之前,您需要对一些基本的angr概念以及如何构造一些基本的angr对象有一个基本的了解。我们将通过认识在您加载了二进制文件之后,您可以直接使用哪些功能来探究这个问题!

使用angr的第一个操作总是将二进制文件加载到项目中。我们将使用/bin/true程序来做示例。

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

基本性质

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

>>> import monkeyhex # this will format numerical results in hexadecimal
>>> proj.arch
<Arch AMD64 (LE)>
>>> proj.entry
0x401670
>>> proj.filename
'/bin/true'
  • 对任何编译该程序的架构,arch是archinfo.Arch对象的一个实例,在本例中为小端amd64。它包含大量关于它所运行的CPU的书面数据,您可以在闲暇时阅读这些数据。最常用的是arch.bits字长,arch.bytes字节长,arch.name,andarch.memory_endness大小端.
  • entry是程序的入口地址!
  • 文件名是二进制文件的绝对地址。

The loader加载器

从一个二进制文件到被映射到虚拟地址空间的过程非常复杂!我们有一个叫做CLE的模块来处理这个问题。CLE的结果,称为loader,在.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对象,它可以告诉你关于代码块的很多有趣的事情:

>>> 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对象来获得代码块的其他表示形式:

>>> 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执行时,您使用的是表示程序状态的特定对象—SimState。让我们获取一个程序状态吧!

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

SimState包含程序的内存、寄存器、文件系统数据……任何可以通过执行来更改的“活动数据”都在state中有一席之地。稍后我们将深入讨论如何与states交互,但是现在,让我们使用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!它们是位向量。python整型与CPU上的数据没有相同的语义,例如,wrapping on overflow,所以我们使用位向量,你可以认为它们是用一些位来表示的整型,用作在angr中代表CPU的数据。注意,每个位向量都有一个.length属性,以位为单位描述它的宽度。
我们将很快学习如何使用它们,但现在,先了解如何从python int转换为位向量,然后再转换回来:

>>> bv = state.solver.BVV(0x1234, 32)       # create a 32-bit-wide bitvector with value 0x1234
<BV32 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
<BV64 0x3>

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

mem接口一开始有点使人令人困惑,因为它使用了一些非常强大的python magic。它的用法简单来说,就是:

  • 使用array[index]指定地址
  • 使用.<type>来表示制定内存应该被解释成什么类型。(一般有这些类型: char, short, int, long, size_t, uint8_t, uint16_t……)
  • 基于以上,你可以:
    • 向其中存入一个值,既可以是位向量,也可以是python整型
    • 使用.resolved来获取该值,以位向量的形式。
    • 使用.concret来获取该值,以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>]

模拟管理器可以包含多个状态的盒子。默认的盒子active是用我们传入的状态初始化的。我们可以通过simgr.active[0]来查看我们的状态。
现在…准备好,我们要开始执行了。

>>> simgr.step()

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

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

总结

阅读了本文之后,您现在应该掌握了几个重要的angr概念:基本块、程序状态、位向量、模拟管理器和分析。不过,除了使用angr作为一个加强版的调试器之外,您实际上不能做任何有趣的事情!继续阅读,你会开启更深层次的力量……

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值