当前位置:网站首页>Angr(十)——官方文档(Part1)
Angr(十)——官方文档(Part1)
2022-07-25 09:27:00 【c1rcl3】
通过阅读、整理angr的官方文档,加深对angr的理解,掌握更多、更通用angr的使用方法
参考链接:
简介
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.picFactory
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) [email protected]:/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) [email protected]:/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) [email protected]:/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) [email protected]:/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) [email protected]:/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) [email protected]:/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) [email protected]:/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) [email protected]:/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 "[email protected]@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-o | Mach-O文件的静态加载器,不支持动态链接或者变基址 | 不需要 |
| cgc | Cyber Grand Challenge系统中的二进制文件的静态加载器 | 不需要 |
| backedcgc | 允许指定内存和寄存器支持的CGC二进制文件静态加载器 | 不需要 |
| elfcore | ELF核心转储文件的静态加载器 | 不需要 |
| ida | 启动ida来分析文件 | 需要 |
| blob | 按照平坦模式加载文件到内存中 | 需要 |
符号函数摘要
默认情况下,Project会使用SimProcedures这个符号函数摘要来替换主函数的外部调用。SimProcedures使用高效的python函数模拟外部库函数对state的影响。SimProcedures实现的所有函数可以在GitHub仓库的procedures中查看。这些内置的函数可以通过angr.SIM_PROCEDURES使用,这是一个两级字典。第一级的关键字是包名称(libc,posix,win32,stubs),第二级的关键字是库函数名称。执行SimProcedures中实现的函数而非从系统中加载的实际库函数可以使分析更容易。
当找不到某个函数的摘要时:
- 如果auto_load_libs的值被设置为True(默认值),就会执行真正的库函数。但是libc的一些函数非常复杂,尝试分析执行很有可能导致路径爆炸。
- 如果auto_load_libs的值被设置为False,那么外部库函数就是未解析的状态。Project会将它们解析为ReturnUnconstrained。当这类函数被调用时,会返回一个唯一的无约束符号。
- 如果use_sim_procedures参数的值被设置为False(默认值为True),只有由外部对象提供的符号将会被SimProcedures替换,并且会被替换为ReturnUnconstrained。
- 可以通过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) | 给出最大可行解 |
此外,上述所有方法都可以额外接收以下参数:
- extra_constraints:以元组的形式传递约束。这些约束会被考虑到此次求解中,但不会被添加到state中。
- cast_to:传递结果的数据类型。目前该参数只能为int或bytes。
边栏推荐
猜你喜欢

静态路由的配置(以华为eNSP为例)

字典树的使用

JDBC总结

关于slf4j log4j log4j2的jar包配合使用的那些事

message from server: “Host ‘xxx.xxx.xxx.xxx‘ is not allowed to connect to this MySQL server“

CentOS install redis

UE4 框架介绍

拷贝过来老的项目变成web项目
![[necessary for growth] Why do I recommend you to write a blog? May you be what you want to be in years to come.](/img/f5/e6739083f0dce8da1d09d078321633.png)
[necessary for growth] Why do I recommend you to write a blog? May you be what you want to be in years to come.

Common methods of nodejs version upgrade or switching
随机推荐
An ASP code that can return to the previous page and automatically refresh the page
将 conda 虚拟环境 env 加入 jupyter kernel
Output stream in io stream
Salt FAQs
记录一些JS工具函数
@Import,Conditional和@ImportResourse注解
Pytorch 张量列表转换为张量 List of Tensor to Tensor 使用 torch.stack()
GUI窗口
RedisUtil
PyTorch 代码模板 (CNN)
oracle 解析同名xml 问题
Filter过滤器详解(监听器以及它们的应用)
四舍五入取近似值
CentOs安装redis
Chrome开发者工具详解
[nearly 10000 words dry goods] don't let your resume don't match your talent -- teach you to make the most suitable resume by hand
简易加法计算器
Selenium 等待元素出现与等待操作可以执行的条件
多线程——五大状态
【专栏】RPC系列(理论)-夜的第一章