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函数进行模拟

    angr-1.drawio

解析二进制文件 -> 获取架构信息 -> 使用翻译器翻译

核心概念

运行以下语句将二进制文件加载:

>>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
posted @ 2024-03-07 05:25  7resp4ss  阅读(33)  评论(0编辑  收藏  举报