堆漏洞挖掘:10---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数组所管理

堆漏洞挖掘:10---bins的单向链表、双向链表存储结构_第1张图片

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

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

堆漏洞挖掘:10---bins的单向链表、双向链表存储结构_第2张图片堆漏洞挖掘:10---bins的单向链表、双向链表存储结构_第3张图片

 

演示案例

#include 
#include 
#include 

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上,并且运行起来

堆漏洞挖掘:10---bins的单向链表、双向链表存储结构_第4张图片

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

堆漏洞挖掘:10---bins的单向链表、双向链表存储结构_第5张图片堆漏洞挖掘:10---bins的单向链表、双向链表存储结构_第6张图片

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

堆漏洞挖掘:10---bins的单向链表、双向链表存储结构_第7张图片堆漏洞挖掘:10---bins的单向链表、双向链表存储结构_第8张图片堆漏洞挖掘:10---bins的单向链表、双向链表存储结构_第9张图片

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

堆漏洞挖掘:10---bins的单向链表、双向链表存储结构_第10张图片堆漏洞挖掘:10---bins的单向链表、双向链表存储结构_第11张图片

二、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

堆漏洞挖掘:10---bins的单向链表、双向链表存储结构_第12张图片

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

堆漏洞挖掘:10---bins的单向链表、双向链表存储结构_第13张图片

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

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

堆漏洞挖掘:10---bins的单向链表、双向链表存储结构_第14张图片

演示案例:

#include 
#include 
#include 

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都处于分配状态)

堆漏洞挖掘:10---bins的单向链表、双向链表存储结构_第15张图片堆漏洞挖掘:10---bins的单向链表、双向链表存储结构_第16张图片

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

堆漏洞挖掘:10---bins的单向链表、双向链表存储结构_第17张图片堆漏洞挖掘:10---bins的单向链表、双向链表存储结构_第18张图片

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

堆漏洞挖掘:10---bins的单向链表、双向链表存储结构_第19张图片堆漏洞挖掘:10---bins的单向链表、双向链表存储结构_第20张图片

三、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)的地址处

堆漏洞挖掘:10---bins的单向链表、双向链表存储结构_第21张图片

你可能感兴趣的:(堆漏洞挖掘,bins的单向链表,双向链表存储结构)