Angr:Loading a Binary

原文链接: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_opslib_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_listexclude_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的行为。由于这些库函数只是类,所以可以对它们进行子类化,覆盖它们的行为片段,然后在钩子中使用子类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值