zoukankan      html  css  js  c++  java
  • 初级堆溢出unlink漏洞

    Linux下堆的unlink漏洞

    参考文章:https://blog.csdn.net/qq_25201379/article/details/81545128

    首先介绍一下Linux的堆块结构:

    struct malloc_chunk {
    INTERNAL_SIZE_T prev_size;
    INTERNAL_SIZE_T size;
    struct malloc_chunk *fd;
    Struct malloc_chunk *bk;
    }

    0x01、其中前两个结构体成员组成了堆块的块首:1、prev_size字段仅在该堆块是空闲时有意义,代表了前一个堆块的size(包括块首的大小在内),注意国内很多相关blog中将prev解释成“后一个”堆块,这非常的别扭,此处我们将prev当作前一个。 2、size字段表示该堆块的大小,由于堆块的大小必须是8字节的整数倍,因此size字段的后三个二进制位是不表示大小的,因此作为其他标志位,我们需要知道的是,最后一位表示prev堆块是否空闲,若prev空闲,则最后一位为0。 3、至于fd和bk,也是在空闲堆块中才有意义的数据,应用在双链表中表示前后堆块(指向块首),而在in_use堆块中它们是用户数据。

    0x02、关于malloc的返回值:malloc返回的指针是用户态指针,是指向chunk_body的,不包括块首,而malloc的参数表示的size也是用户态size

    0x03、链表安全检查:Linux的堆内存管理有一个很重要的机制,会检查  p->bk->fd==p && p->fd->bk==p,我们不讨论宏中没有这个机制的漏洞利用

    0x04、漏洞利用详解:我们不先讲原理,直接看过程来体会

    (图中8byte有误,应该是16byte,图片不方便换了,聊以填坑。。)

    我们需要至少两个堆块,堆内存分布如上,此处不要引起误会,虽然每个chunk都列出了fd和bk字段,但是只是为了方便读者参考,并不代表这些堆块是空闲的

    首先malloc p堆块和引线堆块,那么如果放到空闲链表中看,p就是引线堆块的prev_chunk,但是此时两个堆块都是非空闲的。

    往下方是高地址这个不用多说,我们在p堆块中打一个溢出,踩到引线堆块,怎么踩呢?要把引线堆块的prev_size覆盖成p堆块的用户区大小,把引线堆块的size字段的最后一个二进制位弄成0,这样就造成了p堆块是free态的假象;此外在这个溢出过程中,由于写操作是从p发起的,要顺便看一下能否写到p_user的2和3个的偏移,也就是p_user[2]和p_user[3],如果能,就把它们分别覆盖成fake的fd和bk,不能的话也不慌,寻找一下有没有别的可行写操作或者可以从更低地址堆块打来溢出。

    那么fake的fd和bk应该改成多少呢?注意改了以后还要过链表安全检查的!我们来看一个巧夺天工的构造:

    fd = &p - 3*size(int); bk = &p - 2*size(int) 我们来分析一下这个小小的艺术品奇妙在哪里:

    首先我们需要清楚结构体的寻址是按偏移来寻址的,然后我们来看一下free函数的具体实现:

    FD = p->fd;
    BK = p->bk;
    FD->bk = BK;
    BK->fd = FD;

     其实就是一个很简单的双向链表拆卸,不多说;

    然后我们来看,fd = &p - 3*size(int),bk = &p - 2*size(int) ,所以FD=&p - 3*size(int) , BK=&p - 2*size(int)

    FD->bk按照偏移寻址,就是FD+3*size(int)==&p,FD->bk==p,同理 BK->fd==p,这样一来就绕过了安全检查

    检查通过就按照上述代码来free,第三行等价于p=&p - 2*size(int)

    但是第四行又把值覆盖回来了,最终执行完毕后就变成了p=&p - 3*size(int)

    回到本例中,此时如果程序free(引线堆块),那么由于检查到引线堆块的size最后一位是0,因此认为p堆块空闲,进行合并,触发p的unlink

    现在大家应该就明白“引线堆块”这个名字是怎么来的了,free相当于点火,堆块就是引线,触发prev的unlink爆炸

    还记得我们已经将引线堆块的prev_size覆盖成了prev用户区的大小,因此会造成一种假象,认为prev用户区的起始就是prev的块首起始,因此做unlink时,进行fd和bk操作的时候,fd和bk就能成功定位到之前的fake值!

    这样一来,在没有触发检查报警的情况下,成功将指针p_user劫持到了存放p_user自己的内存往上三个单位的内存处:

    (图中p为p_user)

    此时p_user=&p_user - _3

    但是,此时在受害程序视角上,堆块p并非空闲态,也就是说,此时程序可能继续以指针p_user为接口继续进行读写操作。此时,便可以为所欲为。

    这里举例一个具体操作:(p指p_user)

    比如接下来存在p堆块的写操作,原程序中正常的堆块操作是,首先写p[3],然后写p[0]

    假定我们现在已经知道了libc在内存中的映像地址,即得知了&free_got(free_plt)、system_got

    那么我们就可以在写操作中执行p[3]=&free_got,p[0]=system_got

    这两个操作分别实现的效果是,p[3]指向p即p[0],将p[0]的值即p的值改成了&free_got,之后p[0]=system_got,就相当于实现了*(&free_got)=system_got

    这样一来,在程序执行任何free操作的时候,都会被劫持到system函数,get shell

    当然有一个问题忘了提到,就是伪造fd和bk的时候,我们需要事先知道&p的值,这个估计需要具体的内存泄露,不再赘述。

    希望对各位pwn们有所裨益,有不当之处欢迎指正!

    (尊重版权,转载请注明出处,谢谢!)

     

  • 相关阅读:
    快速搭建Android 开发环境-使用ADT Bundle
    js window.print ()只打印网页图片
    数据范式的理解与解析技巧
    C#/.Net文件打包下载
    动态加载echarts数据,防止动态加载后数据叠加
    忙忙碌碌程序媛的工作小知识
    动态数据库名称的时候,使用sp_executesql
    C#的NPOI根据模板导出动态EXCEL图表,
    Android 安卓实现页面相互跳转并相互传递参数
    C#编码规范
  • 原文地址:https://www.cnblogs.com/Magpie/p/9705942.html
Copyright © 2011-2022 走看看