0.防止我忘记
malloc不会自动初始化内存,calloc才会自动初始化。malloc初始化需要配合memset或者构造函数。
因此如果malloc时没有初始化内存,则可能会泄露脏数据。
1.libc2.23下的offbyone
1-1.offbyone漏洞
我们知道,对于普通的堆溢出,我们希望至少能够溢出到next chunk的fd、bk字段以修改它们来完成利用。
但是,有这么一种特殊的堆溢出,它只能够溢出一个字节,这样的堆溢出漏洞称为offbyone。看似难以利用,但是因为linux的堆管理机制ptmalloc验证的松散性,基于此的offbyone漏洞利用手段不难且好用。
1-2.漏洞产生原因
offbyone漏洞产生的原因往往是对于边界检查的不严格或是字符串操作不严谨,也不排除写入的size正好就多了一个字节的情况。
- 边界检查不严格:例如,在使用循环语句向堆块中写入数据的时候,循环的次数设置错误导致多循环了一次(这种错误新手常犯)。
- 字符串操作不严谨:例如使用strlen这类函数来获取写入的数据的长度。
1-3.漏洞利用
offbyone的利用分为两种情况,一种是溢出的那一字节为可控制的任意字节,另一种是溢出的一字节为NULL字节。
这里重点说明第一种的利用。
如果offbyone溢出字节为任意可控字节,则可以通过修改堆块的大小造成overlap,从而泄露其他的块数据,或是覆盖其他块数据。自然,能用这种方法也可以使用NULL字节溢出的方法。
那么,具体的做法又是怎样呢?
我们知道,chunk是16字节对齐的,因此,如果我们申请的是0x78或者0x98这样的堆块,那么这个堆块就会使用next chunk的prev_size字段。(因为一个prev_size字段只有当size字段的最低位为0时,即chunk被认为释放后才会被解析,否则会作为上一个chunk的user_data区。)因此,溢出的这一个字节实际上可以改变next chunk的size。
如下图:
假设存在offbyone,堆中有ABCD四个已经被分配的大小为0x70的chunk,都为使用状态。那么,我们可以利用A来修改B的size。
将A填充’A’*0x68+’\xe1’,此时的堆布局如下图:
可以看出B的size被我们修改为了0xe0,正好可以覆盖到C的末尾。因此我们成功构造了chunk overlap。
这时,我们将C给free掉,则C会进入fastbin。
我们再将B给free掉,则B+C这块区域会进入unsortedbin。
我们再申请一个大小为0xd0(实际大小为0xe0)的堆块,则B+C这块内存又被我们给控制了。此时C还在fastbin,因此我们成功创造了UAF。之后我们就可以控制C的fd字段实现fastbin attack了。
2.例子
2-1.附件
点击下载:offbyone
libc版本为2.23。
2-2.分析
老样子先查保护:
拖进IDA中进行代码审计:
可以看出在add时使用了malloc函数,并且没有初始化。因此可以利用unsortedbin leak出libc中的地址。具体做法见代码部分。
可以看出del时清空了对应的指针,因此没有现成的UAF。
漏洞存在于edit函数。它在检测当前要修改内容的堆块的内容长度时使用了strlen函数,而strlen函数是遇到\x00才停止。因此,如果我们事先用非空字符填充了申请大小为0x68(实际分配了0x70)大小的堆块,则strlen会将next chunk的size字段的低位一字节一并读取,从而造成了offbyone。这样,在使用edit函数时就可以修改next chunk的size了。
2-3.exp
完整的exp如下:
1 | from pwn import * |
在46行后下断点,查看heap,可以看出chunk 3的大小已经被修改为0xe0了:
在49行后下断点,查看bins,可以看出fake_chunk已经进入fastbin了:
运行成功获取了shell: