angr——loading a binary
主要学习一些api,以及angr的使用。源代码阅读暂时不做考虑
加载器、映射
加载器主要起到将binary加载到固定空间的作用
proj = angr.Project('examples/fauxware/fauxware')
loader有一些api可供调用,可以通过此查看相应libc,加载器等
>>> 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
# This is a dictionary mapping from shared object name to object
>>> proj.loader.shared_objects
# 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]>]
总之可以看出,它的作用是可以和一些程序加载过程中相关性比较大的内容交互。
以上对象可以被inspect,查看的内容包括被加载内容的起始地址,结束地址,段大小,权限位等。简单来说,就是我们在gdb中也可以看到的一些和被加载程序相关的内容
>>> obj = proj.loader.main_object
>>> obj.entry
0x400580
# 输出地址开始以及末尾
>>> obj.min_addr, obj.max_addr
(0x400000, 0x60105f)
# Retrieve this ELF's segments and sections
# 输出elf的段信息和区间信息
>>> 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
# 输出程序加载基地址以及在CLE(一个自定义加载器中)被加载的基地址
>>> obj.linked_base
0x400000
>>> obj.mapped_base
0x400000
符号、重定位
angr还可以用来查找一些符号的地址,包括加载前地址以及运行过程中的地址。
文档中提到,查找一个符号的方法有两种:名称,地址。但是地址往往是含糊的,因为不确定是否是重定位之后的地址。
一下实例说明用名称来查找一个符号
>>> strcmp = proj.loader.find_symbol('strcmp')
>>> strcmp
<Symbol "strcmp" in libc.so.6 at 0x1089cd0>
对于寻找到的符号,有相应的api来快速查询他的名称,重定位基地址,链接地址,归属等信息
>>> 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
除了加载器中的符号,二进制文件本身也会包含符号。下图为寻找二进制文件中包含的strcmp符号。
>>> 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的地址写到import富豪的某个位置上
CLE加载器
CLE加载器可以自动辨认并加载许多已知文件格式,包括pe,elf等
可以为加载器设置加载起始位置,加载架构
下图为设置加载格式为blob(随机存取块的形式存储任何种类的二进制数据),设置架构为i386,设置库文件为elf格式的命令
>>> angr.Project('examples/fauxware/fauxware', main_opts={'backend': 'blob', 'arch': 'i386'}, lib_opts={'libc.so.6': {'backend': 'elf'}})
<Project examples/fauxware/fauxware>
除此以外,还可以设置加载地址
backend
- which backend to use, as either a class or a name
base_addr
- a base address to use
entry_point
- an entry point to use
arch
- the name of an architecture to use
hook 执行
在任意地址处,angr会先检查有没有hook函数,如果有就会先执行hook
以下代码说明如何完成hook
>>> stub_func = angr.SIM_PROCEDURES['stubs']['ReturnUnconstrained'] # 替代函数
>>> proj.hook(0x10000, stub_func()) # hook操作
#接下来验证是否被hook
>>> proj.is_hooked(0x10000)
True
>>> proj.hooked_by(0x10000)
<ReturnUnconstrained>
>>> proj.unhook(0x10000)
# length指定了当hook执行完毕之后,程序执行流rip需要往后移动多少字节
>>> @proj.hook(0x20000, length=5)
... def my_hook(state):
... state.regs.rax = 1
>>> proj.is_hooked(0x20000)
True
此外,可以hook具有名称的函数
以下代码将名称为name的函数修改为hook位置的函数指针
proj.hook_symbol(name, hook)
小结
文档中的加载器部分主要想告诉我们:angr可以再加载过程中实现哪些事情。可以看出angr还是可以实现很多的。从加载之前的elf文件分段,信息提取(elf信息,符号信息),到加载过程中架构选择,映射地址选择,再到动态插装中的hook函数。可以看出angr的加载器部分既包含静态分析的功能,也包含动态分析。