查找函数的加载地址
指针
寻找函数地址的典型方法是通过计算我们泄漏的同一个库中另一个函数的地址到所需函数的偏移量。但是,要使此方法有效工作,glibc的远程服务器版本需要与我们的版本相同。我们也可以通过泄漏一些函数并在libcdb.com中搜索来找到glibc的远程版本,但是有时这个方法会失败。
DynELF
如果我们有一个函数允许我们在任何给定的地址泄漏内存,我们可以使用类似DynELF使用pwntools / binjitsu。如文档所述,DynELF使用了两种主要技术。首先,它找到gLibc的基本地址,然后使用Symbol Table部分和String Table部分解析所有的符号,直到找到我们要查找的函数的符号。
这里有一些我想放在一起的细节,这是这篇博文的真正原因:)。
查找GNU C库的基本地址
要找到gLibc的基本地址,我们首先需要在gLibc的地址空间中获取一个地址。我们可以通过查看二进制文件的全局偏移表来获得一个已经解析的地址。接下来,我们可以使用内存页大小(0x1000)递减的泄漏来解析这个地址空间,直到找到指示基本负载地址的\x7fELF常量。下面是实现这一目的的示例代码:
# leak func returns n number of bytes from given address
def findLibcBase(ptr):
ptr &= 0xfffffffffffff000
while leak(ptr, 4) != "\x7fELF":
ptr -= 0x1000
return ptr
查找程序标头
程序头包含一个Elf32_Phdr/Elf64_Phdr结构数组,每个结构保存二进制文件中现有段的信息。
要查找程序头的起始位置,只需查看ELF头(模块的基本地址)中偏移量0x1c(32位二进制)或0x20(64位二进制)。
Elf32_Phdr结构包含以下元素:
typedef struct {
Elf32_Word p_type;
Elf32_Off p_offset;
Elf32_Addr p_vaddr;
Elf32_Addr p_paddr;
Elf32_Word p_filesz;
Elf32_Word p_memsz;
Elf32_Word p_flags;
Elf32_Word p_align;
} Elf32_Phdr;
找到程序头开始的示例代码:
# addr argument is the module's base address or rather the beginning of ELF Header
def findPhdr(addr):
if bits == 32:
e_phoff = u32(leak(addr + 0x1c, wordSz).ljust(4, '\0'))
else:
e_phoff = u64(leak(addr + 0x20, wordSz).ljust(8, '\0'))
return e_phoff + addr
求动态部分
我们的下一个目标是识别特定于动态部分的Elf32_Phdr结构。我们可以通过解析所有的程序头结构来做到这一点,直到找到元素Elf32_Phdr->p_type == 2。找到它之后,元素Elf32_Phdr->p_vaddr包含动态部分的虚拟加载地址。
找到动态部分的例子代码:
def findDynamic(Elf32_Phdr, bitSz):
if bitSz == 32:
i = -32
p_type = 0
while p_type != 2:
i += 32
p_type = u32(leak(Elf32_Phdr + i, wordSz).ljust(4, '\0'))
return u32(leak(Elf32_Phdr + i + 8, wordSz).ljust(4, '\0')) # + PIE
else:
i = -56
p_type = 0
while p_type != 2:
i += 56
p_type = u64(leak(Elf32_Phdr + i, hwordSz).ljust(8, '\0'))
return u64(leak(Elf32_Phdr + i + 16, wordSz).ljust(8, '\0')) # + PIE
对于PIE (Possition独立可执行文件)二进制文件,在Elf32_Phdr->p_vaddr中会有一个来自模块基址的偏移量。对于非pie二进制文件,将有一个虚拟加载地址。
动态部分包含一个Elf32_Dyn/Elf64_Dyn结构数组。每个结构都包含有关参与动态链接过程的节表的信息。其中包括DT_GOTPLT、DT_HASH、DT_STRTAB、DT_SYMTAB、DT_DEBUG等等。
我们感兴趣的动态部分表是DT_SYMTAB aka符号表和DT_STRTAB aka字符串表。
找到DT_SYMTAB 和DT_STRTAB
符号表包含一个Elf32_Sym/Elf64_Sym结构数组。我们想要定位的每个符号/函数都有一个结构。函数的加载地址可以在Elf32_Sym->st_value元素中找到。Elf32_Sym->st_name元素在DT_STRTAB中保存一个偏移量,该偏移量是有关符号的字符串所在的位置。
查找DT_STRTAB和DT_SYMTAB的示例代码:
def findDynTable(Elf32_Dyn, table, bitSz):
p_val = 0
if bitSz == 32:
i = -8
while p_val != table:
i += 8
p_val = u32(leak(Elf32_Dyn + i, wordSz).ljust(4, '\0'))
return u32(leak(Elf32_Dyn + i + 4, wordSz).ljust(4, '\0'))
else:
i = -16
while p_val != table:
i += 16
p_val = u64(leak(Elf32_Dyn + i, wordSz).ljust(8, '\0'))
return u64(leak(Elf32_Dyn + i + 8, wordSz).ljust(8, '\0'))
DT_STRTAB = findDynTable(libcDynamic, 5, bits)
DT_SYMTAB = findDynTable(libcDynamic, 6, bits)
寻找函数地址
为了找到目标符号表,我们解析每个Elf32_Sym->st_name,直到DT_STRTAB[Elf32_Sym->st_name] == target_symbol。一旦以上证明是正确的,我们就找到了目标Elf32_Sym结构,现在我们只需查看Elf32_Sym->st_value元素来获得目标符号的加载地址。
示例代码片段:
def findSymbol(strtab, symtab, symbol, bitSz):
if bitSz == 32:
i = -16
while True:
i += 16
st_name = u32(leak(symtab + i, 2).ljust(4, '\0'))
if leak( strtab + st_name, len(symbol)+1 ).lower() == (symbol.lower() + '\0'):
return u32(leak(symtab + i + 4, 4).ljust(4, '\0'))
else:
i = -24
while True:
i += 24
st_name = u64(leak(symtab + i, 4).ljust(8, '\0'))
if leak( strtab + st_name, len(symbol)).lower() == (symbol.lower()):
return u64(leak(symtab + i + 8, 8).ljust(8, '\0'))
最终例子
这里我们将从/proc//mem中读取模拟泄漏函数。使用上面的示例,让我们看看它是如何查找系统地址的。
#!/usr/bin/env python
from pwn import *
import sys, os
wordSz = 4
hwordSz = 2
bits = 32
PIE = 0
def leak(address, size):
with open('/proc/%s/mem' % pid) as mem:
mem.seek(address)
return mem.read(size)
def findModuleBase(pid, mem):
name = os.readlink('/proc/%s/exe' % pid)
with open('/proc/%s/maps' % pid) as maps:
for line in maps:
if name in line:
addr = int(line.split('-')[0], 16)
mem.seek(addr)
if mem.read(4) == "\x7fELF":
bitFormat = u8(leak(addr + 4, 1))
if bitFormat == 2:
global wordSz
global hwordSz
global bits
wordSz = 8
hwordSz = 4
bits = 64
return addr
log.failure("Module's base address not found.")
sys.exit(1)
def findIfPIE(addr):
e_type = u8(leak(addr + 0x10, 1))
if e_type == 3:
return addr
else:
return 0
def findPhdr(addr):
if bits == 32:
e_phoff = u32(leak(addr + 0x1c, wordSz).ljust(4, '\0'))
else:
e_phoff = u64(leak(addr + 0x20, wordSz).ljust(8, '\0'))
return e_phoff + addr
def findDynamic(Elf32_Phdr, moduleBase, bitSz):
if bitSz == 32:
i = -32
p_type = 0
while p_type != 2:
i += 32
p_type = u32(leak(Elf32_Phdr + i, wordSz).ljust(4, '\0'))
return u32(leak(Elf32_Phdr + i + 8, wordSz).ljust(4, '\0')) + PIE
else:
i = -56
p_type = 0
while p_type != 2:
i += 56
p_type = u64(leak(Elf32_Phdr + i, hwordSz).ljust(8, '\0'))
return u64(leak(Elf32_Phdr + i + 16, wordSz).ljust(8, '\0')) + PIE
def findDynTable(Elf32_Dyn, table, bitSz):
p_val = 0
if bitSz == 32:
i = -8
while p_val != table:
i += 8
p_val = u32(leak(Elf32_Dyn + i, wordSz).ljust(4, '\0'))
return u32(leak(Elf32_Dyn + i + 4, wordSz).ljust(4, '\0'))
else:
i = -16
while p_val != table:
i += 16
p_val = u64(leak(Elf32_Dyn + i, wordSz).ljust(8, '\0'))
return u64(leak(Elf32_Dyn + i + 8, wordSz).ljust(8, '\0'))
def getPtr(addr, bitSz):
with open('/proc/%s/maps' % sys.argv[1]) as maps:
for line in maps:
if 'libc-' in line and 'r-x' in line:
libc = line.split(' ')[0].split('-')
i = 3
while True:
if bitSz == 32:
gotPtr = u32(leak(addr + i*4, wordSz).ljust(4, '\0'))
else:
gotPtr = u64(leak(addr + i*8, wordSz).ljust(8, '\0'))
if (gotPtr > int(libc[0], 16)) and (gotPtr < int(libc[1], 16)):
return gotPtr
else:
i += 1
continue
def findLibcBase(ptr):
ptr &= 0xfffffffffffff000
while leak(ptr, 4) != "\x7fELF":
ptr -= 0x1000
return ptr
def findSymbol(strtab, symtab, symbol, bitSz):
if bitSz == 32:
i = -16
while True:
i += 16
st_name = u32(leak(symtab + i, 2).ljust(4, '\0'))
if leak( strtab + st_name, len(symbol)+1 ).lower() == (symbol.lower() + '\0'):
return u32(leak(symtab + i + 4, 4).ljust(4, '\0'))
else:
i = -24
while True:
i += 24
st_name = u64(leak(symtab + i, 4).ljust(8, '\0'))
if leak( strtab + st_name, len(symbol)).lower() == (symbol.lower()):
return u64(leak(symtab + i + 8, 8).ljust(8, '\0'))
def lookup(pid, symbol):
with open('/proc/%s/mem' % pid) as mem:
moduleBase = findModuleBase(pid, mem)
log.info("Module's base address:................. " + hex(moduleBase))
global PIE
PIE = findIfPIE(moduleBase)
if PIE:
log.info("Binary is PIE enabled.")
else:
log.info("Binary is not PIE enabled.")
modulePhdr = findPhdr(moduleBase)
log.info("Module's Program Header:............... " + hex(modulePhdr))
moduleDynamic = findDynamic(modulePhdr, moduleBase, bits)
log.info("Module's _DYNAMIC Section:............. " + hex(moduleDynamic))
moduleGot = findDynTable(moduleDynamic, 3, bits)
log.info("Module's GOT:.......................... " + hex(moduleGot))
libcPtr = getPtr(moduleGot, bits)
log.info("Pointer from GOT to a function in libc: " + hex(libcPtr))
libcBase = findLibcBase(libcPtr)
log.info("Libc's base address:................... " + hex(libcBase))
libcPhdr = findPhdr(libcBase)
log.info("Libc's Program Header:................. " + hex(libcPhdr))
PIE = findIfPIE(libcBase)
libcDynamic = findDynamic(libcPhdr, libcBase, bits)
log.info("Libc's _DYNAMIC Section:............... " + hex(libcDynamic))
libcStrtab = findDynTable(libcDynamic, 5, bits)
log.info("Libc's DT_STRTAB Table:................ " + hex(libcStrtab))
libcSymtab = findDynTable(libcDynamic, 6, bits)
log.info("Libc's DT_SYMTAB Table:................ " + hex(libcSymtab))
symbolAddr = findSymbol(libcStrtab, libcSymtab, symbol, bits)
log.success("%s loaded at address:.............. %s" % (symbol, hex(symbolAddr + libcBase)))
if __name__ == "__main__":
log.info("Manual usage of pwnlib.dynelf")
if len(sys.argv) == 3:
pid = sys.argv[1]
symbol = sys.argv[2]
lookup(pid, symbol)
else:
log.failure("Usage: %s PID SYMBOL" % sys.argv[0])
示例输出:
➜ ~ python ./DynELF_manual.py 29530 system
[*] Manual usage of pwnlib.dynelf
[*] Module's base address:................. 0x400000
[*] Binary is not PIE enabled.
[*] Module's Program Header:............... 0x400040
[*] Module's _DYNAMIC Section:............. 0x602e08
[*] Module's GOT:.......................... 0x603000
[*] Pointer from GOT to a function in libc: 0x7ffff743ddd0
[*] Libc's base address:................... 0x7ffff741c000
[*] Libc's Program Header:................. 0x7ffff741c040
[*] Libc's _DYNAMIC Section:............... 0x7ffff77d9ba0
[*] Libc's DT_STRTAB Table:................ 0x7ffff742cd78
[*] Libc's DT_SYMTAB Table:................ 0x7ffff741fd28
[+] system loaded at address:.............. 0x7ffff7462640
➜ ~
链接图结构
另一种查找动态段的方法是使用link_map结构。Link_map是动态链接器的内部结构,用于跟踪加载的库和库中的符号。
struct link_map
{
ElfW(Addr) l_addr; /* Difference between the address in the ELF
file and the addresses in memory. */
char *l_name; /* Absolute file name object was found in. */
ElfW(Dyn) *l_ld; /* Dynamic section of the shared object. */
struct link_map *l_next, *l_prev; /* Chain of loaded objects. */
};
字段的一个小说明:
l_addr:加载共享对象的基本地址。这个值也可以在/proc//maps中找到
l_name:指向字符串表中库名的指针
l_ld : 指向共享库的动态(DT_*)部分的指针
l_next:指向下一个link_map节点的指针
l_prev:指向以前的link_map节点的指针
我们可以在get数组的索引[1]槽中找到link_map结构。
在运行时,这个索引将由运行时链接器填充,正如我们在这里看到的。
gdb-peda$ x/4wx 0x804b000
0x804b000: 0x0804af14 0xf7ffd938 0xf7ff04f0 0x080484c6
gdb-peda$ ^^^
get[0]是模块动态部分的地址。get[1]是link_map的虚拟加载地址,get[2]是运行时解析器函数的地址(我们将在下面讨论)。
SGOT[0]是模块动态部分的地址。get[1]是link_map的虚拟加载地址,get[2]是运行时解析器函数的地址(我们将在下面讨论)。
因此,如果我们遍历链接列表,直到link_map->l_name包含加载的gLibc库的完整路径,我们就可以在link_map->l_ld元素中找到gLibc的动态部分,在link_map->l_addr中找到gLibc的基本地址。
gdb-peda$ x/4wx 0x804b000
0x804b000: 0x0804af14 0xf7ffd938 0xf7ff04f0 0x080484c6
gdb-peda$ x/4wx 0xf7ffd938
0xf7ffd938: 0x00000000 0xf7ffdc24 0x0804af14 0xf7ffdc28
gdb-peda$ x/4wx 0xf7ffdc28
0xf7ffdc28: 0xf7fdd000 0xf7ffde94 0xf7fdb350 0xf7fda858
gdb-peda$ x/4wx 0xf7fda858
0xf7fda858: 0xf7e1e000 0xf7fda838 0xf7fc7da8 0xf7ffd55c
gdb-peda$ x/s 0xf7fda838
0xf7fda838: "/lib/i386-linux-gnu/libc.so.6"
gdb-peda$ vmmap libc
Start End Perm Name
0xf7e1e000 0xf7fc6000 r-xp /lib/i386-linux-gnu/libc-2.19.so
0xf7fc6000 0xf7fc8000 r--p /lib/i386-linux-gnu/libc-2.19.so
0xf7fc8000 0xf7fc9000 rw-p /lib/i386-linux-gnu/libc-2.19.so
gdb-peda$
在上面的代码片段中,我们可以验证在遍历了3个link_map结构之后,我们找到了libc的link_map,它的基础是link_map->l_addr == 0xf7e1e000
,动态部分是在link_map->l_ld == 0xf7fc7da8
和加载模块的完整路径link_map->l_name == "/lib/i386-linux-gnu/libc.so.6"
在完全支持RELRO的二进制文件中,link_map看起来好像不在get中了,但是多亏了这篇stackoverflow的文章,我知道我们可以在动态段的DT_DEBUG表中找到它。
原文地址:https://uaf.io/exploitation/misc/2016/04/02/Finding-Functions.html