之前,你只是对angr的加载工具有了一个简单的了解,它先加载了/bin/true,然后又在没有共享库的情况下再次加载了它。你还看到了proj.loader和它可以做的一些事情。现在,我们将深入探讨这些界面的细微差别以及它们可以告诉您的内容。
我们简要提到了angr的二进制加载组件CLE。 CLE代表“ CLE加载所有内容”,并负责获取二进制文件(及其依赖的任何库)并将其以易于使用的方式呈现给其他angr。

装载器

让我们装载examples/fauxware/fauxware,并更深入的了解如何与装载器进行交互

  1. >>> import angr, monkeyhex
  2. >>> proj = angr.Project('examples/fauxware/fauxware')
  3. >>> proj.loader
  4. <Loaded fauxware, maps [0x400000:0x5008000]>

装载的对象

CLE加载器(cle.Loader)代表整个已加载二进制对象的集合,这些二进制对象已加载并映射到单个内存空间。 每个二进制对象由可以处理其文件类型(cle.Backend的子类)的加载器后端加载。 例如,cle.ELF用于加载ELF二进制文件。
内存中还会有与任何已加载二进制文件不对应的对象。 例如,用于提供线程本地存储支持的对象和用于提供未解析符号的externs对象。
您可以使用loader.all_objects获取CLE已加载的对象的完整列表,以及更多目标明确的分类:

  1. # All loaded objects
  2. >>> proj.loader.all_objects
  3. [<ELF Object fauxware, maps [0x400000:0x60105f]>,
  4. <ELF Object libc-2.23.so, maps [0x1000000:0x13c999f]>,
  5. <ELF Object ld-2.23.so, maps [0x2000000:0x2227167]>,
  6. <ELFTLSObject Object cle##tls, maps [0x3000000:0x3015010]>,
  7. <ExternObject Object cle##externs, maps [0x4000000:0x4008000]>,
  8. <KernelObject Object cle##kernel, maps [0x5000000:0x5008000]>]
  9. # This is the "main" object, the one that you directly specified when loading the project
  10. >>> proj.loader.main_object
  11. <ELF Object fauxware, maps [0x400000:0x60105f]>
  12. # This is a dictionary mapping from shared object name to object
  13. >>> proj.loader.shared_objects
  14. { 'fauxware': <ELF Object fauxware, maps [0x400000:0x60105f]>,
  15. 'libc.so.6': <ELF Object libc-2.23.so, maps [0x1000000:0x13c999f]>,
  16. 'ld-linux-x86-64.so.2': <ELF Object ld-2.23.so, maps [0x2000000:0x2227167]> }
  17. # Here's all the objects that were loaded from ELF files
  18. # If this were a windows program we'd use all_pe_objects!
  19. >>> proj.loader.all_elf_objects
  20. [<ELF Object fauxware, maps [0x400000:0x60105f]>,
  21. <ELF Object libc-2.23.so, maps [0x1000000:0x13c999f]>,
  22. <ELF Object ld-2.23.so, maps [0x2000000:0x2227167]>]
  23. # Here's the "externs object", which we use to provide addresses for unresolved imports and angr internals
  24. >>> proj.loader.extern_object
  25. <ExternObject Object cle##externs, maps [0x4000000:0x4008000]>
  26. # This object is used to provide addresses for emulated syscalls
  27. >>> proj.loader.kernel_object
  28. <KernelObject Object cle##kernel, maps [0x5000000:0x5008000]>
  29. # Finally, you can to get a reference to an object given an address in it
  30. >>> proj.loader.find_object_containing(0x400000)
  31. <ELF Object fauxware, maps [0x400000:0x60105f]>

您可以直接与这些对象进行交互以从中提取元数据:

  1. >>> obj = proj.loader.main_object
  2. # The entry point of the object
  3. >>> obj.entry
  4. 0x400580
  5. >>> obj.min_addr, obj.max_addr
  6. (0x400000, 0x60105f)
  7. # Retrieve this ELF's segments and sections
  8. >>> obj.segments
  9. <Regions: [<ELFSegment memsize=0xa74, filesize=0xa74, vaddr=0x400000, flags=0x5, offset=0x0>,
  10. <ELFSegment memsize=0x238, filesize=0x228, vaddr=0x600e28, flags=0x6, offset=0xe28>]>
  11. >>> obj.sections
  12. <Regions: [<Unnamed | offset 0x0, vaddr 0x0, size 0x0>,
  13. <.interp | offset 0x238, vaddr 0x400238, size 0x1c>,
  14. <.note.ABI-tag | offset 0x254, vaddr 0x400254, size 0x20>,
  15. ...etc
  16. # You can get an individual segment or section by an address it contains:
  17. >>> obj.find_segment_containing(obj.entry)
  18. <ELFSegment memsize=0xa74, filesize=0xa74, vaddr=0x400000, flags=0x5, offset=0x0>
  19. >>> obj.find_section_containing(obj.entry)
  20. <.text | offset 0x580, vaddr 0x400580, size 0x338>
  21. # Get the address of the PLT stub for a symbol
  22. >>> addr = obj.plt['strcmp']
  23. >>> addr
  24. 0x400550
  25. >>> obj.reverse_plt[addr]
  26. 'strcmp'
  27. # Show the prelinked base of the object and the location it was actually mapped into memory by CLE
  28. >>> obj.linked_base
  29. 0x400000
  30. >>> obj.mapped_base
  31. 0x400000

符号和重定位

您还可以在使用 CLE 时处理符号。 符号是可执行格式世界中的一个基本概念,可有效地将名称映射到地址。
从CLE中得到符号最简单的方法就是使用loader.find_symbol,它接受一个名称或一个地址并返回一个 Symbol 对象。

  1. >>> strcmp = proj.loader.find_symbol('strcmp')
  2. >>> strcmp
  3. <Symbol "strcmp" in libc.so.6 at 0x1089cd0>

符号最有用的属性是其名称、所有者和地址,但符号的“地址”可能不明确。 Symbol 对象具有三种报告其地址的方式:

  • .rebased_addr 是它在全局地址空间中的地址。 这就是打印输出中显示的内容。
  • .linked_addr 是其相对于二进制预链接基址的地址。 例如,这是在readelf(1)中报告的地址。
  • .relative_addr 是它相对于对象库的地址。 这在文献(尤其是 Windows 文献)中称为 RVA(相对虚拟地址) ```python

    strcmp.name ‘strcmp’

strcmp.owner

strcmp.rebased_addr 0x1089cd0 strcmp.linked_addr 0x89cd0 strcmp.relative_addr 0x89cd0 除了提供调试信息之外,符号还支持动态链接的概念。 libc 提供 strcmp 符号作为导出,主要二进制文件依赖于它。 如果我们要求 CLE 直接从主对象中给我们一个 strcmp 符号,它会告诉我们这是一个导入符号。 导入符号没有与之关联的有意义的地址,但它们确实提供了对用于解析它们的符号的引用,如 `.resolvedby`。python 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

main_strcmp.is_export False main_strcmp.is_import True main_strcmp.resolvedby

  1. 导入和导出之间的链接应该在内存中注册的特定方式由另一个称为重定位的概念处理。 一个重定位说,“当你匹配 [import] 和一个导出符号时,请将导出的地址写到 [location],格式为 [format]。” 我们可以看到一个对象(作为 `Relocation` 实例)的完整重定位列表作为 `obj.relocs`,或者只是一个从符号名称到重定位的映射作为 `obj.imports` 没有相应的导出符号列表。<br />重定位的相应导入符号可以作为 `.symbol` 访问。 重定位将写入的地址可通过您可用于 Symbol 的任何地址标识符访问,您也可以使用 `.owner` 获取对请求重定位的对象的引用。
  2. ```python
  3. # Relocations don't have a good pretty-printing, so those addresses are python-internal, unrelated to our program
  4. proj.loader.shared_objects['libc.so.6'].imports
  5. {'__libc_enable_secure': <cle.backends.elf.relocation.amd64.R_X86_64_GLOB_DAT at 0x7ff5c5fce780>,
  6. '__tls_get_addr': <cle.backends.elf.relocation.amd64.R_X86_64_JUMP_SLOT at 0x7ff5c6018358>,
  7. '_dl_argv': <cle.backends.elf.relocation.amd64.R_X86_64_GLOB_DAT at 0x7ff5c5fd2e48>,
  8. '_dl_find_dso_for_object': <cle.backends.elf.relocation.amd64.R_X86_64_JUMP_SLOT at 0x7ff5c6018588>,
  9. '_dl_starting_up': <cle.backends.elf.relocation.amd64.R_X86_64_GLOB_DAT at 0x7ff5c5fd2550>,
  10. '_rtld_global': <cle.backends.elf.relocation.amd64.R_X86_64_GLOB_DAT at 0x7ff5c5fce4e0>,
  11. '_rtld_global_ro': <cle.backends.elf.relocation.amd64.R_X86_64_GLOB_DAT at 0x7ff5c5fcea20>}

如果导入无法解析为任何导出,例如,因为找不到共享库,CLE 将自动更新 externs 对象 (loader.extern_obj) 以声明它提供符号作为导出。

装载选项

如果您正在使用 angr.Project 加载某些内容,并且您想将一个选项传递给 Project 隐式创建的 cle.Loader 实例,您只需将关键字参数直接传递给 Project 构造函数,它将被传递给 CLE。 你应该看看CLE API docs。如果您想了解可能作为选项传入的所有内容,但我们将在此处介绍一些重要且常用的选项。

基本选项

我们已经讨论过 auto_load_libs - 它启用或禁用 CLE 尝试自动解析共享库依赖项,并且默认情况下处于启用状态。 此外,还有一个相反的方法,except_missing_libs,如果设置为 true,则会导致在二进制文件具有无法解析的共享库依赖项时引发异常。
您可以将字符串列表传递给 force_load_libs,列出的任何内容都将被视为未解析的共享库依赖项,或者您可以将字符串列表传递给 skip_libs 以防止该名称的任何库被解析为依赖项 . 此外,您可以将字符串列表(或单个字符串)传递给 ld_path,它将用作共享库的附加搜索路径,在任何默认值之前:与加载的程序相同的目录,当前工作目录, 和你的系统库。

二进制选项

如果您想指定一些仅适用于特定二进制对象的选项,CLE 也可以让您这样做。 参数 main_optslib_opts 通过获取选项字典来完成此操作。 main_opts 是从选项名称到选项值的映射,而 lib_opts 是从库名称到字典的映射,将选项名称映射到选项值
您可以使用的选项因后端而异,但一些常见的有:

  • backend - 要使用的后端,作为类或名称
  • base_addr - 要使用的基地址
  • entry_point - 要使用的入口点
  • arch - 要使用的架构的名称

例子:

  1. >>> angr.Project('examples/fauxware/fauxware', main_opts={'backend': 'blob', 'arch': 'i386'}, lib_opts={'libc.so.6': {'backend': 'elf'}})
  2. <Project examples/fauxware/fauxware>

后端

CLE 当前具有用于静态加载 ELF、PE、CGC、Mach-O 和 ELF 核心转储文件以及将文件加载到平面地址空间的后端。 在大多数情况下,CLE 会自动检测要使用的正确后端,因此除非您正在做一些非常奇怪的事情,否则您不需要指定正在使用的后端。
如上所述,您可以通过在其选项字典中包含一个键来强制 CLE 为对象使用特定的后端。 某些后端无法自动检测要使用的架构,必须指定架构。 密钥不需要匹配任何架构列表; angr 将根据几乎所有支持的架构的通用标识符来确定您指的是哪种架构。
要引用后端,请使用下表中的名称:
见原文档

符号函数摘要

默认情况下,Project 尝试通过使用称为 SimProcedures 的符号摘要来替换对库函数的外部调用 - 实际上只是模仿库函数对状态影响的 python 函数。我们已经实现了一系列像SimProcedures一样的函数。这些内置过程在 angr.SIM_PROCEDURES 字典中可用,它是两级的,首先以包名(libc、posix、win32、stubs)为键,然后以库函数的名称为键。相比于执行装载的库文件,执行SimProcedures使得分析损失了一点可能的精准性,但是分析过程更加可追溯
当给定函数没有这样的摘要时:

  • 如果 auto_load_libsTrue(这是默认值),则改为执行真正的库函数。 这可能是也可能不是您想要的,具体取决于实际功能。 例如,libc 的某些函数分析起来极其复杂,并且很可能会导致尝试执行它们的路径的状态数量激增。
  • 如果 auto_load_libsFalse,则外部函数未解析,Project 会将它们解析为称为 ReturnUnconstrained 的通用“存根”SimProcedure。 它如其名所言:每次调用它时都会返回一个唯一的无约束符号值。
  • 如果 use_sim_procedures(这是 angr.Project 的参数,而不是 cle.Loader 的参数)为 False(默认情况下为 True),则只有 extern 对象提供的符号将被 SimProcedures 替换,并且它们将被一个存根 ReturnUnconstrained 替换 ,它只返回一个符号值。
  • 您可以使用 angr.Project 的参数指定要排除的特定符号,以免被 SimProcedures 替换:exclude_sim_procedures_listexclude_sim_procedures_func
  • 查看 angr.Project._register_object 的代码以获得确切的算法。

    Hooking

    angr 用python 摘要替换库代码的机制称为挂钩,您也可以这样做! 在执行模拟时,在每一步 angr 都会检查当前地址是否已被挂钩,如果是,则在该地址运行挂钩而不是二进制代码。 让您执行此操作的 API 是 proj.hook(addr, hook),其中 hook 是一个 SimProcedure 实例。 您可以使用 .is_hooked.unhook.hooked_by 管理项目的钩子,希望不需要解释。
    有一个用于挂钩地址的替代 API,通过使用 proj.hook(addr) 作为函数装饰器,您可以指定自己的即用函数以用作挂钩。 如果这样做,您还可以选择指定一个长度关键字参数,以使执行在钩子完成后向前跳转一定数量的字节。 ```python

    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)

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 API docs中查看你可以使用CLE做的所有事情