Angr-Learn-0x1
Angr-Learn-0x1
介绍
本文可以理解为官方文档的简单翻译+一部分个人理解,并不会在此介绍angr
该怎么使用(如果想快速开始angr
,可以尝试从angr_ctf中学习),而是打算简单说说它的设计。
以编程的理念来分析二进制文件必须克服几个问题,它们大致是:
- 将二进制文件加载到分析程序中
- 将二进制文件转换为中间表示(
IR
) - 进行确切的分析
- 对程序部分或者全部的静态分析
- 对程序状态空间的符号探索
- 上面两种的组合
但angr
可以应对上诉这些问题
About Angr
Angr
框架的总体架构包含如下几个部分:
-
加载器—
CLE
:用于解析加载二进制文件,识别文件格式,从ELF/PE头中提取架构、代码段和数据段等程序信息 -
架构数据库—
Archinfo
:根据程序架构信息,加载对应的CPU架构模型,包括寄存器、位宽、大小端等数据 -
翻译器—
PyVEX
:将程序机器码翻译成中间语言VEX,VEX是开源二进制插桩工具Valgrind所使用的中间语言,angr需要处理不同的架构,所以它选择一种中间语言来进行它的分析 -
模拟执行引擎—
SimEngine
:对VEX指令进行解释执行,支持具体值执行和符号值执行,执行时支持自定义函数Hook和断点,支持自定义路径探索策略 -
约束求解器—
Claripy
:将符号执行中生成的路径约束转化成SMT公式,使用Z3进行求解 -
OS模拟器—
SimOS
:用于模拟程序与系统环境交互,提供了许多模拟的libc函数和系统调用,用户也可以自行编写Hook函数进行模拟
解析二进制文件 -> 获取架构信息 -> 使用翻译器翻译
核心概念
运行以下语句将二进制文件加载:
>>import angr
>>proj = angr.Project('/bin/true')
基本属性
这里的基本属性是指二进制文件的基本属性,如下:
- CPU架构(arch)
- 文件名(filename)
- 入口点地址(entry)
>>import monkeyhex # this will format numerical results in hexadecimal
>>proj.arch
<Arch AMD64 (LE)>
>>proj.entry
0x401670
>>proj.filename
'/bin/true'
加载
angr
利用CLE
模块对二进制文件进行加载,CLE
的运行结果是加载后的程序,可以通过.loader获取各种属性:
>>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
factory
factory
是angr中比较重要的一个类。
块
可以用project.factory.block(addr)
从给定地址获取基本块信息,它的返回值就是基本块。
>>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>
状态
前面的project对象其实只是代码了程序“初始化内存映像”,而当我们使用angr进行程序执行的时候,代表我们在使用SimState模拟程序状态,下面这行代码就是运行SimState的起点。
>>state = proj.factory.entry_state()
<SimState @ 0x401670>
SimState
的状态包含程序的内存、寄存器、文件系统数据等任何可以通过执行更改的“实时数据,我们可以使用例如:state.regs和state.mem来访问该状态的寄存器和内存:
>>state.regs.rip # get the current instruction pointer
<BV64 0x401670> # or symbolic variable:<BV64 reg_48_11_64{UNINITIALIZED}>
>>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)
模拟管理
模拟管理可以简单理解为模拟执行管理,它是angr
中的主要结构,用于状态的执行、模拟。我们可以创建我们要使用的模拟管理器,传入的参数应该是一个状态列表。
>>simgr = proj.factory.simulation_manager(state)
<SimulationManager with 1 active>
>>simgr.active
[<SimState @ 0x401670>]
模拟管理可以包含多个状态。上面这段代码中active是默认传入的状态的状态(因为一个状态有不同的状态)。
然后我们可以通过simgr.step()
基本块的符号执行,执行后可以看到存储的状态会发生更新。
分析
我们可以利用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
以下是构建和使用快速控制流图的一个例子
# 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