这第一篇写了unlink,以前做题遇到unlink,exploit都是跟模板一样,比如说
遇见64位的题 fd=target - 0x18 bk=target - 0x10
32 位的题 fd = target -0xC bk=target-0x8
那么为什么要这样做呢,往下看。
本文主要介绍如何利用堆内存溢出进行 unlink 攻击,进而实现任意代码执行。
1 Unlink
基本知识
unlink是利用glibc malloc 的内存回收机制造成攻击的,核心就在于当两个free的堆块在物理上相邻时,会将他们合并,并将原来free的堆块在原来的链表中解链,加入新的链表中,但这样的合并是有条件的,向前或向后合并。
unlink实际上就是把一个双向链表中的空闲块拿出来,然后与相邻的free chunk进行合并(可以向前合并也可以向后合并),从而因此触发漏洞
这里需要了解一下向前合并和向后合并
以当前的chunk为基准,将preivous free chunk合并到当前chunk称为向后合并,将后面的free chunk合并到当前chunk就称为向前合并。但这里的前和后都是指在物理内存中的位置,而不是fd和bk链表所指向的堆块。
向后合并源码
1 | /* consolidate backward */ |
修改当前chunk的指针,到前一个 chunk。堆内存中的第一个申请的chunk总是被设置为allocated的,即使它根本就不存在,也不会进行向后合并操作。
向前合并源码
1 | if (nextchunk != av->top) { |
向前合并不修正P的指针,只增加size大小。
在合并后glbc malloc会将合并后新的chunk加入unsorted bin中,加入第一个可用的chunk之前,更改自己的size字段将前一个chunk标记为已用,再将后一个chunk的previous size改为当前chunk的大小。
unlink源码分析
这是一个单纯的不加保护的unlink
1 | #define unlink(P,BK,FD){ |
这里引用一个ctfwiki的图片,非常经典
unlink可以简单看作上面的代码部分,将链表中的P脱链,把之前P的下一个chunk与P的上一个chunk连接,使P离开链表。unlink 即为将 P 从链表中删除的过程。
但在较新版本的 glibc 中,为了缓解攻击者进行 unlink 攻击,在宏定义中加入了安全校验,使得利用难度加大,只能在特定条件下使用一些技巧绕过校验。
unlink的源码如下
1 | #define unlink(P, BK, FD) { \ |
其实加入的新的Unlink检查就是从双链表中去除一个节点p然后检查,p的前一个节点的下一个节点是不是p,p的后一个节点的的上一个节点是不是p。
那么我们如何绕过这层检验呢
unlink攻击利用思路
需要以下条件:
a) 程序中存在一个全局指针变量 ptr
b) ptr 指向的堆内存可由用户控制
具体步骤:
首先需要两个相邻的堆块,其中一个堆块空闲,一个堆块占用,释放占用的堆块,引发两个堆块合并。正常的空闲堆块链接在空闲链表中,我们无法控制其中的fd和bk指针,所以方法是伪造一个空闲的堆块。libc判断相邻堆块空闲的方法是通过本堆块的size字段。攻击者在ptr处伪造一个空闲的chunk p,根据ptr构造合适的地址覆盖chunk p的fd和bk
1.我们就malloc 两个堆块,大于0x80 因为小于就成了fastbin
2 伪造的是空闲堆块 然后就要设置一下 它的fd 和bk指针 下面有
3 需要注意的是 我们伪造空闲堆块 要注意 size的最后一字段为0 而且pre_size的大小要为上一字节的大小
怎么去构造伪造堆块的fd和bk
1 | P->fd = ptr - 0x18 |
在执行 unlink p 时的指针操作如下:
1 | 1)FD = P->fd = ptr - 0x18; |
我们来解读一下,为什么FD = p -> fd = ptr - 0x18
因为 FD->bk = ptr - 0x18 + 0x18 后面要加0x18
那为什么后面要加0x18呢
我们来看一下图
到这里 unlink的原理就是讲完了 我们再总结一下,unlink无非就是在堆块合并时造成的任意地址读写漏洞,unlink只是一个机制,它通常是伴随着其他漏洞出现的 比如UAF 和 off by one 造成溢出才能修改chunk里的值
2 实战深入解析 Unlink
BUU的 hitcon2014_stkof
ctrl加f 搜一下
这里也并没有开启pie 说明got可以修改
程序中专门用来存放已分配的堆块的地址,那么我们的思路就来了 通过unlink 控制指针为got表 然后修改其中一个为puts 然后泄露libc地址
值得注意的是,本例中没有调用 setvbuf 函数设置缓冲区,所以在进行输入和输出时会申请缓冲区,我们可以调试看看:
在fgets函数调用前,堆还没有被初始化,当call完之后
可以发现,程序自动申请了数个空间作为输入缓冲区,当调用输出相关函数时,同样会设置输出缓冲区,注意不要让这些缓冲区干扰我们,可以先申请一定的空间,防止干扰。
先分配一个大一些的堆块,选择0x100,起到了占位的作用,可以自己申请小堆块试试看
这里申请了0x30和0x80
由于程序没有设置缓冲区,导致在第一次调用输入、输出函数时,程序会自动申请空间作为缓冲区,将申请的第一个和第二个堆块分割,所以先申请一个堆块作为占位,防止堆块被分割,影响后续操作。
1 | # -*- coding:utf-8 -*- |
先拿了一下h哥的代码 还有几个地方不懂 有时间再看看
参考文献:
- https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/unlink-zh/
- http://0x4c43.cn/2017/1231/linux-heap-memory-overflow-unlink-attack/
- https://blog.csdn.net/qq_35493457/article/details/105857572?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.control&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.control
- https://www.bilibili.com/video/BV1s7411N7rw
- https://blog.csdn.net/seaaseesa/article/details/105406240
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1204342476@qq.com