之前,您只了解了angr的加载工具——您加载了/bin/true,然后在没有共享库的情况下再次加载它。你也看到了proj.loader和它能做的一些事情。现在,我们将深入研究这些界面的细微差别,以及它们可以告诉你的事情。
我们简要地提到了angr的二进制加载组件,CLE。CLE代表“CLE Loads Everything”,它负责获取一个二进制文件(以及它所依赖的任何库),并以一种易于使用的方式将其呈现给angr的其他部分。
加载器
让我们加载examples/fauxware/fauxware,然后深入看看加载器发生了什么
>>> import angr, monkeyhex
>>> proj = angr.Project('examples/fauxware/fauxware')
>>> proj.loader
<Loaded fauxware, maps [0x400000:0x5008000]>
加载的对象
CLE加载器(CLE.loader)表示已加载二进制对象的整个集合,这些对象已加载并映射到单个内存空间中。每个二进制对象都由一个加载器后端加载,该后端可以处理其文件类型(cle.Backend的子类)。例如,cle.ELF用于加载ELF二进制文件。
内存中也会有对象不对应于任何加载的二进制文件。例如,一个对象用于提供线程本地存储支持,一个externs对象用于提供未解析的符号。
您可以获得CLE已使用加载器加载的对象的完整列表。loader.all_objects,以及一些更有针对性的分类:
# 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>>> 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
符号和重定位
您也可以在使用CLE时使用符号。符号是可执行格式世界中的一个基本概念,它能有效地将名称映射到地址。
从CLE获取符号的最简单方法是使用loader.find_symbol,它接受一个名称或一个地址并返回一个Symbol对象。
>>> strcmp = proj.loader.find_symbol('strcmp')
>>> strcmp
<Symbol "strcmp" in libc.so.6 at 0x1089cd0>
符号最有用的属性是它的名称、所有者和地址,但是符号的“地址”可能是不明确的。Symbol对象有三种方式来报告它的地址:
- .rebased_addr是它在全局地址空间中的地址。这是打印输出中所显示的内容。
- .linked_addr是它相对于二进制文件预链接基的地址。这是在readelf(1)中报告的地址。
- .relative_addr是它相对于对象库的地址。这在文献(特别是Windows文献)中被称为RVA(相对虚拟地址)。
>>> 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
除了提供调试信息外,符号还支持动态链接的概念。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>
导入和导出之间的链接在内存中注册的具体方式由另一个称为重定位的概念来处理。重新定位表示,“当您将[import]与export符号匹配时,请将export的地址写到[location],格式为[format]。”我们可以看到作为对象(作为relocation实例)的重定位的完整列表obj.relocs,或者只是一个从符号名到Relocation的映射,如obj.imports。没有相应的导出符号列表。
重定位对应的导入符号可以被访问为.symbol。重定位要写入的地址可以通过任何你可以用于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将自动更新externs对象(loader.extern_obj),以声明它提供了作为导出的符号。
Loading Options
如果你正在加载一些angr.Project,并且希望将一个选项传递给cle.Loader实例,您可以直接将关键字参数传递给Project构造函数,它将被传递给CLE。您应该查看CLE API文档。如果您想知道所有可能作为一个选项传递进来的内容,那么我们将在这里介绍一些重要和常用的选项。
Basic options
我们已经讨论过auto_load_libs—它启用或禁用CLE自动解析共享库依赖关系的尝试,并且在默认情况下是开启的。此外,还有相反的情况,except_missing_libs,如果设置为true,将导致每当二进制文件具有无法解析的共享库依赖时抛出异常。
你可以传递一个字符串列表,force_load_libs和任何上市将被视为一个未解决的共享库依赖的大门口,或者你可以传递一个字符串列表skip_libs防止任何库的名称解析为依赖。此外,您可以将字符串列表(或单个字符串)传递给ld_path,它将用作共享库的附加搜索路径,在任何默认值之前:与加载的程序相同的目录、当前工作目录和系统库。
Per-Binary Options
如果您希望指定一些仅适用于特定二进制对象的选项,CLE也允许您这样做。参数main_opts和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核心转储文件的后端,以及将文件加载到平面地址空间。在大多数情况下,CLE将自动检测要使用的正确后端,因此您不需要指定使用的是哪个后端,除非您正在做一些非常奇怪的事情。
如上所述,通过在对象的选项字典中包含一个键,可以强制CLE使用对象的特定后端。有些后端不能自动检测要使用的体系结构,必须指定一个arch。这个键不需要匹配任何架构列表;angr将识别出你所指的架构,为任何支持的arch提供几乎任何通用的标识符。
要引用后端,请使用下面表中的名称:
后端名 | 描述 | 需要arch? |
---|---|---|
elf | 基于PyELFTools的ELF文件静态加载器 | no |
pe | 基于PEFile的PE文件静态加载器 | no |
mach-o | Mach-O静态加载器,不支持动态链接和rebase | no |
cgc | Cyber Grand Challenge 二进制文件静态加载器 | no |
backedcgc | CGC二进制文件的静态加载器,允许指定内存和寄存器 | no |
elfcore | ELF core dump 静态加载器 | no |
blob | 将文件作为平面映像加载到内存中 | yes |
符号函数集合
默认情况下,Project试图通过使用称为SimProcedures的符号总结来替换库函数的外部调用——实际上就是模仿库函数对状态的影响的python函数。我们已经实现了一大堆SimProcedures函数。这些内置过程在angr.SIM_PROCEDURES这个两级字典中是可用的,key首先在包名(libc、posix、win32、stubs)上,然后在库函数的名称上。执行SimProcedure而不是从系统中加载的实际库函数会使分析更加容易处理,但代价是可能出现一些 不准确的情况。
当给定函数没有这样的摘要时:
- 如果auto_load_libs为True(这是默认值),则执行真正的库函数。这可能是您想要的,也可能不是,这取决于实际的函数。例如,libc的一些函数分析起来极其复杂,并且极有可能导致试图执行它们的路径的状态数激增。
- 如果auto_load_libs为False,那么外部函数将被解析,项目将把它们解析为一个名为ReturnUnconstrained的通用“stub”SimProcedure。它的功能正如其名称所示:每次调用它时,它都会返回一个唯一的不受约束的符号值。
- 如果use_sim_procedures(这是angr.Project的参数而不是cle.Loader的)是False(默认情况下是True),那么只有extern对象提供的符号将被SimProcedures替换,它们将被stub ReturnUnconstrained替换,它只返回一个符号值。
- 您可以指定特定的符号,以排除被替换为SimProcedures与参数的angr.Project:exclude_sim_procedures_list和exclude_sim_procedures_func。
- 看看angry.Project._register_object用于精确算法的代码。
Hooking
angr用python摘要替换库代码的机制叫做钩子,你也可以这么做!当执行模拟时,每一步angr都检查当前地址是否被钩住,如果是,运行钩子而不是该地址的二进制代码。让你这么做的API是proj.hook(addr, hook),其中hook是一个SimProcedure实例。可以使用.is_hooked、.unhook和.hooked_by管理项目的钩子,希望不需要解释。
还有一种用于连接地址的替代API,通过使用project.hook(addr)作为函数装饰器,可以指定自己的即兴函数作为钩子使用。如果你这样做,你也可以选择指定一个长度关键字参数,在你的钩子完成后执行跳转一些字节。
>>> 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内置库SimProcedures的行为。因为这些库函数只是类,所以你可以子类化它们,重写它们的行为,然后在钩子中使用你的子类。
到目前为止一切都好!
到目前为止,您应该对如何在CLE加载器和angr项目级别上控制进行分析的环境有了一个合理的理解。您还应该理解,angr通过将复杂的库函数与总结函数效果的SimProcedures关联起来,从而进行了合理的尝试,以简化其分析。
为了了解使用CLE加载器及其后端可以做的所有事情,请查看CLE API文档。