内容简介:(pwn场景中,译者注)查找某个函数地址的典型方法是:通过计算与同一库中另一个已泄露出地址的函数的偏移量,得到所需函数的地址。然而,要使此方法有效工作,远程服务器的gLibc版本需要与我们本地的版本相同。当然我们也可以通过泄漏一些函数并在libcdb.com中搜索找到匹配的gLibc版本,但有时这种方法会失败。如果我们有一个函数允许我们在任何给定地址泄漏内存,我们可以通过
前言
(pwn场景中,译者注)查找某个函数地址的典型方法是:通过计算与同一库中另一个已泄露出地址的函数的偏移量,得到所需函数的地址。然而,要使此方法有效工作,远程服务器的gLibc版本需要与我们本地的版本相同。当然我们也可以通过泄漏一些函数并在libcdb.com中搜索找到匹配的gLibc版本,但有时这种方法会失败。
DynELF
如果我们有一个函数允许我们在任何给定地址泄漏内存,我们可以通过 pwntools / binjitsu 使用类似 DynELF 的 工具 来找到所需要的函数地址。如文档所述,DynELF主要使用了两种技术。它使用的第一种技术是:找到gLibc的基址,然后使用符号表和字符表解析所有的符号,直到找到我们要查找的函数的符号。
这里有一些细节我想放在一起公布出来,这是写这篇博客文章的真正原因:).
查找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
找到程序头
程序头包含一个结构体数组,结构体类型依据32/64架构分别为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, '')) else: e_phoff = u64(leak(addr + 0x20, wordSz).ljust(8, '')) 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, '')) return u32(leak(Elf32_Phdr + i + 8, wordSz).ljust(4, '')) # + PIE else: i = -56 p_type = 0 while p_type != 2: i += 56 p_type = u64(leak(Elf32_Phdr + i, hwordSz).ljust(8, '')) return u64(leak(Elf32_Phdr + i + 16, wordSz).ljust(8, '')) # + PIE
对于开启了PIE保护(位置独立的可执行区域)的二进制程序,在Elf32_Phdr->p_vaddr将有一个到模块的基地址的偏移量。对于未开启pie的二进制文件,Elf32_Phdr->p_vaddr将有一个虚拟地址。
DYNAMIC部分包含一个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_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, '')) return u32(leak(Elf32_Dyn + i + 4, wordSz).ljust(4, '')) else: i = -16 while p_val != table: i += 16 p_val = u64(leak(Elf32_Dyn + i, wordSz).ljust(8, '')) return u64(leak(Elf32_Dyn + i + 8, wordSz).ljust(8, '')) 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, '')) if leak( strtab + st_name, len(symbol)+1 ).lower() == (symbol.lower() + ''): return u32(leak(symtab + i + 4, 4).ljust(4, '')) else: i = -24 while True: i += 24 st_name = u64(leak(symtab + i, 4).ljust(8, '')) if leak( strtab + st_name, len(symbol)).lower() == (symbol.lower()): return u64(leak(symtab + i + 8, 8).ljust(8, ''))
完整的例子
这里我们将从/proc/<pid>/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, '')) else: e_phoff = u64(leak(addr + 0x20, wordSz).ljust(8, '')) 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, '')) return u32(leak(Elf32_Phdr + i + 8, wordSz).ljust(4, '')) + PIE else: i = -56 p_type = 0 while p_type != 2: i += 56 p_type = u64(leak(Elf32_Phdr + i, hwordSz).ljust(8, '')) return u64(leak(Elf32_Phdr + i + 16, wordSz).ljust(8, '')) + 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, '')) return u32(leak(Elf32_Dyn + i + 4, wordSz).ljust(4, '')) else: i = -16 while p_val != table: i += 16 p_val = u64(leak(Elf32_Dyn + i, wordSz).ljust(8, '')) return u64(leak(Elf32_Dyn + i + 8, wordSz).ljust(8, '')) 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, '')) else: gotPtr = u64(leak(addr + i*8, wordSz).ljust(8, '')) 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, '')) if leak( strtab + st_name, len(symbol)+1 ).lower() == (symbol.lower() + ''): return u32(leak(symtab + i + 4, 4).ljust(4, '')) else: i = -24 while True: i += 24 st_name = u64(leak(symtab + i, 4).ljust(8, '')) if leak( strtab + st_name, len(symbol)).lower() == (symbol.lower()): return u64(leak(symtab + i + 8, 8).ljust(8, '')) 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结构体
DynELF所使用的另一种查找动态段的方法是使用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. */ };
关于上面字段的说明:
1.l_addr:加载共享对象的基本地址。这个值也可以从/proc//maps中找到
2.l_name:指向共享库中动态节区的指针
3.l_next:指向下一个link_map节点的指针
4.l_prev:指向前一个link_map节点的指针
我们可以在GOT表的第二项中找到link_map结构:
在运行时,这个地址将由运行时链接器填充,我们可以在这里看到:
gdb-peda$ x/4wx 0x804b000 0x804b000: 0x0804af14 0xf7ffd938 0xf7ff04f0 0x080484c6 gdb-peda$
GOT表的第一项是模块动态部分的地址。GOT表的第二项是link_map的虚拟地址(上图中箭头所指部分,译者注),GOT表的第三项是运行时解析器函数的地址(我们将在接下来讨论)。
因此,如果我们遍历链表,直到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(read only relocation)保护的二进制程序中link_map看起来不在GOT表中了,但是由于多谢这篇 stackoverflow 上的文章,我才知道我们可以在位于动态节区的DT_DEBUG表中找到它。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- LeetCode 69. x 的平方根:二分查找法实现自定义的函数:x 的平方根
- PHP有序表查找之二分查找(折半查找)算法示例
- 查找--插值查找(Java)
- 查找算法(一):查找
- 二叉查找树(查找、插入、删除)——C语言
- 数据结构和算法(Golang实现)(27)查找算法-二叉查找树
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。