首先,检查一下程序的保护机制,保护全开
然后用IDA分析,发现在创建并读入数据时,有一个null off by one漏洞
我们所能利用的也就是这个漏洞
第一步仍然是想办法泄露一些关键地址
由于可以溢出一字节的0数据到下一个chunk,以前,我们阅读glibc内存管理,知道了**chunk空间的共用情况,也就是[下一个的chunk的prev_size域给当前chunk当做数据域使用]{.mark},这种情况只出现在malloc的大小为8的奇数倍(32为4的奇数倍)的情况。考虑到unsorted bin的表头会有libc中的[main_arena+88的地址,]{.mark}**因此我们首先肯定得创建一些unsorted bin。
现在,假如我们创建了几个堆
1 | #chunk0 |
堆布局如下

其中,chunk0、chunk1、chunk4大小在unsorted bin范围,当他们free后就进入了unsorted bin,并且相邻的chunk会进行unlink合并。既然能够溢出一字节0数据,那么**[假如我们从chunk3中溢出,覆盖chunk4的size域的低一字节为0,那么就标志了chunk4的前一chunk处于空闲状态]{.mark},那么,[当我们free chunk4的时候,chunk4就会与它的前一chunk合并]{.mark}**,而它的前一chunk是如何取到的呢,看看glibc源码
1 | /* Ptr to previous physical malloc_chunk. Only valid if !prev_inuse (P). */ |
也就是依赖于prev_size,由于chunk3的大小为0x68,是8的奇数倍,因此它会把chunk4的prev_size域作为数据域,因此,prev_size我们可以自己指定大小
假如,我们在chunk3中,[把chunk4的prev_size设置为(0x110+0x110+0x70+0x70 = 0x300),然后覆盖chunk4的size低1字节为0]{.mark},那么,当我们delete(4)时,就会合并chunk4和chunk4-0x300处的chunk,也就是会合并chunk0、chunk1、chunk2、chunk3、chunk4
由于**[没有编辑功能,我们只能delete(3)后再重新create分配到那个位置,同时构造payload溢出到chunk4]{.mark}**
由于合并时,unlink会报错,因此,我们在事先,应该delete(0),让chunk0加入unsorted bin中。delete(2)用于将chunk2放入fastbin,供后续fastbin attack使用,[注意,必需在合并chunk0、chunk1、chunk2、chunk3、chunk4之前让chunk2加入fastbin]{.mark},合并后再delete(2)不能让chunk2加入到fastbin中
1 | #chunk2用于放入fastbin |
现在堆的布局是这样子的

接下来,我们delete(4),chunk0~chunk4发生合并,合并后chunk0作为unsorted bin表头fd、bk值仍然指向main_arena+88
然而,当我们实际操作时,delete(4)时出现了报错
我们看看glibc源码
还记得chunk4的size被我们覆盖成了0x100吗,原本是0x111的,也就是说,[现在取到的nextsize是chunk4数据域里偏移0x90+8处的数据,而不是下一个chunk的size,]{.mark}
为了避免后续的其他类似的错误,我们把chunk4留下的那(0x110-0x100=0x10的空间伪装成一个chunk5)
所以,在一开始创建chunk4的应该应该这样写
1 | #chunk4的创建,后0x10空间用于伪装出一个假chunk5 |
现在,我们再查看堆的布局情况(pwndbg attach 到pid上,然后输入bin命令)
现在发现,fastbin里的那个就是我们的chunk2,待会fastbin attack用到
而unsorted bin里就一个节点(chunk0),中间那个是main_arena+88,它们组成了双向链表
现在,假如我们create(0x100),那么glibc就会从unsorted bin中的表头(chunk0)处开始,切割出0x110的空间给我们,然后表头变成了chunk0+0x110
并且chunk0+0x110处开始作为一个chunk,[它的fd和bk会被设置为main_arena+88]{.mark},
但是,你发现了什么,chunk0 + 0x110不就是chunk1吗,还记得[chunk1并没有被我们free,它只是参与了合并,]{.mark}因此它的指针存在于数组中,并没有被清0
那么,当我们show()的时候,就会把chunk1的fd值打印出来,从而泄露了main_arena+88的地址
[由于main_arena+88和malloc_hook物理位置上在同一页,并且靠的很近,因此,它们的地址只有后三位不一样]{.mark},那么我们就能计算出malloc_hook的地址,然后就能计算出libc基地址,从而获取gadget的地址
1 | #申请掉chunk0后,main_area+88指针放到了chunk1的fd和bk处 |
现在一些需要的信息我们都得到了,我们接下来是想办法把gadget的地址写入到malloc_hook里,这样当程序再次malloc时,便会触发gadget,从而get shell
现在我们用fastbin attack,fastbin attack能让我们把堆申请到malloc_hook-0x23处,知道我们的chunk2为什么申请时设置为0x68的大小吗,因为实际创建的大小是0x70,而malloc_hook-0x23处偏移0x8处的数据为0x7F,与0x70大小相当
fastbin只检查chunk的size域是否符合要求,因此,我们的chunk2的size要与它大小相当,这样,我们想办法把chunk2的fd指向malloc_hook-0x23这个假chunk处,这样,chunk2和malloc_hook-0x23处构成了单向链表,当我们第二次申请0x68大小的堆时,就会申请到malloc_hook-0x23处,更多详细知识见我的博客https://blog.csdn.net/seaaseesa/article/details/103057937
那么如何才能修改到chunk2的fd域呢?
我们可以利用**[堆重叠]{.mark}**
假如我们create(0x118,payload),由于之前已经create(0x100)过一次,那么这次chunk分配的范围就是chunk0 + 0x110 ~ chunk0+0x110+0x128也就是chunk1~chunk2+0x18处,正好可以把chunk2的fd给覆盖了,如果觉得麻烦,直接申请大点,不用这么精确
1 | #现在用fastbin attack |
现在,我们再看看堆的布局
malloc_hook-0x23处成功链入fastbin,那么当我们第二次申请0x68大小空间时就能申请到这里
1 | #第一次申请 |
这样,看似已经可以了,我们只需再触发一次malloc,即可getshell
1 | #触发malloc_hook getshell |
然而,gadget变得不可用,执行不成功
[以下两段来自看雪论坛]{.mark}https://bbs.pediy.com/thread-246786.htm
[有些情况下one_gadget因为栈环境原因全部都不可用,这时可以通过realloc_hook来调整堆栈环境使one_gadget可用。realloc函数在函数起始会检查realloc_hook的值是否为0,不为0则跳转至realloc_hook指向地址。realloc_hook同malloc_hook相邻,故可通过fastbin attack一同修改两个值。]{.mark}
如何利用realloc_hook来调整栈环境呢?
观察realloc函数的流程push寄存器,最后全部pop出来跳转至realloc_hook的值。
[因此可以将realloc_hook设置为选择好的one_gadget,将malloc_hook设置为realloc函数开头某一push寄存器处。push和pop的次数是一致的,若push次数减少则会压低堆栈,改变栈环境。这时one_gadget就会可以使用。具体要压低栈多少要根据环境决定,这里我们可以进行小于48字节内或72字节的堆栈调整。]{.mark}
经过测试,我们只需在realloc函数地址向下偏移2就可以使栈环境正常
于是,我们修改后的代码
1 | #修改realloc_hook和malloc_hook |
最终,我们写出了如下的exp脚本
1 | #coding:utf8 |