The typical way of finding function addresses is by calculating the offset to the desired function from the address of another function in the same library that we have leaked. However, for this method to work effectively the gLibc’s version of the remote server needs to be the same as ours. We can also find the remote version’s of gLibc by leaking a few functions and searching in libcdb.com but sometimes this method fails.
If we have a function that allows us to leak memory at any given address we could use something like DynELF using pwntools / binjitsu. As described in the documentation, DynELF uses 2 main techniques. First it finds the base address of gLibc and second it parses through all of the symbols using the Symbol Table Section and the String Table Section until it finds the symbol of the function we are looking for.
There are just a few details that I would like to put together, which are the real reason for this blog post :).
Finding GNU C Library’s Base Address
To find the base address of gLibc we first need to grab an address inside the gLibc’s address space. We can do that by looking into the binary’s Global Offset Table for an already resolved address. Next we can parse through that address space using the leak with decrements of the memory page size (0x1000) until we find the \x7fELF magic constant indicating the base load address. Here is example code to do that:
Our next goal is to identify the Elf32_Phdr structure specific for the DYNAMIC Section. We can do this by parsing throught all of the Program Header structures until we find the element Elf32_Phdr->p_type == 2. Once we find it, the element Elf32_Phdr->p_vaddr contains the virtual load address of the DYNAMIC Section.
Example code to find the DYNAMIC Section:
For PIE (Possition Independent Executable) binaries, at Elf32_Phdr->p_vaddr there’s gonna be an offset from the module’s base address. For non-PIE binaries there’s gonna be a virtual load address.
The DYNAMIC Section contains an array of Elf32_Dyn/Elf64_Dyn structures. Each structure contains information about section tables participating in the dynamic linking process. Some of them include DT_GOTPLT, DT_HASH, DT_STRTAB, DT_SYMTAB, DT_DEBUG any many more.
The dynamic section tables we are interested in are DT_SYMTAB aka Symbol Table and DT_STRTAB aka String Table.
The Symbol Table contains an array of Elf32_Sym/Elf64_Sym structures. There is a structure for each symbol/function we want to locate. The load address of the functions can be found in Elf32_Sym->st_value element. The Elf32_Sym->st_name element holds an offset in the DT_STRTAB which is where the string for the symbol in question is located.
Example code of finding DT_STRTAB and DT_SYMTAB:
Finding Function Addresses
To find the target symbol table we parse through each Elf32_Sym->st_name until DT_STRTAB[Elf32_Sym->st_name] == target_symbol.
Once the above proves True we have found the target Elf32_Sym struct, now we just look in the Elf32_Sym->st_value element to get the load address of the target symbol.
Here we are gonna read from /proc/<pid>/mem simulating a leak function. Using the above example let’s see how it finds the address of system.
Another method of finding the DYNAMIC Segment is using the link_map structure.
Link_map is the dynamic linker’s internal structure with which it keeps track of loaded libraries and symbols within libraries.
A small explanation for the fields:
l_addr: Base address where shared object is loaded. This value can also be found from /proc//maps
l_name: pointer to library name in string table
l_ld : pointer to dynamic (DT_*) sections of shared lib
l_next: pointer to next link_map node
l_prev: pointer to previous link_map node
We can find the link_map structure located on the index  slot in the GOT array.
At runtime this index will be populated by the runtime linker as we can see here.
GOT is a the address of the module’s DYNAMIC section. GOT is the virtual load address of the link_map, GOT is the address for the runtime resolver function (which we will cover next).
So, if we walk the linked list until link_map->l_name contains the full path to the loaded gLibc library we can find the gLibc’s DYNAMIC section in link_map->l_ld element and gLibc’s base address in link_map->l_addr.
In the above snippet we can verify after traversing 3 link_map structures we found libc’s link_map with base found at link_map->l_addr == 0xf7e1e000, DYNAMIC section found at link_map->l_ld == 0xf7fc7da8 and the loaded module’s full path link_map->l_name == "/lib/i386-linux-gnu/libc.so.6"
In FULL RELRO enabled binaries the link_map looks like it’s not in the GOT anymore, but thanks to this stackoverflow post I learned we can find it in the DT_DEBUG table located from the DYNAMIC Segment.