Angr(十)——官方文档(Part1)

通过阅读、整理angr的官方文档,加深对angr的理解,掌握更多、更通用angr的使用方法

参考链接:

angr Documentation

angr-doc

简介

    angr是一个多架构二进制分析工具包,能够对二进制文件进行动态符号执行或多种静态分析。其开发者致力于让使用angr尽可能地简单方便,让用户只需在iPython中执行几条命令就实现对二进制文件的分析。但是,二进制分析的复杂性使得angr变得复杂。想通过编程分析二进制文件必须克服一些挑战:

  • 将二进制文件加载到分析程序中
  • 将二进制转换为中间表示
  • 进行分析
    • 对程序进行部分或全局的静态分析
    • 通过符号执行探索程序的状态空间

    angr通过不同组件来解决上述所有挑战,本文档将解释每一部分是如何工作的,以及应该如何使用。

核心原理

顶层接口

    使用angr的第一步永远是将二进制文件加载到一个project中。

import angr
project = angr.Project('/path/binary')

    project是使用angr的基础。创建了project后,就能够据此对加载的二进制文件进行模拟和调度分析。

基本属性

    可以获取一些project的基本属性信息。

import monkeyhex

# arch表示程序编译的目标平台和大小端信息
# 其中包含了大量与其运行CPU相关的信息
# 常用的有arch.bits, arch.bytes, arch.name, arch.memory_endness
print(project.arch)
# entry表示二进制文件的入口点(地址)
print(project.entry)
# filename表示二进制文件的绝对路径
print(project.filename)

加载器

    CLE模块用于将二进制文件映射到它在虚拟地址空间中的表示。CLE模块(也就是loader)的执行结果可以通过.loader属性获得。

# 程序加载的地址空间信息
project.loader
project.loader.min_addr
project.loader.max_addr
# 加载的主要二进制文件
project.loader.main_object
# 与二进制文件共同加载的共享库信息
project.loader.shared_objects
# 查询二进制文件是否拥有可执行栈
project.loader.main_object.execstack
# 查询程序是否开启地址随机化
project.loader.main_object.pic

Factory

    angr里面有很多类,且大多数需要通过project对其进行实例化。通过project.factory提供的构造函数可以方便的创建对象。

        通过project.factory.block()可以从一个指定地址提取出一个基本代码块。基本代码块是没有分支的一段代码,对应于IDA中的代码块。基本代码块是angr进行代码分析的基本单元。返回值是一个Block对象。

# 构造函数创建block类的对象
block = project.factory.block(point_addr)
# 基本代码块的汇编指令
block.pp()
# 基本代码块的汇编指令数目
lock.instructions
# 基本代码块的每条汇编指令的起始地址
block.instruction_addrs

        构造函数会截取从传入的地址参数到第一个分支指令地址(如jmp,jnz)之间的字节码,并对它们反汇编,而不会向前寻找到该块的真实起始地址。根据传入的地址参数不同,得到的汇编代码也不同,甚至会出现反汇编失败的情况。因此,如果想要得到正确的block,需要正确的block的起始地址。

  • 状态

        project对象只能表示程序的初始镜像。但是在用angr执行程序时,需要SimState对象来表示模拟的程序状态。SimState对象包含了任何能够在运行过程中被改变的实时数据,如:内存、寄存器、文件系统数据......

# 构造SimState对象
state = project.factory.entry_state()
state = project.factory.blank_state(start_addr)
# 访问寄存器值
state.regs.rip
state.regs.rax
# 访问内存值(以C语言的int类型)
state.mem[addr].int.resolved
# 设置寄存器值
state.regs.rsi = state.solver.BVV(3, 64)
# 设置内存值(以C语言的long类型)
state.mem[addr].long = 4

        得到的值是bitvector(位向量)类型,它与python中的整型不同。bitvector类型存在.length属性,表示其以位为单位的宽度。python中的整型与bitvector类型相互转化的方法如下:

# python int转bitvector
bv = state.solver.BVV(0x1234, 32)
# bitvector转python int
pi = state.solver.eval(bv)

        关于mem:

            1. 可以按照数组的方式指定内存地址mem[index]

            2. 可以通过.type来指定内存数据应该以什么数据类型处理(char、short、int、long、size_t...)

            3. 可以向内存地址存储数据(bitvector类型或python的int类型)

            4. 使用.resolved以bitvector类型获取数据,使用.concrete以python的int类型获取数据

        在访问某些寄存器时,里面没有存储一个数值,取而代之的是一个名字。它们被称为符号变量,是符号执行的基础。

  • 模拟管理器

        state允许我们描述一个程序在某时间点的状态,但仍需要一种方法允许程序从该时间点转移到下一时间点。模拟管理器是angr通过state来执行与模拟的主要接口。

        首先需要创建一个模拟管理器,构造函数的参数可以是一个状态或一个状态列表。

