首先,介绍一下malloc_hook,这是C语言提供的一个用来包装malloc的,类似还有free_hook,具体可以查阅相关资料学习
Malloc hook
我们看如下代码
1 |
|
只要我们把 __malloc_hook赋值为某个函数的地址,那么,当我们malloc时,系统就会去调用那个函数。
Unsorted bin unlink
Unsorted bin使用双向链表维护,假如在物理地址上,chunk0后面是chunk1,我们通过chunk0溢出到chunk1,并且还在chunk0里面伪造了一个chunk,其中fd和bk指向了我们的目标附近处,溢出修改chunk1的prev_size和size,让假的chunk伪装成free的状态,这样,当我们释放chunk1时就触发了unlink,使得chunk0的指针指向了我们的目标处,接下来,我们编辑chunk0,就是编辑目标处。关于unlink的详细知识,请看我之前的一篇博客https://blog.csdn.net/seaaseesa/article/details/102907138 。
本题,当我们free一个unsorted bin时,它的fd指针会指向libc中main_arena+88地址处,而该处的上方不远处,就是__malloc_hook
我们先创建一个256字节的堆,然后释放它,释放后的堆块数据结构是这样的,fd和bk设置成了main_arena+88的值
__malloc_hook
main_arena+88处
我们的目的是修改__malloc_hook,让它指向指向我们的shellcode地址,这样,当我们malloc时,就会触发我们的shellcode执行。
fastbin attack
为了实现修改__malloc_hook处的数据,我们可以利用fastbin将堆块分配到此处,这样我们就能改写此处的数据了。
[Fastbin只检查它单向链表中每个块的size域是否符合条件]{.mark}。我们可以在__malloc_hook附近找一个可以伪造的块,想办法把这个块加入到fastbin的链表中,这样,当我们malloc分配时,就可以分配到这个块,这样我们就能修改__malloc_hook的数据了。
我们发现,__malloc_hook上方不远处有一个符合条件的区域
假如,我们的块从图中的0x 7F535F744AED处开始,那么0x 7F535F744AED + 8 = 7F535F744AF5正好是块的size域,由于size域是8字节空间,图中,它后面都是0,因此它的值就是7F,由于fastbin在malloc时,只检查size域,因此,我们可以把0x 7F535F744AED(注意这个地址是随机的,但是**[它位于__malloc_hook – 0x23处]{.mark}**,因此,我们可以来爆破,看下文partial write技术)链接到fastbin里,这样,我们重新分配时,就可以分配到这里。
partial write
前面的演示,我们知道了unsorted bin里最后一个块的fd会残留libc中main_arena+88的地址。(这里开启了另一个程序实例,所有里面的地址变了,不要在意)
由于内存的页载入机制,PIE的随机化只能影响到单个内存页。一般一个内存页大小为0x1000,这就意味着不管地址怎么变,某条指令的后3个十六进制数字的是始终不变的,与它在二进制文件中的地址后3个十六进制是一样的,从图中,我们可以看出,main_arena在libc2.23中的地址后三个数是B78。
我们验证一下,以malloc_hook的地址为例,我们看到,它的静态地址是这样的
后三个数数B10
我们再看看它加载的地址
也是B10
也就是说,在同一个页里,就后2字节不一样,由于main_arena+ 88和__malloc_hook-0x23在同一个页里,现在我们已知后三个十六进制数字,那么我们只需爆破倒数第四个数字,即可得到__malloc_hook-0x23的地址。其实也不用爆破,我们就让倒数第4个数字固定,我们就假如它是2吧(随意),只要我们多次运行程序,总有一次地址正好吻合。
attack_addr_suffix = 4096 * 2 + ((malloc_hook & 0xFFF) - 0x23)
那么,我们需要把上面这个2字节覆盖到那个unsorted bin块的fd的低二字节,由于采用的是小端存储,正好可以覆盖,并且保留前面的字节数据。
如图,通过前一个块的溢出,我们修改了数据,我们跳过去看看
遗憾的是,这个地址不是我们的目标处,由于我们假定倒数的第四个数字是2,当前这一次,不走运,地址没有和假定吻合。但是,当我们不断重新运行程序,总有一次会吻合。那个时候我们就可以把__malloc_hook指向我们的shellcode,然后进行一次malloc操作即可getshell。
好了,那我们正式解题吧
先看看保护机制(这一步应该放在最前面的,因为我们需要依据这个想办法)
RELRO 完全开启,意味着我们不能修改GOT表。NX和PIE未开启,意味着我们可以在堆栈里存入shellcode,然后跳到那里去执行shellcode。
先看看程序逻辑(delete功能没有把指针清空)
编辑功能存在UAF以及溢出漏洞,size未做检查,可以溢出堆,UAF也可以编辑堆,真是漏洞百出
我们决定把shellcode存到bss段0x601020处,因为没有开启PIE,它的地址固定
我们如何向这里写入数据呢?
我们需要先利用unsorted bin发生unlink,控制保存堆指针的数组。
1 | #chunk0 |
不明白unlink的可以看我之前的博客里有详解https://blog.csdn.net/seaaseesa/article/details/102907138
发生了unlink,数组里,堆0的指针变成了0x601028,那么我们可以构造payload,从0x601028处溢出到下面,把数组里的几个指针全部修改成其他我们需要的地方,这样当我们编辑对应的堆时,就是编辑这些地址处的数据。比如,现在我们edit(0)就是在编辑0x601028处的数据。
1 | payload = p64(0) * 3 |
接下来,我们要开始来实现如何修改malloc_hook了。
1 | #chunk2 |
chunk2用于溢出到chunk3,对chunk3进行修改。
我们要想实现将malloc_hook-0x23处链接到unsorted bin的链表中去,现在,假设我们覆盖chunk3的fd低2字节后,地址吻合了,那么malloc_hook-0x23已经连接到了chunk3的fd,那么我们就需要把chunk3加入到单向链表中去即可,那么我们需要通过chunk2溢出到chunk3修改chunk3的size域,使它在fastbin范围内,这里,我们就改成0x71吧
1 | payload = 'c'*0x10 |
现在还有问题是,我们又不知道chunk3的地址,那么我们还需要再利用一次partial write。
我们先释放chunk4、chunk5,这样,形成了以chunk5为头结点的fastbin单向链表,由于chunk4和chunk3在内存中是相邻的,并且他们的地址只有最后一字节不一样,由于最后一字节我们是可以得到的,只需用IDA观察即可
Chunk3
Chunk4
Chunk5的fd域
我们发现,chunk3和chunk4的地址只有最后一个字节的差别,我们可以利用UAF漏洞,把chunk5的fd的低1字节覆盖成0x30,这样,chunk5的fd就指向了chunk3
1 | #释放chunk4、chunk5,这样chunk5、chunk4构成了fastbin单向链表 |
这样操作以后,形成了这样的一个fastbin链表
[Chunk5➡ chunk3 ➡ __malloc_hook-0x23]{.mark}
因此我们malloc三次后,就分配到了__malloc_hook-0x23处,我们就可以修改__malloc_hook了
由于程序会检测数组里指针的个数,超过10个就不给创建了,因此我们在bss段写入shellcode时,顺便把数组里覆盖为0
1 | #在bss段写入shellcode,顺便把堆指针从数组里清空,这样我们又创建多个堆了,不然超过10个就不允许创建了 |
我们最终完整的exp脚本
1 | #coding:utf8 |
成功getshell