pwnlib.dynelf — Resolving remote functions using leaks¶
Resolve symbols in loaded, dynamically-linked ELF binaries. Given a function which can leak data at an arbitrary address, any symbol in any loaded library can be resolved.
# Assume a process or remote connection p = process('./pwnme') # Declare a function that takes a single address, and # leaks at least one byte at that address. def leak(address): data = p.read(address, 4) log.debug("%#x => %s" % (address, (data or '').encode('hex'))) return data # For the sake of this example, let's say that we # have any of these pointers. One is a pointer into # the target binary, the other two are pointers into libc main = 0xfeedf4ce libc = 0xdeadb000 system = 0xdeadbeef # With our leaker, and a pointer into our target binary, # we can resolve the address of anything. # # We do not actually need to have a copy of the target # binary for this to work. d = DynELF(leak, main) assert d.lookup(None, 'libc') == libc assert d.lookup('system', 'libc') == system # However, if we *do* have a copy of the target binary, # we can speed up some of the steps. d = DynELF(leak, main, elf=ELF('./pwnme')) assert d.lookup(None, 'libc') == libc assert d.lookup('system', 'libc') == system # Alternately, we can resolve symbols inside another library, # given a pointer into it. d = DynELF(leak, libc + 0x1234) assert d.lookup('system') == system
Here’s another example which actually serves as a doctest. First, let’s assume that we have a pointer somewhere into the main executable.
>>> maps = read('/proc/self/maps').splitlines() >>> map = (line for line in maps if '/python' in line).next() >>> addr = map.split('-') >>> addr = int(addr, 16)
Normally we don’t get the base address, but some random offset into the binary.
>>> addr += 0xdead
Next, let’s describe a leak function which can return memory from the target address space.
>>> @MemLeak ... def leak(addr): ... with open('/proc/self/mem', 'rb') as mem: ... mem.seek(addr) ... try: ... return mem.read(32) ... except: ... pass
Now we can find arbitrary functions, in arbitrary modules.
>>> de = DynELF(leak, addr) >>> system = de.lookup('system', 'libc')
Let’s try out our function pointer! Normally we would have register or stack control to do this, but we can do it properly with ctypes.
>>> import ctypes >>> system_functype = ctypes.CFUNCTYPE(None, ctypes.c_char_p) >>> system_functype(system)("echo hello > hello.dynelf") >>> read('hello.dynelf') 'hello\n'
DynELF(leak, pointer=None, elf=None)¶
DynELF knows how to resolve symbols in remote processes via an infoleak or memleak vulnerability encapsulated by
In all ELFs which export symbols for importing by other libraries, (e.g.
libc.so) there are a series of tables which give exported symbol names, exported symbol addresses, and the
hashof those exported symbols. By applying a hash function to the name of the desired symbol (e.g.,
'printf'), it can be located in the hash table. Its location in the hash table provides an index into the string name table (strtab), and the symbol address (symtab).
Assuming we have the base address of
libc.so, the way to resolve the address of
printfis to locate the
strtab, and hash table. The string
"printf"is hashed according to the style of the hash table (SYSV or GNU), and the hash table is walked until a matching entry is located. We can verify an exact match by checking the string table, and then get the offset into
Resolving Library Addresses:
If we have a pointer into a dynamically-linked executable, we can leverage an internal linker structure called the link map. This is a linked list structure which contains information about each loaded library, including its full path and base address.
A pointer to the
link mapcan be found in two ways. Both are referenced from entries in the DYNAMIC array.
- In non-RELRO binaries, a pointer is placed in the .got.plt area in the binary. This is marked by finding the DT_PLTGOT area in the binary.
- In all binaries, a pointer can be found in the area described by the DT_DEBUG area. This exists even in stripped binaries.
For maximum flexibility, both mechanisms are used exhaustively.
Resolve base addresses of all loaded libraries.
Return a dictionary mapping library path to its base address.
Returns – Pointer to the
32 or 64
pwnlib.memleak.MemLeakobject and a pointer into a library, find its base address.
Finds the beginning of the heap via __curbrk, which is an exported symbol in the linker, which points to the current brk.
Leak the Build ID of the remote libc.so, download the file, and load an
ELFobject with the correct base address.
Returns: An ELF object, or None.
Pointer to the runtime link_map object
lookup(symb = None, lib = None) → int¶
Find the address of
symbol, which is found in
Address of the named symbol, or
Finds a pointer to the stack via __environ, which is an exported symbol in libc, which points to the environment block.
gnu_hash(str) → int¶
Function used to generated GNU-style hashes for strings.
sysv_hash(str) → int¶
Function used to generate SYSV-style hashes for strings.