在我们开始使用angr之前,你需要对于一些angr的概念有一个基本的总体印象,而且知道如何去构造一些基本的angr对象。我们会通过过一遍在装载了二进制文件后,所有对你来说可以用的东西。

你用angr的第一步经常是去装载一个二进制文件到项目(Project),我们会使用/bin/true来当作例子

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

一个项目是你在angr中控制的基础。通过他,你可以快速分析,模拟执行你刚刚装载的文件。几乎每一个在angr中你使用的对象依赖于项目。

基本属性

首先我们有一些项目的基本属性:他的cpu架构,文件名,还有入口点的地址

  1. >>> import monkeyhex # this will format numerical results in hexadecimal
  2. >>> proj.arch
  3. <Arch AMD64 (LE)>
  4. >>> proj.entry
  5. 0x401670
  6. >>> proj.filename
  7. '/bin/true'

arch是archinfo.Arch对象的实例,该对象针对程序要编译的任何体系结构(在本例中为little-endian amd64)。 它包含有关其运行CPU的大量文书数据,您可以在闲暇时仔细阅读。 您关心的常见变量是arch.bits,arch.bytes(在Arch主类上是@property声明),arch.name和arch.memory_endness。
entry是二进制文件的入口点
filename是二进制文件的绝对文件名。

装载器(loader)

从二进制文件转换到虚拟地址空间中的表示形式非常复杂! 我们有一个名为CLE的模块来处理。 CLE的结果称为加载程序,可在.loader属性中找到。 我们将很快详细介绍如何使用它,但是现在您只知道可以使用它来查看angr在程序旁边加载的共享库并执行有关加载的地址空间的基本查询。

  1. >>> proj.loader
  2. <Loaded true, maps [0x400000:0x5004000]>
  3. >>> proj.loader.shared_objects # may look a little different for you!
  4. {'ld-linux-x86-64.so.2': <ELF Object ld-2.24.so, maps [0x2000000:0x2227167]>,
  5. 'libc.so.6': <ELF Object libc-2.24.so, maps [0x1000000:0x13c699f]>}
  6. >>> proj.loader.min_addr
  7. 0x400000
  8. >>> proj.loader.max_addr
  9. 0x5004000
  10. >>> proj.loader.main_object # we've loaded several binaries into this project. Here's the main one!
  11. <ELF Object true, maps [0x400000:0x60721f]>
  12. >>> proj.loader.main_object.execstack # sample query: does this binary have an executable stack?
  13. False
  14. >>> proj.loader.main_object.pic # sample query: is this binary position-independent?
  15. True

工厂(factory)

在angr中有许多类,而且其中的大部分都需要一个项目去实例化。为了避免让你到处传递项目,我们提供了project.factory,他提供了一些方便的构造函数,用于你经常要使用的常见对象

这节也会介绍一些最基本的概念
blocks
首先我们有project.factory.block(),这是从给定地址的基本代码块中提取出来的。angr通过基本的代码块来分析代码,这是一个重要的事实。你将得到一个Block对象,这个对象可以告诉你很多有趣的事情。

  1. >>> block = proj.factory.block(proj.entry) # lift a block of code from the program's entry point
  2. <Block for 0x401670, 42 bytes>
  3. >>> block.pp() # pretty-print a disassembly to stdout
  4. 0x401670: xor ebp, ebp
  5. 0x401672: mov r9, rdx
  6. 0x401675: pop rsi
  7. 0x401676: mov rdx, rsp
  8. 0x401679: and rsp, 0xfffffffffffffff0
  9. 0x40167d: push rax
  10. 0x40167e: push rsp
  11. 0x40167f: lea r8, [rip + 0x2e2a]
  12. 0x401686: lea rcx, [rip + 0x2db3]
  13. 0x40168d: lea rdi, [rip - 0xd4]
  14. 0x401694: call qword ptr [rip + 0x205866]
  15. >>> block.instructions # how many instructions are there?
  16. 0xb
  17. >>> block.instruction_addrs # what are the addresses of the instructions?
  18. [0x401670, 0x401672, 0x401675, 0x401676, 0x401679, 0x40167d, 0x40167e, 0x40167f, 0x401686, 0x40168d, 0x401694]

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

  1. >>> block.capstone # capstone disassembly
  2. <CapstoneBlock for 0x401670>
  3. >>> block.vex # VEX IRSB (that's a python internal address, not a program address)
  4. <pyvex.block.IRSB at 0x7706330>