# 创建模拟管理器
simgr = project.factory.simulation_manager(state)
simgr.active

        一个模拟管理器中可以有多个状态存储区,默认的状态列表是active,它由构造函数传入的参数进行初始化。

simgr.step()

        通过运行上面的代码完成了一个基本代码块的符号执行。运行结束后可以发现active列表被更新了。但是运行前的初始状态并没有被改变,SimState对象是不可变的,即可以安全地将单个状态作为多轮执行的基础。

  • 分析器

        angr预先打包了一些内置的分析器,可以借助它们从程序中提取一些有趣的信息:

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文档来确定应该使用哪个分析器。

加载二进制文件

加载器

    通过加载fauxware来深入理解与加载器的交互。

import angr
import monkeyhex

project = angr.Project("./fauxware")
print(project.loader)

     运行python脚本:

(angr) root@ubuntu:/home/c1rcl3/Desktop/Angr/doc# python load.py 
<Loaded fauxware, maps [0x400000:0x1007fff]>
  • 加载的对象

        CLE加载器(cle.loader)代表所有被加载的二进制文件的集合,它们被加载并映射到同一个内存空间中。每一个二进制文件都被一个能够处理对应文件类型的加载器(cle.backend)加载。例如:用cle.ELF来加载ELF格式的二进制文件。

        内存中还会有一些与所有二进制文件都不对应的对象。例如:用来为线程本地存储(thread-local storage)提供支持的对象。

        可以通过loader.all_objects获取CLE已加载的所有对象,也可以通过指定某些类型来访问更具体的对象。

import angr
import monkeyhex

project = angr.Project("./fauxware")

print("all_objects:")
print(project.loader.all_objects)
print("main_object:")
print(project.loader.main_object)
print("shared_objects:")
print(project.loader.shared_objects)
print("all_elf_objects:")
print(project.loader.all_elf_objects)
print("extern_object:")
print(project.loader.extern_object)
print("kernel_object:")
print(project.loader.kernel_object)
print("find_object_containing:")
print(project.loader.find_object_containing(0x400000))

        运行python脚本:

