遇到无法leak的题目的时候..日常roputils一把梭..这次就打算从原理学习一下”Return_to_dl_resolve“这项技术。可能写的会有些杂乱
借鉴和参考学习的师傅们的文章 1 2 3 4 5 6 https: //veritas501 .space/2017 /10 /07 /ret 2 dl_resolve%E5 %AD %A6 %E4 %B9 %A0 %E7 %AC %94 %E8 %AE %B0 /http: //pwn4 .fun/2016 /11 /09 /Return-to -dl-resolve/https: //www.hirworld.xyz/posts/3 bfd0449 /https: //www.anquanke.com/post/id/177450 #h3 -7 http: //www.reversing.win/2017 /08 /29 /%E4 %BA %8 C%E6 %A0 %88 %E6 %BA %A2 %E5 %87 %BA %E6 %BC %8 F%E6 %B4 %9 E%E5 %88 %A9 %E7 %94 %A8-ret2resolve /https: //xz.aliyun.com/t/5111 #toc-2
看了这么多才看懂。。实在是tcl…
原理部分 ret2_dl技术利用了函数的延迟绑定机制
动态链接 ELF将GOT表拆成了两个表,一个是“.got”(保存变量),”.got.plt”(保存函数) 而.got.plt表的前三项为:
.dynamic 段的地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 .dynamic段:Elf32_Dyn 由一个类型值加上一个附加的数值或指针。 /*typedef struct { Elf32_Sword d_tag; union { Elf32_Word d_val; Elf32_Addr d_ptr; } } Elf32_Dyn */DT_SYMTAB ,动态链接符号表的地址,d_ptr表示.dynsym的地址DT_STRTAB ,动态链接字符串表地址,d_ptr表示.dynstr的地址DT_HASH ,动态链接哈希表大小,d_val表示大小DT_SONAME ,本共享对象的SO -NAME DT_RPATH ,动态链接共享对象搜索路径DT_INIT ,初始化代码地址DT_FINIT ,结束代码地址DT_NEED ,依赖的共享对象文件,d_ptr表示依赖的共享对象文件名DT_REL ,动态链接重定位表地址DT_RELA ,DT_RELENT ,动态重读位表入口数量DT_RELANET
本模块的 ID
_dl_runtime_resolve()的地址
我们可以查看任意程序的节区:
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 Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .interp PROGBITS 08048154 000154 000013 00 A 0 0 1 [ 2] .note.ABI-tag NOTE 08048168 000168 000020 00 A 0 0 4 [ 3] .note.gnu.build-i NOTE 08048188 000188 000024 00 A 0 0 4 [ 4] .gnu.hash GNU_HASH 080481ac 0001ac 00002c 04 A 5 0 4 [ 5] .dynsym DYNSYM 080481d8 0001d8 0000a0 10 A 6 1 4 [ 6] .dynstr STRTAB 08048278 000278 00006b 00 A 0 0 1 [ 7] .gnu.version VERSYM 080482e4 0002e4 000014 02 A 5 0 2 [ 8] .gnu.version_r VERNEED 080482f8 0002f8 000020 00 A 6 1 4 [ 9] .rel.dyn REL 08048318 000318 000018 08 A 5 0 4 [10] .rel.plt REL 08048330 000330 000028 08 AI 5 24 4 [11] .init PROGBITS 08048358 000358 000023 00 AX 0 0 4 [12] .plt PROGBITS 08048380 000380 000060 04 AX 0 0 16 [13] .plt.got PROGBITS 080483e0 0003e0 000008 00 AX 0 0 8 [14] .text PROGBITS 080483f0 0003f0 000232 00 AX 0 0 16 [15] .fini PROGBITS 08048624 000624 000014 00 AX 0 0 4 [16] .rodata PROGBITS 08048638 000638 000008 00 A 0 0 4 [17] .eh_frame_hdr PROGBITS 08048640 000640 000034 00 A 0 0 4 [18] .eh_frame PROGBITS 08048674 000674 0000f4 00 A 0 0 4 [19] .init_array INIT_ARRAY 08049f08 000f08 000004 00 WA 0 0 4 [20] .fini_array FINI_ARRAY 08049f0c 000f0c 000004 00 WA 0 0 4 [21] .jcr PROGBITS 08049f10 000f10 000004 00 WA 0 0 4 [22] .dynamic DYNAMIC 08049f14 000f14 0000e8 08 WA 6 0 4 [23] .got PROGBITS 08049ffc 000ffc 000004 04 WA 0 0 4 [24] .got.plt PROGBITS 0804a000 001000 000020 04 WA 0 0 4 [25] .data PROGBITS 0804a020 001020 000008 00 WA 0 0 4 [26] .bss NOBITS 0804a040 001028 00000c 00 WA 0 0 32 [27] .comment PROGBITS 00000000 001028 000035 01 MS 0 0 1 [28] .shstrtab STRTAB 00000000 001798 00010a 00 0 0 1 [29] .symtab SYMTAB 00000000 001060 0004b0 10 30 47 4 [30] .strtab STRTAB 00000000 001510 000288 00 0 0 1
其中.rel.plt是用于函数重定位的 其结构体定义如下:
1 2 3 4 5 6 7 8 typedef struct { Elf32_Addr r_offset; Elf32_Word r_info; } Elf32_Rel;#define ELF32_R_SYM (info) ((info)>>8 )#define ELF32_R_TYPE (info) ((unsigned char)(info))#define ELF32_R_INFO (sym, type) (((sym)<<8 )+(unsigned char)(type))
而在最前面我们也提及过got.plt中保存了函数偏移,而这个正好对应的是Rel结构体中的r_offset,而LF32_R_TYPE=7,这也是R_386_JUMP_SLOT的值
.dynsym节包含了动态链接符号表 其结构体定义如下:
1 2 3 4 5 6 7 8 9 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_arg压入栈中然后跳转到函数的PLT[0]位置
PLT[0]将link_map压入栈中
调用_dl_runtime_resolve(link_map, reloc_arg)函数,将函数的绝对地址写入GOT表中,从而完成调用
这其中还调用了_dl_fixup函数,这里引用师傅的函数解释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 _dl_fixup(struct link_map *l, ElfW (Word) reloc_arg) { 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)]; assert (ELFW (R_TYPE)(reloc-> r_info) == ELF_MACHINE_JMP_SLOT); result = _dl_lookup_symbol_x (strtab + sym-> st_name, l, &sym, l-> l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL); value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym-> st_value) : 0 ); return elf_machine_fixup_plt (l, result, reloc, rel_addr, value); }
到这里动态链接的过程就结束了
漏洞利用 在上面的链接过程中我们了解到 在.dynsym里第一个是name的地址,而这个地址加上.dynstr就是字符串的位置,如我的示例程序中.dynsym地址为:0x80481d8,.dynstr为0x08048278:
1 2 3 4 5 6 7 8 9 10 11 12 13 pwndbg > x/32 x 0 x80481d80x80481d8 : 0 x00000000 0 x00000000 0 x00000000 0 x000000000x80481e8 : 0 x00000033 0 x00000000 0 x00000000 0 x000000120x80481f8 : 0 x00000027 0 x00000000 0 x00000000 0 x000000120x8048208 : 0 x00000052 0 x00000000 0 x00000000 0 x000000200x8048218 : 0 x00000020 0 x00000000 0 x00000000 0 x000000120x8048228 : 0 x0000003a 0 x00000000 0 x00000000 0 x000000120x8048238 : 0 x0000004c 0 x00000000 0 x00000000 0 x000000120x8048248 : 0 x0000002c 0 x0804a044 0 x00000004 0 x001a0011pwndbg > x/s 0 x08048278 +0 x330x80482ab : "setbuf" pwndbg > x/s 0 x08048278 +0 x270x804829f : "read"
综上,我们可以得到一个利用的思路(这里调整综合了一些师傅的思路):
根据.dynamic段是否可写分为两种:
当.dynamic段可写时,我们可以把.dynstr段劫持到.bss段,而在.bss段里伪造目标字符串
当.dynamic不可写时,我们可以把传入的rel_offset改为目标函数的偏移大小.
因为.rel.plt段中不一定存在我们需要的函数,所以我们可以将rel_offset修改为一个比较大的值,然后伪造一个.rel.plt段
因为程序是根据r_info来找到.dynsym[num]的,所以我们可以伪造Elf32_Sym -> st_name,使.dynstr + st_name可控,指向我们伪造的函数字符串(函数名)
假设从bss + 0×80处伪造.rel.plt项,rel_offset = bss_address + 0x80 - Addr[.rel.plt].
r_offset为函数对应.got.plt项的地址.r_info保存了对应.dynsym中的index和flag.可以在这段数据后伪造.dynsym,例如:
bss + 0x100处,即index = (bss + 0×100 - Addr[.dynsym]) / 0×10(SYMENT为16字节),类型必须为7(LF32_R_TYPE)
r_info = (index << 8 ) | 0x7.
因为.dynsym包含四个字段,而我们只需要更改st_name部分即可,其余的值完全可以不更改,因为st_name表示了字符串相对strtab的偏移,所以我们可以将字符串写在这段数据后.