状态(state)

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

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

SimState包含程序的内存,寄存器,文件系统数据…任何可以通过执行更改的实时数据都处于该状态。 稍后我们将详细介绍如何与状态交互,但现在,让我们使用state.regsand state.mem访问该状态的寄存器和内存:

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

这些不是python中的int变量 这些是位向量。 Python整数的语义与CPU上的单词的语义不同,例如 在溢出时自动换行,因此我们使用位向量(可以将其视为由一系列位表示的整数)来表示angr中的CPU数据。 请注意,每个位向量都具有.length属性,该属性描述了位的宽度。

我们将很快学习所有有关如何使用它们的知识,但是现在,这是从python ints转换为bitvector并再次转换的方法:

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

您可以将这些位向量存储回寄存器和内存,或者可以直接存储python整数,然后将其转换为适当大小的位向量:

  1. >>> state.regs.rsi = state.solver.BVV(3, 64)
  2. >>> state.regs.rsi
  3. <BV64 0x3>
  4. >>> state.mem[0x1000].long = 4
  5. >>> state.mem[0x1000].long.resolved
  6. <BV64 0x4>

这个mem接口会令人非常困惑,这个因为它使用一些非常强大的python magic,简短的用法是使用array[index]来表示一个指定的地址,用来表示将指定地址解释为什么。从那里,你可以:
1.给他存储一个值,可以是位向量或者是python中的int
2.用.resolved获取值作为位向量
3.用.concrete以python int的形式获取值
关于这个还有更多高级的用法

最后,如果尝试读取更多寄存器,则可能会遇到一个非常奇怪的值:

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

它仍然是一个64位的位向量,但不包含数值。 相反,它有一个名字 这称为符号变量,它是符号执行的基础。 不要惊慌 从现在开始,我们将在两章中详细讨论所有这些内容。

simulation manager

如果状态允许我们在给定的时间点表示程序,则必须有一种方法可以使它到达下一个时间点。 simulation manager是angr中的主要界面,用于执行带有状态的执行,仿真(无论您想调用什么)。 作为一个简短的介绍,让我们展示如何改变我们先前创建的状态,前进一些基本块。

首先,我们先创造一个我们即将使用的simulation manager,这个构造器可以接受一个state或者一个state的列表

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

simulation manager可以包含多个存储状态。默认存储区active是用传入的状态初始化的。如果还不够的话,我们可以使用simgr.active[0]来进一步查看状态

现在,我们准备做一些执行了

  1. >>> simgr.step()

我们刚刚执行了基本块的符号执行! 我们可以再次查看活动存储,注意到它已被更新,此外,它还没有修改我们的原始状态。 SimState对象被执行视为不可变-您可以安全地将单个状态用作多轮执行的“基础”。

/bin/true并不是一个非常好的例子去描述如何利用符号执行做一些有趣的事情。

分析(analyses)

angr预先打包了一些内置分析,可以用于从程序中提取一些有趣的信息,比如

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

其中的一些文本将在后面提及,但是通常来说,如果你想知道如何使用已经给出的分析,你应该查找api文档。作为一个非常简单的示例:这是如何构造和使用快速控制流图的方式

  1. # Originally, when we loaded this binary it also loaded all its dependencies into the same virtual address space
  2. # This is undesirable for most analysis.
  3. >>> proj = angr.Project('/bin/true', auto_load_libs=False)
  4. >>> cfg = proj.analyses.CFGFast()
  5. <CFGFast Analysis Result at 0x2d85130>
  6. # cfg.graph is a networkx DiGraph full of CFGNode instances
  7. # You should go look up the networkx APIs to learn how to use this!
  8. >>> cfg.graph
  9. <networkx.classes.digraph.DiGraph at 0x2da43a0>
  10. >>> len(cfg.graph.nodes())
  11. 951
  12. # To get the CFGNode for a given address, use cfg.get_any_node
  13. >>> entry_node = cfg.get_any_node(proj.entry)
  14. >>> len(list(cfg.graph.successors(entry_node)))
  15. 2

阅读此页面后,您现在应该熟悉几个重要的angr概念:basic block,state,bitvectors,simulation manager,analses。 但是,除了将angr用作出色的调试器之外,您实际上无法做任何有趣的事情! 继续阅读,您将释放更深的力量…