(angr) root@ubuntu:/home/c1rcl3/Desktop/Angr/doc# python load.py 
all_objects:
[<ELF Object fauxware, maps [0x400000:0x60105f]>, <ELF Object libc-2.27.so, maps [0x700000:0xaf0adf]>, <ELF Object ld-2.27.so, maps [0xb00000:0xd2b16f]>, <ExternObject Object cle##externs, maps [0xe00000:0xe7ffff]>, <ELFTLSObjectV2 Object cle##tls, maps [0xf00000:0xf1500f]>, <KernelObject Object cle##kernel, maps [0x1000000:0x1007fff]>]
main_object:
<ELF Object fauxware, maps [0x400000:0x60105f]>
shared_objects:
OrderedDict([('fauxware', <ELF Object fauxware, maps [0x400000:0x60105f]>), ('libc.so.6', <ELF Object libc-2.27.so, maps [0x700000:0xaf0adf]>), ('ld-linux-x86-64.so.2', <ELF Object ld-2.27.so, maps [0xb00000:0xd2b16f]>), ('extern-address space', <ExternObject Object cle##externs, maps [0xe00000:0xe7ffff]>), ('cle##tls', <ELFTLSObjectV2 Object cle##tls, maps [0xf00000:0xf1500f]>)])
all_elf_objects:
[<ELF Object fauxware, maps [0x400000:0x60105f]>, <ELF Object libc-2.27.so, maps [0x700000:0xaf0adf]>, <ELF Object ld-2.27.so, maps [0xb00000:0xd2b16f]>]
extern_object:
<ExternObject Object cle##externs, maps [0xe00000:0xe7ffff]>
kernel_object:
<KernelObject Object cle##kernel, maps [0x1000000:0x1007fff]>
find_object_containing:
<ELF Object fauxware, maps [0x400000:0x60105f]>

         通过访问这些对象,可以提取出相关的更具体的信息。

import angr
import monkeyhex

project = angr.Project("./fauxware")
obj = project.loader.main_object

# 二进制对象入口点
print("obj.entry:")
print(obj.entry)
# 地址范围
print("obj.min_addr:")
print(obj.min_addr)
print("obj.max_addr:")
print(obj.max_addr)
# ELF的内存分段信息和文件分段信息
print("obj.segments:")
print(obj.segments)
print("obj.sections:")
print(obj.sections)
print("obj.find_segment_containing:")
print(obj.find_segment_containing(obj.entry))
print("obj.find_section_containing:")
print(obj.find_section_containing(obj.entry))
# 获取PLT表信息
addr = obj.plt['strcmp']
print("strcmp_addr:")
print(addr)
print("reverse_function:")
print(obj.reverse_plt[addr])
# 预链接基址
print("obj.linked_base:")
print(obj.linked_base)
# 实际装载到的内存基址
print("obj.mapped_base:")
print(obj.mapped_base)

         运行python脚本:

(angr) root@ubuntu:/home/c1rcl3/Desktop/Angr/doc# python load.py 
obj.entry:
4195712
obj.min_addr:
4194304
obj.max_addr:
6295647
obj.segments:
<Regions: [<ELFSegment flags=0x5, relro=0x0, vaddr=0x400000, memsize=0xa74, filesize=0xa74, offset=0x0>, <ELFSegment flags=0x4, relro=0x1, vaddr=0x600e28, memsize=0x1d8, filesize=0x1d8, offset=0xe28>, <ELFSegment flags=0x6, relro=0x0, vaddr=0x601000, memsize=0x60, filesize=0x50, offset=0x1000>]>
obj.sections:
<Regions: [<Unnamed | offset 0x0, vaddr 0x0, size 0x0>, <.interp | offset 0x238, vaddr 0x400238, size 0x1c>, <.note.ABI-tag | offset 0x254, vaddr 0x400254, size 0x20>, <.note.gnu.build-id | offset 0x274, vaddr 0x400274, size 0x24>, <.gnu.hash | offset 0x298, vaddr 0x400298, size 0x1c>, <.dynsym | offset 0x2b8, vaddr 0x4002b8, size 0xd8>, <.dynstr | offset 0x390, vaddr 0x400390, size 0x5a>, <.gnu.version | offset 0x3ea, vaddr 0x4003ea, size 0x12>, <.gnu.version_r | offset 0x400, vaddr 0x400400, size 0x20>, <.rela.dyn | offset 0x420, vaddr 0x400420, size 0x18>, <.rela.plt | offset 0x438, vaddr 0x400438, size 0xa8>, <.init | offset 0x4e0, vaddr 0x4004e0, size 0x18>, <.plt | offset 0x500, vaddr 0x400500, size 0x80>, <.text | offset 0x580, vaddr 0x400580, size 0x338>, <.fini | offset 0x8b8, vaddr 0x4008b8, size 0xe>, <.rodata | offset 0x8c8, vaddr 0x4008c8, size 0x63>, <.eh_frame_hdr | offset 0x92c, vaddr 0x40092c, size 0x44>, <.eh_frame | offset 0x970, vaddr 0x400970, size 0x104>, <.ctors | offset 0xe28, vaddr 0x600e28, size 0x10>, <.dtors | offset 0xe38, vaddr 0x600e38, size 0x10>, <.jcr | offset 0xe48, vaddr 0x600e48, size 0x8>, <.dynamic | offset 0xe50, vaddr 0x600e50, size 0x190>, <.got | offset 0xfe0, vaddr 0x600fe0, size 0x8>, <.got.plt | offset 0xfe8, vaddr 0x600fe8, size 0x50>, <.data | offset 0x1038, vaddr 0x601038, size 0x18>, <.bss | offset 0x1050, vaddr 0x601050, size 0x10>, <.comment | offset 0x1050, vaddr 0x0, size 0x2a>, <.shstrtab | offset 0x107a, vaddr 0x0, size 0xfe>, <.symtab | offset 0x18f8, vaddr 0x0, size 0x6d8>, <.strtab | offset 0x1fd0, vaddr 0x0, size 0x278>]>
obj.find_segment_containing:
<ELFSegment flags=0x5, relro=0x0, vaddr=0x400000, memsize=0xa74, filesize=0xa74, offset=0x0>
obj.find_section_containing:
<.text | offset 0x580, vaddr 0x400580, size 0x338>
strcmp_addr:
4195664
reverse_function:
strcmp
obj.linked_base:
4194304
obj.mapped_base:
4194304
  • 符号和重定位

        可以通过符号使用CLE。符号是可执行程序的基本概念,可以有效地讲名称转换为地址。从CLE中获取符号最简单的方法是使用loader.find_symbol(),该函数接收名称或地址作为参数,并返回一个符号对象(Symbol)。

import angr
import monkeyhex

project = angr.Project("./fauxware")
cmpf = project.loader.find_symbol('strcmp')
print(cmpf)

         运行python脚本:

(angr) root@ubuntu:/home/c1rcl3/Desktop/Angr/doc# python load.py 
<Symbol "strcmp" in libc-2.27.so at 0x79d820>

         一个符号对象最重要的属性是它的名称、父对象和地址。Symbol对象有三种表示地址的方式:

        1. rebased_addr属性对应的是符号在全局地址空间中的地址

        2. linked_addr属性对应的是符号相对于二进制文件预链接基址的地址

        3. relative_addr属性对应的是符号相对于对象基址的地址(也被称为RVA,相对虚拟地址)

import angr
import monkeyhex

project = angr.Project("./fauxware")
cmpf = project.loader.find_symbol('strcmp')

print("name:")
print(cmpf.name)
print("owner:")
print(cmpf.owner)
print("rebased_addr:")
print(cmpf.rebased_addr)
print("linked_addr:")
print(cmpf.linked_addr)
print("relative_addr:")
print(cmpf.relative_addr)

         运行python脚本:

(angr) root@ubuntu:/home/c1rcl3/Desktop/Angr/doc# python load.py 
name:
strcmp
owner:
<ELF Object libc-2.27.so, maps [0x700000:0xaf0adf]>
rebased_addr:
7985184
linked_addr:
645152
relative_addr:
645152

         除了提供调试信息,符号还支持动态链接的信息。libc提供了strcmp符号作为导出函数,并且main函数(fauxware)调用了这个函数。如果想让CLE直接从main_object对象中获取strcmp的符号对象,它会告诉我们这是一个导入符号。导入符号没有有意义的地址,但是提供了能够用于解析出符号对象的引用,可以通过.resolvedby来获得引用。

import angr
import monkeyhex

project = angr.Project("./fauxware")
cmpf = project.loader.find_symbol('strcmp')

print("is_export:")
print(cmpf.is_export)
print("is_import:")
print(cmpf.is_import)

main_cmpf = project.loader.main_object.get_symbol('strcmp')
print("main_strcmp:")
print(main_cmpf)
print("is_export:")
print(main_cmpf.is_export)
print("is_import:")
print(main_cmpf.is_import)
print("resolvedby:")
print(main_cmpf.resolvedby)

         运行python脚本:

(angr) root@ubuntu:/home/c1rcl3/Desktop/Angr/doc# python load.py 
is_export:
True
is_import:
False
main_strcmp:
<Symbol "strcmp" in fauxware (import)>
is_export:
False
is_import:
True
resolvedby:
<Symbol "strcmp" in libc-2.27.so at 0x79d820>

         导入符号和导出符号之间的关系,在内存中以一种特别的方式被记录——重定位。重定位指的是:当需要将一个[import]和一个导出符号匹配时,需要将导出符号的地址以[format]的形式写入[location]。可以通过obj.relocs来查看某对象的完整重定位表,或者通过obj.imports来查看符号名和重定位地址之间的映射关系。

import angr
import monkeyhex

project = angr.Project("./fauxware")
print("relocs:")
for reloc in project.loader.shared_objects['libc.so.6'].relocs:
    print(reloc)
print("imports:")
for imports in project.loader.shared_objects['libc.so.6'].imports:
    print(imports)

        运行python脚本:

(angr) root@ubuntu:/home/c1rcl3/Desktop/Angr/doc# python load.py 
relocs:
<cle.backends.elf.relocation.amd64.R_X86_64_RELATIVE object at 0x7ff9532d2a90>
<cle.backends.elf.relocation.amd64.R_X86_64_RELATIVE object at 0x7ff9532d2ac8>
<cle.backends.elf.relocation.amd64.R_X86_64_RELATIVE object at 0x7ff9532d2b00>
<cle.backends.elf.relocation.amd64.R_X86_64_RELATIVE object at 0x7ff9532d2d30>
<cle.backends.elf.relocation.amd64.R_X86_64_RELATIVE object at 0x7ff9532d2b38>
<cle.backends.elf.relocation.amd64.R_X86_64_RELATIVE object at 0x7ff9532d2b70>
<cle.backends.elf.relocation.amd64.R_X86_64_RELATIVE object at 0x7ff9532d2ba8>
<cle.backends.elf.relocation.amd64.R_X86_64_RELATIVE object at 0x7ff9532d2be0>
<cle.backends.elf.relocation.amd64.R_X86_64_RELATIVE object at 0x7ff9532d2c18>
... ...
imports:
_rtld_global
<cle.backends.elf.relocation.amd64.R_X86_64_GLOB_DAT object at 0x7ff953285630>
__libc_enable_secure
<cle.backends.elf.relocation.amd64.R_X86_64_GLOB_DAT object at 0x7ff953285860>
_rtld_global_ro
<cle.backends.elf.relocation.amd64.R_X86_64_GLOB_DAT object at 0x7ff953285588>
_dl_starting_up
<cle.backends.elf.relocation.amd64.R_X86_64_GLOB_DAT object at 0x7ff9532887f0>
_dl_argv
<cle.backends.elf.relocation.amd64.R_X86_64_GLOB_DAT object at 0x7ff95328b128>
__tls_get_addr
<cle.backends.elf.relocation.amd64.R_X86_64_JUMP_SLOT object at 0x7ff9532d2e48>
_dl_exception_create
<cle.backends.elf.relocation.amd64.R_X86_64_JUMP_SLOT object at 0x7ff9532d2d68>
__tunable_get_val
<cle.backends.elf.relocation.amd64.R_X86_64_JUMP_SLOT object at 0x7ff95328b400>
_dl_find_dso_for_object
<cle.backends.elf.relocation.amd64.R_X86_64_JUMP_SLOT object at 0x7ff95328b550>

         通过obj.symbols_by_addr可以访问所有的导入符号,通过obj.owner_obj可以获得对重定位符号的引用。

import angr
import monkeyhex

project = angr.Project("./fauxware")
obj = project.loader.main_object
print("symbols_by_addr:")
print(obj.symbols_by_addr)
cmpf = project.loader.find_symbol('strcmp')
print("owner_obj:")
print(cmpf.owner_obj)

        运行python脚本:

(angr) root@ubuntu:/home/c1rcl3/Desktop/Angr/doc# python load.py 
symbols_by_addr:
CRITICAL | 2022-07-22 08:59:03,595 | cle.backends | Deprecation warning: symbols_by_addr is deprecated - use loader.find_symbol() for lookup and .symbols for enumeration
{0: <Symbol "exit@@GLIBC_2.2.5" in fauxware (import)>, 4194872: <Symbol "" in fauxware at 0x400238>, 4194900: <Symbol "" in fauxware at 0x400254>, 4194932: <Symbol "" in fauxware at 0x400274>, 4194968: <Symbol "" in fauxware at 0x400298>, 4195000: <Symbol "" in fauxware at 0x4002b8>, 4195216: <Symbol "" in fauxware at 0x400390>, 4195306: <Symbol "" in fauxware at 0x4003ea>, 4195328: <Symbol "" in fauxware at 0x400400>, 4195360: <Symbol "" in fauxware at 0x400420>, 4195384: <Symbol "" in fauxware at 0x400438>, 4195552: <Symbol "_init" in fauxware at 0x4004e0>, 4195584: <Symbol "" in fauxware at 0x400500>, 4195712: <Symbol "_start" in fauxware at 0x400580>, 4195756: <Symbol "call_gmon_start" in fauxware at 0x4005ac>, 4195792: <Symbol "__do_global_dtors_aux" in fauxware at 0x4005d0>, 4195904: <Symbol "frame_dummy" in fauxware at 0x400640>, 4195940: <Symbol "authenticate" in fauxware at 0x400664>, 4196077: <Symbol "accepted" in fauxware at 0x4006ed>, 4196093: <Symbol "rejected" in fauxware at 0x4006fd>, 4196125: <Symbol "main" in fauxware at 0x40071d>, 4196320: <Symbol "__libc_csu_init" in fauxware at 0x4007e0>, 4196464: <Symbol "__libc_csu_fini" in fauxware at 0x400870>, 4196480: <Symbol "__do_global_ctors_aux" in fauxware at 0x400880>, 4196536: <Symbol "_fini" in fauxware at 0x4008b8>, 4196552: <Symbol "_IO_stdin_used" in fauxware at 0x4008c8>, 4196652: <Symbol "" in fauxware at 0x40092c>, 4196720: <Symbol "" in fauxware at 0x400970>, 4196976: <Symbol "__FRAME_END__" in fauxware at 0x400a70>, 6295076: <Symbol "__init_array_start" in fauxware at 0x600e24>, 6295080: <Symbol "__CTOR_LIST__" in fauxware at 0x600e28>, 6295088: <Symbol "__CTOR_END__" in fauxware at 0x600e30>, 6295096: <Symbol "__DTOR_LIST__" in fauxware at 0x600e38>, 6295104: <Symbol "__DTOR_END__" in fauxware at 0x600e40>, 6295112: <Symbol "__JCR_END__" in fauxware at 0x600e48>, 6295120: <Symbol "_DYNAMIC" in fauxware at 0x600e50>, 6295520: <Symbol "" in fauxware at 0x600fe0>, 6295528: <Symbol "_GLOBAL_OFFSET_TABLE_" in fauxware at 0x600fe8>, 6295608: <Symbol "__data_start" in fauxware at 0x601038>, 6295616: <Symbol "__dso_handle" in fauxware at 0x601040>, 6295624: <Symbol "sneaky" in fauxware at 0x601048>, 6295632: <Symbol "__bss_start" in fauxware at 0x601050>, 6295640: <Symbol "dtor_idx.6533" in fauxware at 0x601058>, 6295648: <Symbol "_end" in fauxware at 0x601060>}
owner_obj:
CRITICAL | 2022-07-22 08:59:03,597 | cle.backends.symbol | Deprecation warning: use symbol.owner instead of symbol.owner_obj
<ELF Object libc-2.27.so, maps [0x700000:0xaf0adf]>

         如果一个导入符号不能被解析为任何一个导出符号(例如找不到对应的共享库文件),CLE会自动更新loader.extern_obj来表明这个符号由CLE导出。


 加载选项

    当使用angr.Project加载二进制文件,并希望给cle.loader实例传递一个选项来创建一个project时,可以直接将关键字参数传递给Project构造函数,之后这个参数会被传递给CLE。可以通过查看CLE API docs来了解所有可以作为选项传入的内容。

  • 基本选项
auto_load_libs

可以启用或禁用CLE自动加载共享库文件,默认情况下处于启用状态

except_missing_libs如果被设置为True,那么当二进制包含无法解析的共享库时会抛出异常
force_load_libs传递一个字符串列表,其中的每个字符串会被当做一个无法解析的共享库
skip_libs传递一个字符串列表,避免将其中的字符串解析为共享库
ld_path传递一个字符串或一个字符串列表,作为额外的共享库搜索路径,且在默认路径(与加载程序相同的路径、当前工作路径和系统库路径)前进行搜索
  • Per-binary选项

        通过Per-binary可以指定一些仅适用于特定二进制文件的选项,可以通过main_opts和lib_opts关键字接受字典类型的参数来实现。main_opts指定主程序加载的参数,是一级字典。而lib_opts指定库加载的参数,由于可能有多个库,所以是二级字典。通用的选项有:

backend指定使用哪个后端,可以是类或字符串名称
base_addr指定基地址
entry_point指定入口点
arch使用的处理器体系结构的名字
  •  backend

        CLE目前有用于静态加载ELF、PE、CGC、Mach-O和ELF核心转储文件的后端,以及将文件加载到平面地址空间的后端。在大多数情况下,CLE会自动检测出需要使用的正确后端。因此大多数情况下不需要特别指定。

        但是也可以通过在选项字典中添加backend键来强制CLE为加载的对象使用指定的后端。有些后端无法自动检测要使用的处理器体系结构,所以需要由arch来指定。

后端名称描述是否需要指定arch
elf基于PyELFTools的ELF文件静态加载器不需要
pe基于PEFile的静态PE文件加载器不需要
mach-oMach-O文件的静态加载器,不支持动态链接或者变基址不需要
cgcCyber Grand Challenge系统中的二进制文件的静态加载器不需要
backedcgc允许指定内存和寄存器支持的CGC二进制文件静态加载器不需要
elfcoreELF核心转储文件的静态加载器不需要
ida启动ida来分析文件需要
blob按照平坦模式加载文件到内存中需要

符号函数摘要

    默认情况下,Project会使用SimProcedures这个符号函数摘要来替换主函数的外部调用。SimProcedures使用高效的python函数模拟外部库函数对state的影响。SimProcedures实现的所有函数可以在GitHub仓库的procedures中查看。这些内置的函数可以通过angr.SIM_PROCEDURES使用,这是一个两级字典。第一级的关键字是包名称(libc,posix,win32,stubs),第二级的关键字是库函数名称。执行SimProcedures中实现的函数而非从系统中加载的实际库函数可以使分析更容易。

    当找不到某个函数的摘要时:

  1. 如果auto_load_libs的值被设置为True(默认值),就会执行真正的库函数。但是libc的一些函数非常复杂,尝试分析执行很有可能导致路径爆炸。
  2. 如果auto_load_libs的值被设置为False,那么外部库函数就是未解析的状态。Project会将它们解析为ReturnUnconstrained。当这类函数被调用时,会返回一个唯一的无约束符号。
  3. 如果use_sim_procedures参数的值被设置为False(默认值为True),只有由外部对象提供的符号将会被SimProcedures替换,并且会被替换为ReturnUnconstrained。
  4. 可以通过exclude_sim_procedures_list参数和exclude_sim_procedures_func参数指定一些符号,使它们可以不被SimProcedures替换。

    angr将系统库函数替换为python摘要函数的机制是hook。angr在执行模拟的每一步时都会检查当前地址是否已经被hook,如果是,则运行hook函数而非原本该地址处的二进制代码。可以通过project.hook(addr, hook)这个API来实现挂钩,其中hook是一个SimProcedure实例。也可以通过.is_hooked,.unhook和.hook_by属性来管理钩子。

    可以通过project.hook(addr)指定对地址addr处的代码进行挂钩,而hook函数由自己实现。还可以使用一个可选关键词参数length,来决定当自定义的hook函数执行结束后,程序跳过多少字节字节码再继续执行。

    此外,还可以通过project.hook_symbol(name, hook)进行hook。其中name参数指定符号名称,可以通过该函数挂钩符号所在的地址。一个重要用法是扩展angr内置库SimProcedures的行为,由于这些库函数只是类,因此可以写它们的子类,重写其中的方法,然后在hook中使用子类。

求解器

    angr的强大之处在于它能够使用符号变量来执行。变量不具有一个具体的数值,而是只有一个符号,或者说一个名字。在使用这些变量进行算术运算时,将会形成一棵操作树(AST)。这些AST成为SMT求解器的约束条件(类似于python中的z3约束求解器)。这样一来,就能够回答“给定一系列操作的输出,那么输入应该是什么”的问题。

使用位向量

    位向量是一个位序列,可以解释为算术中的有界整数。

>>> import angr
>>> import monkeyhex
>>> project = angr.Project("./fauxware")
>>> state = project.factory.entry_state()
>>> one = state.solver.BVV(1, 64)
>>> one
<BV64 0x1>
>>> one_hundred = state.solver.BVV(100, 64)
>>> one_hundred
<BV64 0x64>
>>> weird_nine = state.solver.BVV(9, 27)
>>> weird_nine
<BV27 0x9>

    可以创建拥有任意长度位数的序列,并称为位向量。

>>> one + one_hundred
<BV64 0x65>
>>> one_hundred + 0x100
<BV64 0x164>
>>> one_hundred - one * 200
<BV64 0xffffffffffffff9c>

    相同长度的位向量之间可以进行算术运算,但对不同长度的位向量进行算术运算会触发类型错误。向量的位数是可扩展的。zero_extend会用给定数目的0填充在原向量的左侧,即零扩展。sign_extend会用给定数目的最高位填充在原向量的左侧,即符号扩展。

>>> weird_nine.zero_extend(64 - 27)
<BV64 0x9>
>>> one + weird_nine.zero_extend(64 - 27)
<BV64 0xa>
>>> one + weird_nine
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/root/.virtualenvs/angr/lib/python3.6/site-packages/claripy/operations.py", line 50, in _op
    raise ClaripyOperationError(msg)
claripy.errors.ClaripyOperationError: args' length must all be equal

     加入符号进行运算:

>>> x = state.solver.BVS("x", 64)
>>> x
<BV64 x_38_64>
>>> y = state.solver.BVS("y", 64)
>>> y
<BV64 y_39_64>

    x和y是符号变量,在提供的符号名后面加上了一个递增的计数值。对于符号变量也可以进行算数运算,但不会得到一个数值的结果,而是一个AST。

>>> x + one
<BV64 x_38_64 + 0x1>
>>> (x + one) / 2
<BV64 (x_38_64 + 0x1) / 0x2>
>>> x - y
<BV64 x_38_64 - y_39_64>

    从技术上来讲,任何位向量都是一棵AST树,即使该树的深度为1,只有一个节点。每一棵AST树都有.op和.args。op定义了要执行运算的属性,即操作符;args定义了参与运算的操作数。

>>> tree = (x + 1) / (y + 2)
>>> tree
<BV64 (x_38_64 + 0x1) / (y_39_64 + 0x2)>
>>> tree.op
'__floordiv__'
>>> tree.args
(<BV64 x_38_64 + 0x1>, <BV64 y_39_64 + 0x2>)
>>> tree.args[0].op
'__add__'
>>> tree.args[0].args
(<BV64 x_38_64>, <BV64 0x1>)
>>> tree.args[0].args[1].op
'BVV'
>>> tree.args[0].args[1].args
(0x1, 0x40)

符号约束

    在任何两个相似类型的AST之间执行比较操作时将会产生另一个AST,但它不是一个位向量,而是一个符号布尔值。AST之间的比较默认是无符号比较。

>>> x == 1
<Bool x_38_64 == 0x1>
>>> x == one
<Bool x_38_64 == 0x1>
>>> x > 2
<Bool x_38_64 > 0x2>
>>> x + y == one_hundred + 5
<Bool x_38_64 + y_39_64 == 0x69>
>>> one_hundred > 5
<Bool True>
>>> one_hundred > -5
<Bool False>

    在angr使用时,不应该直接在if或while语句中使用两个变量的比较结果作为判断标准,因为这个比较的结果可能不是一个确定的真值。应该使用solver.is_true或solver.is_false,它们会在不执行约束求解的情况下测试出true或false。

>>> yes = one == 1
>>> no = one == 2
>>> maybe = x == y
>>> state.solver.is_true(yes)
True
>>> state.solver.is_false(yes)
False
>>> state.solver.is_true(no)
False
>>> state.solver.is_false(no)
True
>>> state.solver.is_true(maybe)
False
>>> state.solver.is_false(maybe)
False

    即使有确定的比较结果,if one > one_hundred语句也会产生异常。

>>> if one == 1:
...     print("True")
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/root/.virtualenvs/angr/lib/python3.6/site-packages/claripy/ast/base.py", line 777, in __bool__
    raise ClaripyOperationError('testing Expressions for truthiness does not do what you want, as these expressions can be symbolic')
claripy.errors.ClaripyOperationError: testing Expressions for truthiness does not do what you want, as these expressions can be symbolic
>>> if state.solver.is_true(one == 1):
...     print("True")
... 
True

约束求解

    可以通过将符号布尔值作为约束条件添加到state中(state.solver.add),看作关于符号变量有效值的断言。然后,通过对符号表达式求解(state.solver.eval)来得到符号变量的有效值(如果有多个有效值,则返回其中的一个)。

>>> state.solver.add(x > y)
[<Bool x_38_64 > y_39_64>]
>>> state.solver.add(y > 2)
[<Bool y_39_64 > 0x2>]
>>> state.solver.add(10 > x)
[<Bool x_38_64 < 0xa>]
>>> state.solver.eval(x)
0x8
>>> state.solver.eval(y)
0x7

    如果向状态中添加了矛盾的的约束,使得变量无解,则对变量的查询将会引发异常。可以使用state.satisfiable()来检查状态的可满足性。

    eval是一种通用的方法,它可以将位向量转换为python的基本类型。


浮点数

    z3支持IEEE754的浮点数标准,所以angr也可以使用浮点数。创建一个浮点数向量和创建一个向量的主要区别在于,浮点数向量的第二个参数不是宽度,而是sort。此外,还有第三个参数,用来指定舍入模式。可以使用FPV创建浮点值,使用FPS创建浮点符号。

>>> a = state.solver.FPV(3.2, state.solver.fp.FSORT_DOUBLE)
>>> a
<FP64 FPV(3.2, DOUBLE)>
>>> b = state.solver.FPS('b', state.solver.fp.FSORT_DOUBLE)
>>> b
<FP64 FPS(FP_b_40_64, DOUBLE)>
>>> a + b
<FP64 fpAdd(RM.RM_NearestTiesEven, FPV(3.2, DOUBLE), FPS(FP_b_40_64, DOUBLE))>
>>> a + 4.4
<FP64 FPV(7.6000000000000005, DOUBLE)>
>>> b + 2 < 0
<Bool fpLT(fpAdd(RM.RM_NearestTiesEven, FPS(FP_b_40_64, DOUBLE), FPV(2.0, DOUBLE)), FPV(0.0, DOUBLE))>

    浮点数符号的约束和求解按照与整型符号相同的方式工作,但此时使用eval会得到一个浮点值。

>>> state.solver.add(b + 2 < 0)
[<Bool fpLT(fpAdd(RM.RM_NearestTiesEven, FPS(FP_b_40_64, DOUBLE), FPV(2.0, DOUBLE)), FPV(0.0, DOUBLE))>]
>>> state.solver.add(b + 2 > -1)
[<Bool fpGT(fpAdd(RM.RM_NearestTiesEven, FPS(FP_b_40_64, DOUBLE), FPV(2.0, DOUBLE)), FPV(-1.0, DOUBLE))>]
>>> state.solver.eval(b)
-2.6890552202484974

    可以通过raw_to_bv将浮点数转化为位向量,也可以通过raw_to_fp将位向量转为浮点数。

>>> a.raw_to_bv()
<BV64 0x400999999999999a>
>>> b.raw_to_bv()
<BV64 fpToIEEEBV(FPS(FP_b_40_64, DOUBLE))>
>>> state.solver.BVV(0, 64).raw_to_fp()
<FP64 FPV(0.0, DOUBLE)>
>>> state.solver.BVS('x', 64).raw_to_fp()
<FP64 fpToFP(x_41_64, DOUBLE)>

    这些转换都保留了位模式。但是如果想尽量不丢失精度,可以使用另一组方法val_to_bv和val_to_fp。由于浮点数的特性,这两个方法必须以参数方式指定目标值的大小或sort。

>>> a
<FP64 FPV(3.2, DOUBLE)>
>>> a.val_to_bv(12)
<BV12 0x3>
>>> a.val_to_bv(12).val_to_fp(state.solver.fp.FSORT_FLOAT)
<FP32 FPV(3.0, FLOAT)>

    这些方法还可以传入signed参数,指定源位向量或目的位向量是否是有符号的。

>>> test = state.solver.BVV(-1, 64)
>>> test
<BV64 0xffffffffffffffff>
>>> test.val_to_fp(state.solver.fp.FSORT_FLOAT, signed=True)
<FP32 FPV(-1.0, FLOAT)>
>>> test.val_to_fp(state.solver.fp.FSORT_FLOAT, signed=False)
<FP32 fpToFPUnsigned(RM.RM_NearestTiesEven, 0xffffffffffffffff, FLOAT)>
>>> test = state.solver.FPV(-1.0, state.solver.fp.FSORT_DOUBLE)
>>> test
<FP64 FPV(-1.0, DOUBLE)>
>>> test.val_to_bv(64, signed=True)
<BV64 0xffffffffffffffff>

更多解析方法

solver.eval(expression)给出一个可行解
solver.eval_one(expression)给出一个可行解,如果表达式有多个可行解,则抛出错误
solver.eval_upto(expression, n)给出最多n个可行解,如果可行解不足n个,则给出所有可行解
solver.eval_atleast(expression, n)给出最多n个可行解,如果可行解不足n个,则抛出错误
solver.eval_exact(expression, n)给出n个可行解,如果可行解不足或多于n个,则抛出错误
solver.min(expression)给出最小可行解
solver.max(expression)给出最大可行解

    此外,上述所有方法都可以额外接收以下参数:

  1. extra_constraints:以元组的形式传递约束。这些约束会被考虑到此次求解中,但不会被添加到state中。
  2. cast_to:传递结果的数据类型。目前该参数只能为int或bytes。
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值