原文链接:https://docs.angr.io/core-concepts/loading
在此之前,您只看到了angr加载工具最简单的功能—您加载/bin/true
,然后在没有共享库的情况下再次加载它。你也看到了proj.loader
和一些它能做的事情。现在,我们将深入研究这些接口的细微差别以及它们可以告诉您的东西。
我们简要介绍了angr的二元加载元件CLE。CLE代表"CLE Loads Everything",负责获取二进制文件(以及它所依赖的库),并以一种易于使用的方式将其呈现给angr的其余部分。
加载器
让我们加载示例/fauxware/fauxware
,并深入了解如何与加载器交互。
>>> import angr, monkeyhex
>>> proj = angr.Project('examples/fauxware/fauxware')
>>> proj.loader
<Loaded fauxware, maps [0x400000:0x5008000]>
Loaded Objects加载对象
CLE加载器(cle.Loader
)表示加载并映射到一个独立内存空间的二进制对象的完整集合。每个二进制对象都由一个加载器后端加载,该加载器后端可以处理它的文件类型(cle.Backend
)。例如,cle.ELF
用于加载ELF二进制文件。
内存中除了加载的二进制程序,还有一些其他的对象。例如,用于提供线程本地存储支持的对象和用于提供未解析符号的外部对象。
您可以用loader.all_objects
获得CLE已加载的对象的完整列表和一些特定目标的分类:
# All loaded objects
>>> proj.loader.all_objects
[<ELF Object fauxware, maps [0x400000:0x60105f]>,
<ELF Object libc-2.23.so, maps [0x1000000:0x13c999f]>,
<ELF Object ld-2.23.so, maps [0x2000000:0x2227167]>,
<ELFTLSObject Object cle##tls, maps [0x3000000:0x3015010]>,
<ExternObject Object cle##externs, maps [0x4000000:0x4008000]>,
<KernelObject Object cle##kernel, maps [0x5000000:0x5008000]>]
# This is the "main" object, the one that you directly specified when loading the project
>>> proj.loader.main_object
<ELF Object fauxware, maps [0x400000:0x60105f]>
# This is a dictionary mapping from shared object name to object
>>> proj.loader.shared_objects
{ 'fauxware': <ELF Object fauxware, maps [0x400000:0x60105f]>,
'libc.so.6': <ELF Object libc-2.23.so, maps [0x1000000:0x13c999f]>,
'ld-linux-x86-64.so.2': <ELF Object ld-2.23.so, maps [0x2000000:0x2227167]> }
# Here's all the objects that were loaded from ELF files
# If this were a windows program we'd use all_pe_objects!
>>> proj.loader.all_elf_objects
[<ELF Object fauxware, maps [0x400000:0x60105f]>,
<ELF Object libc-2.23.so, maps [0x1000000:0x13c999f]>,
<ELF Object ld-2.23.so, maps [0x2000000:0x2227167]>]
# Here's the "externs object", which we use to provide addresses for unresolved imports and angr internals
>>> proj.loader.extern_object
<ExternObject Object cle##externs, maps [0x4000000:0x4008000]>
# This object is used to provide addresses for emulated syscalls
>>> proj.loader.kernel_object
<KernelObject Object cle##kernel, maps [0x5000000:0x5008000]>
# Finally, you can to get a reference to an object given an address in it
>>> proj.loader.find_object_containing(0x400000)
<ELF Object fauxware, maps [0x400000:0x60105f]>
您可以直接与这些对象交互,从中提取元数据:
>>> obj = proj.loader.main_object
# The entry point of the object
>>> obj.entry
0x400580
>>> obj.min_addr, obj.max_addr
(0x400000, 0x60105f)
# Retrieve this ELF's segments and sections
>>> obj.segments
<Regions: [<ELFSegment memsize=0xa74, filesize=0xa74, vaddr=0x400000, flags=0x5, offset=0x0>,
<ELFSegment memsize=0x238, filesize=0x228, vaddr=0x600e28, flags=0x6, offset=0xe28>]>
>>> 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>,
...etc
# You can get an individual segment or section by an address it contains:
>>> obj.find_segment_containing(obj.entry)
<ELFSegment memsize=0xa74, filesize=0xa74, vaddr=0x400000, flags=0x5, offset=0x0>
>>> obj.find_section_containing(obj.entry)
<.text | offset 0x580, vaddr 0x400580, size 0x338>
# Get the address of the PLT stub for a symbol
>>> addr = obj.plt['strcmp']
>>> addr
0x400550
>>> obj.reverse_plt[addr]
'strcmp'
# Show the prelinked base of the object and the location it was actually mapped into memory by CLE
>>> obj.linked_base
0x400000
>>> obj.mapped_base
0x400000
值得注意的是,proj.loader.main_object
提供了plt,那么got能不能获得呢?
Symbols and Relocations符号和重定位
您还可以使用CLE处理符号。符号是可执行文件世界中的一个基本概念,有效地将函数名称映射成地址。
从CLE获取符号的最简单方法是使用loader.find_symbol
,它接受名称或地址,并返回一个符号对象。
>>> strcmp = proj.loader.find_symbol('strcmp')
>>> strcmp
<Symbol "strcmp" in libc.so.6 at 0x1089cd0>
符号上最有用的属性是它的名称、所有者和地址,但是符号的“地址”可能是模糊的。符号对象有三种形式来汇报它的地址:
.rebased_addr
全局地址空间中的地址。这是打印输出中显示的内容。.linked_addr
是相对于二进制文件的预链接基地址的地址。.relative_addr
是相对于对象基地址的地址。被称为RVA(relative virtual address)
>>> strcmp.name
'strcmp'
>>> strcmp.owner
<ELF Object libc-2.23.so, maps [0x1000000:0x13c999f]>
>>> strcmp.rebased_addr
0x1089cd0
>>> strcmp.linked_addr
0x89cd0
>>> strcmp.relative_addr
0x89cd0
除了提供调试信息外,symbols还支持动态链接的概念。libc提供strcmp函数导出符号,主二进制文件依赖于它。如果我们要求CLE直接从主对象中给我们一个strcmp符号,它会告诉我们这是一个导入符号。导入符号没有与它们关联的有意义的地址,但是它们提供了对用于解析它们的符号的引用,如.resolvedby
。
>>> strcmp.is_export
True
>>> strcmp.is_import
False
# On Loader, the method is find_symbol because it performs a search operation to find the symbol.
# On an individual object, the method is get_symbol because there can only be one symbol with a given name.
>>> main_strcmp = proj.loader.main_object.get_symbol('strcmp')
>>> main_strcmp
<Symbol "strcmp" in fauxware (import)>
>>> main_strcmp.is_export
False
>>> main_strcmp.is_import
True
>>> main_strcmp.resolvedby
<Symbol "strcmp" in libc.so.6 at 0x1089cd0>
导入和导出之间的链接在内存中由另一个称为relocations
的概念处理。重定位表示,“当您将*[import]与导出符号匹配时,请将导出地址写入[location],格式为[format]*。”我们可以在obj.relocs
中看到重定位的完整列表,也可以通过obj.imports
得到一个从符号名称到重定位的映射。没有对应的导出符号列表。
可以通过.symbol
访问重定位对应的导入符号。重定位写入的地址可以通过任何可以用作符号的地址标识符访问,你同样可以使用.owner获得对请求重定位的对象的引用。
# Relocations don't have a good pretty-printing, so those addresses are python-internal, unrelated to our program
>>> proj.loader.shared_objects['libc.so.6'].imports
{'__libc_enable_secure': <cle.backends.elf.relocation.amd64.R_X86_64_GLOB_DAT at 0x7ff5c5fce780>,
'__tls_get_addr': <cle.backends.elf.relocation.amd64.R_X86_64_JUMP_SLOT at 0x7ff5c6018358>,
'_dl_argv': <cle.backends.elf.relocation.amd64.R_X86_64_GLOB_DAT at 0x7ff5c5fd2e48>,
'_dl_find_dso_for_object': <cle.backends.elf.relocation.amd64.R_X86_64_JUMP_SLOT at 0x7ff5c6018588>,
'_dl_starting_up': <cle.backends.elf.relocation.amd64.R_X86_64_GLOB_DAT at 0x7ff5c5fd2550>,
'_rtld_global': <cle.backends.elf.relocation.amd64.R_X86_64_GLOB_DAT at 0x7ff5c5fce4e0>,
'_rtld_global_ro': <cle.backends.elf.relocation.amd64.R_X86_64_GLOB_DAT at 0x7ff5c5fcea20>}
如果导入不能解析到一个对应的导出(例如,一个共享库丢失),CLE会自动更新外部对象(loader.extern_obj
)来声明它将提供一个对应导出的符号。
Loading Options加载选项
如果你在用angr.Project
加载程序的时候,希望向工程隐式创建的cle.Loader
实例传递一个选项,您可以直接将关键字参数传递给项目构造函数,它将被传递给CLE。如果您想知道所有可能作为选项传递的内容,你应该看看CLE API文档,我们将在这里讨论一些重要的和常用的选项。
Basic Options基本选项
我们已经讨论过auto_load_libs
——它启用或禁用CLE自动加载依赖的共享库的功能,并且默认是打开的。此外,还有一个对应的except_missing_libs
,如果将其设置为true,当二进制文件具有无法解析的共享库依赖关系时,就会抛出异常。
你可以向force_load_libs
传递一个字符串列表,其中列出的内容都将被视为还未被解析的共享库依赖关系,你也可以传递一个字符串列表skip_libs
来阻止一些名称的库被解析为依赖。此外,您可以将字符串列表(或单个字符串)传递给ld_path
,它将用作共享库的附加搜索路径,优先于默认路径。(默认路径:与加载的程序相同的目录、当前工作目录和系统库。)
Per-Binary选项
如果想指定一些只适用于特定二进制对象的选项,也能通过CLE实现。参数main_ops
和lib_opts
通过接受选项字典来实现这一点。main_opts
是一个从选项名到选项值的映射,而lib_opts
是一个从库名到字典的映射,在字典中,选项名映射到选项值。
你可以使用的选项因后端而异,但一些常见的选项是:
backend
- 使用哪个后端,作为类或名称base_addr
- 基地址entry_point
- 入口地址arch
- 架构名称
例子:
>>> angr.Project('examples/fauxware/fauxware', main_opts={'backend': 'blob', 'arch': 'i386'}, lib_opts={'libc.so.6': {'backend': 'elf'}})
<Project examples/fauxware/fauxware>
Backends后端
CLE目前有用于静态加载ELF、PE、CGC、Mach-O和ELF核心转储文件的后端,以及用IDA加载二进制文件和将文件加载到连续地址空间的后端。CLE将在大多数情况下自动检测到要使用的正确后端,所以您不应该指定使用哪个后端,除非您正在做一些非常奇怪的事情。
您可以通过在对象的选项字典中包含一个键来强制CLE为对象使用特定的后端,如上所述。有些后端不能自动检测要使用哪个体系结构,必须指定arch
。密钥不需要匹配任何架构列表;angr将识别您所表示的体系结构,为任何受支持的arch提供几乎任何通用标识符。
Symbolic Function Summaries符号函数总结
默认情况下,Project试图通过使用称为SimProcedures的符号摘要来替换对库函数的外部调用——实际上就是模仿库函数对状态的影响的python函数。我们实现了一系列simprocedure函数。这些内置的程序可以在angr.SIM_PROCEDURES
字典里获取,其中有两级索引,第一级是包名(libc,posix,win32,stubs),第二级是函数名。执行SimProcedure而不是从系统中加载的实际库函数,可以使分析更加容易处理,但可能会导致一些错误。
当某一特定功能没有这样的摘要时:
- 如果
auto_load_libs
是True(默认状态),使用真实的库函数。这可能是您想要的,也可能不是,这取决于实际的函数。例如,libc的一些函数分析起来非常复杂,并且很可能会导致尝试执行它们的路径的状态数暴增。 - 如果
auto_load_libs
是False,外部函数将被解析,Project将它们解析为一个名为ReturnUnrestricted
的通用“存根”SimProcedure。它按照其名称所做的:每次调用它时,它都返回一个惟一的无约束符号值。 - 如果
use_sim_procedures
(这是angr.Project
的参数,而不是cle.Loader
)是False(默认是True),就只有外部对象提供的符号才会被simprocedure替换,它们将被一个存根returnunrestricted
替换,该存根只返回一个符号值。 - 您可以指定要排除的特定符号,使其不会被带有angr.Project参数的SimProcedures替换:
exclude_sim_procedures_list
和exclude_sim_procedures_func.
- 查看
angr.Project._register_object
的代码来获得精确算法。
Hooking钩子
angr用python摘要替换库代码的机制称为hooking,您也可以这样做!在执行模拟时,在每一步angr都会检查当前地址是否已被挂起,如果是,则运行钩子而不是该地址的二进制代码。给你提供这个服务的API是proj.hook(addr, hook)
,其中,hook
是一个SimProcedure实例。您可以用.is_hooked
,.unhook
和.hooked_by
来管理项目的挂钩,希望这些不用多余的解释。
还有一个用于hook地址的替代API,通过使用proj.hook(addr)
作为函数装饰器,您可以指定自己的现成函数作为钩子使用。如果这样做,还可以选择指定length
关键字参数,使执行在钩子完成后向前跳转一定数量的字节。
>>> stub_func = angr.SIM_PROCEDURES['stubs']['ReturnUnconstrained'] # this is a CLASS
>>> proj.hook(0x10000, stub_func()) # hook with an instance of the class
>>> proj.is_hooked(0x10000) # these functions should be pretty self-explanitory
True
>>> proj.hooked_by(0x10000)
<ReturnUnconstrained>
>>> proj.unhook(0x10000)
>>> @proj.hook(0x20000, length=5)
... def my_hook(state):
... state.regs.rax = 1
>>> proj.is_hooked(0x20000)
True
此外,您还可以使用proj.hook_symbol(name, hook)
,提供了一个符号的名称作为第一个参数,用来钩住符号所在的地址。它的一个非常重要的用途是扩展angr的内置库simprocedure的行为。由于这些库函数只是类,所以可以对它们进行子类化,覆盖它们的行为片段,然后在钩子中使用子类。