Gravitational plate of three masses and a slashed discABC0Static engraved plate. Three-dimensional view is unavailable or reduced motion is requested.

← back to fieldarchive

archiveAug 14, 2020

angr binary analysis notes 2: the loader

CLE loader internals in angr: loaded objects, symbols and relocations, load options, backends, SimProcedure summaries, and hooking.

The loader

Load example/fauxware/fauxware and poke at the loader.

Sample binary: dnsdudrla97/angr-doc

>>> import angr, monkeyhex
>>> p = angr.Project('./fauxware')
>>> p.loader
<Loaded fauxware, maps [0x400000:0xc08000]>

Loaded objects

cle.Loader represents the whole group of loaded binaries, mapped into one address space. Each object is loaded by a backend that understands its format (a subclass of cle.Backend). cle.ELF, for example, loads ELF files.

Some objects do not correspond one-to-one with files on disk — TLS support objects, or the extern object that backs unresolved symbols.

Besides loader.all_objects, CLE exposes a few filtered views:

>>> p.loader.all_objects
[<ELF Object fauxware, maps [0x400000:0x60105f]>,
 <ELF Object libc-2.31.so, maps [0x700000:0x8f14d7]>,
 <ELF Object ld-2.31.so, maps [0x900000:0x92f18f]>,
 <ExternObject Object cle##externs, maps [0xa00000:0xa80000]>,
 <ELFTLSObjectV2 Object cle##tls, maps [0xb00000:0xb15010]>,
 <KernelObject Object cle##kernel, maps [0xc00000:0xc08000]>]
 
# 프로젝트 로드시 직접 지정한 main_object
>>> p.loader.main_object
<ELF Object fauxware, maps [0x400000:0x60105f]>
 
# shared 객체와 이름을 매핑해서 보여줌
>>> p.loader.shared_objects
{'fauxware': <ELF Object fauxware, maps [0x400000:0x60105f]>,
 'libc.so.6': <ELF Object libc-2.31.so, maps [0x700000:0x8f14d7]>,
 'ld-linux-x86-64.so.2': <ELF Object ld-2.31.so, maps [0x900000:0x92f18f]>,
 'extern-address space': <ExternObject Object cle##externs, maps [0xa00000:0xa80000]>,
 'cle##tls': <ELFTLSObjectV2 Object cle##tls, maps [0xb00000:0xb15010]>}
 
# ELF 파일에서 로드된 모든 객체
# Window OS 일 경우 all_pe_object를 사용하자
>>> p.loader.all_elf_objects
[<ELF Object fauxware, maps [0x400000:0x60105f]>,
 <ELF Object libc-2.31.so, maps [0x700000:0x8f14d7]>,
 <ELF Object ld-2.31.so, maps [0x900000:0x92f18f]>]
 
# externs_object
>>> p.loader.extern_object
<ExternObject Object cle##externs, maps [0xa00000:0xa80000]>
 
# kernel_object : syscall에 주소를 제공하는데 사용
>>> p.loader.kernel_object
<KernelObject Object cle##kernel, maps [0xc00000:0xc08000]>
 
#주소가 주어진 객체에 대한 참조를 얻을 수 있다
>>> p.loader.find_object_containing(0x400000)
<ELF Object fauxware, maps [0x400000:0x60105f]>

On Windows, prefer all_pe_objects. The kernel object supplies addresses for syscalls.

You can also pull metadata straight off an object:

>>> obj = p.loader.main_object
# 객체의 entry point
>>> obj.entry
0x400580
>>> obj.min_addr, obj.max_addr
(0x400000, 0x60105f)
 
# ELF의 세그먼트와 섹션을 검색한다.
>>> obj.segments
<Regions: [
	<ELFSegment flags=0x5, relro=0x0, vaddr=0x400000, memsize=0xa74, filesize=0xa74, offset=0x0>,
	<ELFSegment flags=0x4, relro=0x1, vaddr=0x600e28, memsize=0x1d8, filesize=0x1d8, offset=0xe28>,
	<ELFSegment flags=0x6,  relro=0x0, vaddr=0x601000, memsize=0x60, filesize=0x50, offset=0x1000>]>
 
>>> 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>,
	<.note.gnu.build-id | offset 0x274, vaddr 0x400274, size 0x24>,
	<.gnu.hash | offset 0x298, vaddr 0x400298, size 0x1c>,
	<.dynsym | offset 0x2b8, vaddr 0x4002b8, size 0xd8>,
	<.dynstr | offset 0x390, vaddr 0x400390, size 0x5a>,
	<.gnu.version | offset 0x3ea, vaddr 0x4003ea, size 0x12>,
	<.gnu.version_r | offset 0x400, vaddr 0x400400, size 0x20>,
	<.rela.dyn | offset 0x420, vaddr 0x400420, size 0x18>,
	<.rela.plt | offset 0x438, vaddr 0x400438, size 0xa8>,
	<.init | offset 0x4e0, vaddr 0x4004e0, size 0x18>,
	<.plt | offset 0x500, vaddr 0x400500, size 0x80>,
	<.text | offset 0x580, vaddr 0x400580, size 0x338>,
	<.fini | offset 0x8b8, vaddr 0x4008b8, size 0xe>,
	<.rodata | offset 0x8c8, vaddr 0x4008c8, size 0x63>,
	<.eh_frame_hdr | offset 0x92c, vaddr 0x40092c, size 0x44>,
	<.eh_frame | offset 0x970, vaddr 0x400970, size 0x104>,
	<.ctors | offset 0xe28, vaddr 0x600e28, size 0x10>,
	<.dtors | offset 0xe38, vaddr 0x600e38, size 0x10>,
	<.jcr | offset 0xe48, vaddr 0x600e48, size 0x8>,
	<.dynamic | offset 0xe50, vaddr 0x600e50, size 0x190>,
	<.got | offset 0xfe0, vaddr 0x600fe0, size 0x8>,
	<.got.plt | offset 0xfe8, vaddr 0x600fe8, size 0x50>,
	<.data | offset 0x1038, vaddr 0x601038, size 0x18>,
	<.bss | offset 0x1050, vaddr 0x601050, size 0x10>,
	<.comment | offset 0x1050, vaddr 0x0, size 0x2a>,
	<.shstrtab | offset 0x107a, vaddr 0x0, size 0xfe>,
	<.symtab | offset 0x18f8, vaddr 0x0, size 0x6d8>,
	<.strtab | offset 0x1fd0, vaddr 0x0, size 0x278>]>
 
# 포함되어진 주소로 개별 세그먼트 또는 섹션을 가져올 수 있다.
>>> obj.find_segment_containing(obj.entry)
<ELFSegment flags=0x5, relro=0x0, vaddr=0x400000,
	memsize=0xa74, filesize=0xa74, offset=0x0>
>>> obj.find_section_containing(obj.entry)
<.text | offset 0x580, vaddr 0x400580, size 0x338>
 
# symbols 대한 PLT stub 주소를 가져 온다.
>>> obj.plt
{'puts': 0x400510,
 'printf': 0x400520,
 'read': 0x400530,
 '__libc_start_main': 0x400540,
 'strcmp': 0x400550,
 'open': 0x400560,
 'exit': 0x400570}
>>> obj.plt['strcmp']
0x400550
>>> obj.reverse_plt[0x400550]
'strcmp'
 
# prelinked base, CLE에 이해 실제로 메모리에 매핑 된 위치를 표시한다.
>>> obj.linked_base
0x400000
>>> obj.mapped_base
0x400000

linked_base is the prelinked base; mapped_base is where CLE actually mapped the object.


Symbols and relocations

CLE also exposes symbols — executable-format records that map names to addresses. The easiest lookup is loader.find_symbol, which takes a name or address and returns a Symbol.

>>> strcmp = p.loader.find_symbol('strcmp')
>>> strcmp
<Symbol "strcmp" in libc-2.31.so at 0x7a22d0>

Name, owner, and address are the useful fields, but "address" is ambiguous. A Symbol exposes three:

  • .rebased_addr — address in the global address space
  • .linked_addr — relative to the binary's prelinked base (what readelf(1) shows)
  • .relative_addr — relative to the object base (RVA in Windows literature)
>>> strcmp.name
'strcmp'
>>> strcmp.owner
<ELF Object libc-2.31.so, maps [0x700000:0x8f14d7]>
 
>>> strcmp.rebased_addr
0x7a22d0
>>> strcmp.linked_addr
0xa22d0
>>> strcmp.relative_addr
0xa22d0

Symbols also drive dynamic linking. libc exports strcmp; the main binary imports it. Asking the main object for strcmp returns an import symbol with no meaningful address of its own, but .resolvedby points at the export that satisfied it.

>>> strcmp.is_export
True
>>> strcmp.is_import
False
# Loader에서 메서드는 Symbol을 찾기 위해 검색 작업을 수행하므로 find_symbol이다.
# 단일 객체에서 메서드는 주어진 이름을 가진 심볼이 하나만 있을 수 있기 때문에 get_symbol이다.
>>> main_strcmp = p.loader.main_object.get_symbol('strcmp')
<Symbol "strcmp" in fauxware (import)>
 
>>> main_strcmp.is_export
False
 
>>> main_strcmp.is_import
True
 
>>> main_strcmp.resolvedby
<Symbol "strcmp" in libc-2.31.so at 0x7a22d0>

Loader-level lookup is find_symbol (search). Object-level lookup is get_symbol (at most one name per object).

The import→export link is written into memory by relocations: when import X matches export Y, write Y's address at location Z in format F. See all relocations on obj.relocs, or the name→relocation map on imports. Relocation objects expose the related import symbol, the write address (same address flavors as symbols), and .owner for the requesting object.

>>> p.loader.shared_objects['libc.so.6'].imports
{'__libpthread_freeres': <cle.backends.elf.relocation.amd64.R_X86_64_GLOB_DAT object at 0x7f96c40a80a0>,
 '_rtld_global': <cle.backends.elf.relocation.amd64.R_X86_64_GLOB_DAT object at 0x7f96c403af40>,
 '__libc_enable_secure': <cle.backends.elf.relocation.amd64.R_X86_64_GLOB_DAT object at 0x7f96c405ad60>,
 '_rtld_global_ro': <cle.backends.elf.relocation.amd64.R_X86_64_GLOB_DAT object at 0x7f96c405a100>,
 '_dl_starting_up': <cle.backends.elf.relocation.amd64.R_X86_64_GLOB_DAT object at 0x7f96c405aeb0>,
 '__libdl_freeres': <cle.backends.elf.relocation.amd64.R_X86_64_GLOB_DAT object at 0x7f96c408f8b0>,
 '_dl_argv': <cle.backends.elf.relocation.amd64.R_X86_64_GLOB_DAT object at 0x7f96c405a2e0>,
 '__tls_get_addr': <cle.backends.elf.relocation.amd64.R_X86_64_JUMP_SLOT object at 0x7f96c4045100>,
 '_dl_exception_create': <cle.backends.elf.relocation.amd64.R_X86_64_JUMP_SLOT object at 0x7f96c4045460>,
 '__tunable_get_val': <cle.backends.elf.relocation.amd64.R_X86_64_JUMP_SLOT object at 0x7f96c4045910>,
 '_dl_find_dso_for_object': <cle.backends.elf.relocation.amd64.R_X86_64_JUMP_SLOT object at 0x7f96c408fa00>}

If a shared library is missing and an import cannot be resolved, CLE updates the extern object (loader.extern_object) so it can export a stand-in symbol.


Loading options

When you construct angr.Project, keyword arguments pass through to the cle.Loader it creates.

Basic options

  • auto_load_libs — whether CLE should resolve shared-library dependencies (default on)
  • except_missing_libs — if True, raise when a dependency cannot be resolved
  • force_load_libs — treat listed names as unresolved dependencies up front
  • skip_libs — never resolve libraries with those names
  • ld_path — extra search paths (string or list); defaults include the program directory, CWD, and system library paths

Per-binary options

main_opts and lib_opts take dictionaries. main_opts maps option name → value. lib_opts maps library name → {option name → value}.

Common options (backend-dependent):

  1. backend — class or name
  2. base_addr
  3. entry_point
  4. arch — architecture name
>>> angr.Project('./fauxware',
	main_opts={'backend': 'blob', 'arch':'i386'},
	lib_opts={'libc.so.6':{'backend':'elf'}}
)
WARNING | 2020-08-14 18:43:05,284 | cle.backends.blob | No entry_point was specified for blob fauxware, assuming 0
WARNING | 2020-08-14 18:43:05,284 | cle.backends.blob | No base_addr was specified for blob fauxware, assuming 0
<Project ./fauxware>

Backends

CLE can statically load ELF, PE, CGC, Mach-O, and ELF core dumps; load via IDA; or map a file as a flat address space (blob). CLE usually picks the right backend. Force one with the backend key in an options dict. Some backends cannot autodetect architecture and need arch. angr accepts most common architecture identifiers.

Symbolic function summaries

Projects try to replace external library calls with SimProcedure summaries — Python functions that mimic the library's effect on state.

auto_load_libs (TRUE)

When auto_load_libs is True (default), real library code runs instead. Some libc functions are extremely complex to analyze and can explode the number of states on a path.

auto_load_libs (FALSE)

When it is False, unresolved externals fall back to a generic stub SimProcedure, ReturnUnconstrained, which returns a fresh unconstrained symbol on every call.

use_sim_procedures (angr.Project parameter, not cle.Loader) (FALSE)

When False (default is True), only symbols provided by the extern object are replaced with SimProcedures; others become the ReturnUnconstrained stub.

Hooking

Hooking is how angr swaps binary code for a Python summary. At each simulation step, if the current address is hooked, angr runs the hook instead of the bytes at that address.

API: proj.hook(addr, hook) where hook is a SimProcedure instance. Manage hooks with .is_hooked, .unhook, and .hooked_by. You can also decorate a function with @proj.hook(addr) (optional length= to skip ahead after the hook returns).

>>> stub_func = angr.SIM_PROCEDURES['stubs']['ReturnUnconstrained']
<class 'angr.procedures.stubs.ReturnUnconstrained.ReturnUnconstrained'>
 
>>> p.hook(0x10000, stub_func()) # class의 인스턴스와 연결
>>> p.is_hooked(0x10000) # class의 인스턴스에 연결되었는지
True
>>> p.hooked_by(0x10000)
<SimProcedure ReturnUnconstrained>
>>> p.unhook(0x10000)
>>> @p.hook(0x20000, length=5)
... def my_hook(state):
...     state.regs.rax = 1
... 
>>> p.is_hooked(0x20000)
True

p.hook_symbol(name, hook) hooks by symbol name. A useful pattern is subclassing angr's built-in library SimProcedures and hooking the subclass in place of the stock behavior.

related

  1. Aug 14, 2020/archiveUsing angr for Binary Analysis, Part 1
  2. Aug 14, 2020/articleUsing angr for Binary Analysis, Part 3
  3. Aug 16, 2020/articleHITCON 2017 Sakura writeup

graphfeed