ret2_dl_runtime_resolve
遇到无法leak的题目的时候..日常roputils一把梭..这次就打算从原理学习一下”Return_to_dl_resolve“这项技术。可能写的会有些杂乱
借鉴和参考学习的师傅们的文章
1 |
|
看了这么多才看懂。。实在是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
33Section 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
8typedef 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
9typedef struct
{
Elf32_Word st_name; // Symbol name(string tbl index)
Elf32_Addr st_value; // Symbol value
Elf32_Word st_size; // Symbol size
unsigned char st_info; // Symbol type and binding
unsigned char st_other; // Symbol visibility under glibc>=2.2
Elf32_Section st_shndx; // Section index
} Elf32_Sym;.dynstr节则包含了字符串,其以\x00为开头结尾
Elf32_Sym[num]->st_name=.dynsym + Elf32_Sym_size * num
而字符串的地址就在:Elf32_Sym[num]->st_name+.dynstr的地方.plt节为过程链接表
用来确定函数的绝对地址
而动态链接的过程即为:
- 第一次调用函数时,将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)
{
// 首先通过参数reloc_arg计算重定位入口,这里的JMPREL即.rel.plt,reloc_offset即reloc_arg
const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
// 然后通过reloc->r_info找到.dynsym中对应的条目
const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
// 这里还会检查reloc->r_info的最低位是不是R_386_JUMP_SLOT=7
assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
// 接着通过strtab+sym->st_name找到符号表字符串,result为libc基地址
result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL);
// value为libc基址加上要解析函数的偏移地址,也即实际地址
value = DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) + sym->st_value) : 0);
// 最后把value写入相应的GOT表条目中
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
13pwndbg> x/32x 0x80481d8
0x80481d8: 0x00000000 0x00000000 0x00000000 0x00000000
0x80481e8: 0x00000033 0x00000000 0x00000000 0x00000012
0x80481f8: 0x00000027 0x00000000 0x00000000 0x00000012
0x8048208: 0x00000052 0x00000000 0x00000000 0x00000020
0x8048218: 0x00000020 0x00000000 0x00000000 0x00000012
0x8048228: 0x0000003a 0x00000000 0x00000000 0x00000012
0x8048238: 0x0000004c 0x00000000 0x00000000 0x00000012
0x8048248: 0x0000002c 0x0804a044 0x00000004 0x001a0011
pwndbg> x/s 0x08048278 +0x33
0x80482ab: "setbuf"
pwndbg> x/s 0x08048278 +0x27
0x804829f: "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的偏移,所以我们可以将字符串写在这段数据后.
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!