堆漏洞挖掘中bins的单向链表、双向链表存储结构

一、前言

  • 前面介绍过:fastbins为单链表存储。unsortedbin、smallbins、largebins都是双向循环链表存储。
  • 并且free掉的chunk,如果大小在0x20~0x80之间会直接放到fastbins上去,大于0x80的会放到unsortedbin上,然后进行整理。
  • 那么这些链表在glibc中到底是如何存储的哪,本片文章进行介绍。

二、fastbins的单向链表存储结构

  • fastbins是单向链表存储,fastbins中的的chunk是不会合并的(glibc规定这些chunk的PREV_INUSE位永远为1)。

存储结构图

  • fastbins的存储采用后进先出(LIFO)的原则:后free的chunk会被添加到先free的chunk的后面;同理,通过malloc取出chunk时是先去取最新放进去的。
  • 因此,fastbins中的所有chunk的bk是没有用到的,因为是单链表。
  • 并且fastbins比较特殊,一个fastbin链第一个chunk指向于一个特殊的“0”,然后后面接的是后free的chunk......以此类推,最后一个chunk再由arena的malloc_state的fastbinsY数组所管理。

采取后进先出(LIFO)的原因:

  • 因为在单链表中一个节点的bk成员是没有使用的。当新增/取走一个freechunk时,都是通过fastbin的bin头的fd指针所操作的。

  • 演示案例如下:

#include <unistd.h>
#include <stdlib.h>
#include <malloc.h>

int main()
{
    int size=0x10;
    int size2=0x20;
    int *p1=malloc(size);
    int *p2=malloc(size);
    int *p3=malloc(size2);

    sleep(0);  //只为了程序打断点,没有其他作用
    free(p1);
    free(p2);
    free(p3);
    return 0;
}
  • 第一步:打断点在sleep上,并且运行起来。

  • 第二步:查看当前的堆信息和bins链信息(因为还没有释放,bins链都为空)。

  • 第三步:释放p1然后再释放p2,并且查看bins信息(可以看到两个chunkfree之后直接存储在fastbins的0x20bin链上),并且p2所指的chunk的fd指针指向于前一个chunk的chunk头处,第一个chunk的fd指向的是特殊的“0”。

  • 第四步:释放p3指针(该指针放到0x30大小的fastbin上,因为该bin链上只有一个chunk,所以fd成员为0,代表前面没有chunk)。

三、unsortedbin的双向链表存储结构

  • free的chunk大小如果大于0x80会放到unsortedbin上。
  • unsortedbin存储这些chunk是使用双向循环链表进行存储的(smallbins、largebins也是如此,此处只介绍unsortedbin)。

存储结构图:

  • 存储循环先入先出(FIFO)原则:上面的是先free掉的chunk,下面是后free掉的chunk;同理,通过malloc取出chunk时是先取上面的,再取下面的。
  • 一个bins中只有一个freechunk时:就是下面这种表示形式。可以看到freechunk的fd和bk都指向于bins的fd,所以我们使用gdb调试时可以用命令看到,当bin链中只有一个freechunk时,其fd和bk都是相同的,都指向于bins的fd。

  • 一个bins中有多个freechunk时:上面使我们的struct malloc_state结构体,结构体中的bins数组存储的就是这些bin链,下面就是bins数组中存储unsortedbin的位置,这个数组元素存储的其实就是一个fd和一个bk指针。下面第一个就是第一个free掉的chunk,再下面一个就是第二个free掉的chunk......以此类推。

采取先入先出(FIFO)的原因:

  • 因为添加/取走bin链上的一个freechunk时,是通过bin头的bk指针所操作的。

  • 演示案例如下:

#include <unistd.h>
#include <stdlib.h>
#include <malloc.h>

int main()
{
    int size=0x100;
    int *p1=malloc(size);
    int *temp=malloc(size); //防止p1与p2合并
    int *p2=malloc(size);   
    int *p3=malloc(size);   //防止p2被top chunk合并

    sleep(0);
    free(p1);
    free(p2);
    return 0;
}
  • 第一步:打断点在sleep上,并运行起来。

  • 第二步:查看当前的chunk信息以及bins信息(可以看到当前的chunk都处于分配状态)。

  • 第三步:释放p1(释放p1之后,unsortedbin中有了第一个chunk,并且该chunk的前指针和后指针都指向于unsortedbin,此时循环双向链表中只有1个chunk,所以看到0x602000处的chunk的fd和bk都为unsortedbin的地址),也可以看到第2个chunk的size变为272,PREV_INUSE位变为了0,说明此chunk前面的chunk变为空闲状态。

  • 第四步:释放p2指针(此时unsortedbin中有两个chunk构成的循环双向链表)。

四、malloc_state结构体的bins数组

  • 我们知道bins数组是NBINS * 2 - 2个大小,那么为什么这么设计哪?原因如下:
    • ①NBINS是arnea中除了fastbins之外,所有的bins链表数目综合(unsortedbin+smallbins+largebins)。
    • ②因为每个bins链表在bins数组中存储的是一个fd和bk成员,所以一个bins链表在bins数组中要存储2个struct malloc_chunk指针,所以数组要乘以2,也就是NBINS*2。
    • ②bins数组的第0、1个索引不想用来存储bins链表,所以要减去2。
  • 综上所述,bins数组的大小就是NBINS*2-2。

五、附加知识

  • 从上面的图中我们可以看到下面这样的结构,我们知道这个是存储的bin链的起始地址,bin链在arnea表示的结构体的bins数组中存储,+88就代表在表示arena结构体的起始地址+88字节寻找到这个bin链的地址。

验证

  • 下面我们查看main_arena结构体的起始地址,然后再计算出某个被free掉的第一个chunk的地址,可以看到该chunk存储在距离main_arena结构体起始地址的0x58(10进制:88)的地址处。


  • 我是小董,V公众点击"笔记白嫖"解锁更多【堆漏洞挖掘】资料内容。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

董哥的黑板报

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值