zoukankan      html  css  js  c++  java
  • 堆的数据结构探究

    堆数据结构探究

      学习堆的过程中,涉及到的数据结构比较复杂,这些数据结构能够理清楚,堆漏洞利用也就会得心应手。个人觉得还是扎扎实实把笔记做过去比较实在。

    1.堆的最基本数据单元——chunk

      chunk是堆的最小结构单元,chunk块在被使用时和未被使用时有两种不同的状态。

      chunk块在未被使用时,previous size表示上一个空闲chunk块的大小(注意这里说的是上一个空闲chunk块的大小,如果上一个chunk块处于使用状态,这里的previous size域可以被复用),size of chunk表示该chunk块的大小。fd指针表示下一个空闲的chunk,bk指针指向上一个空闲的chunk,依靠这两个指针,可以把空闲的链表以双链表的形式放置在bins中。fd_nextchunk和bk_prechunk分别为了方便在large bins中快速地管理chunk块。在一个chunk块中,previous size,size of chunk,fd指针,bk指针是一定要有的,参考地址以Size_t大小对齐,所以32位下至少要分配16个字节来存放空闲chunk块,64位下至少要分配32个字节来存放空闲chunk块。

    struct malloc_chunk {
    
      INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */
    
      INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */
    
     
    
      struct malloc_chunk* fd;         /* double links -- used only if free. */
    
      struct malloc_chunk* bk;
    
     
    
      /* Only used for large blocks: pointer to next larger size.  */
    
      struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
    
      struct malloc_chunk* bk_nextsize;
    
    };

    32位下,分配chunk块大小计算公式:

      in use chunk=用户请求的的大小(数据域)+8 (previous chunk和size ofchunk)- 4(复用下个空闲chunk的previous),但是由于chunk块最小大小要是16个字节,所以当in use chunk小于16个字节的时候,也会分配16个字节的chunk。一个使用中的,没用被free的chunk如下所示:

      接下来介绍一下size of chunk域的P标志位。P标志位表示前一个chunk是否空闲,如果前一个chunk空闲的话,P置位为0,如果前一个chunk不空闲的话,P位置位为1。ptmalloc分配的第一个chunk块,P标志位一定置位为1,以防指针应用到其他比较危险的内存地址处,比如bss段,栈区,或者函数的hook地址处。

    2.空闲堆块的管理模块——bins

      bin的英文意思是垃圾桶的意思,在这里也很形象。bins存放的就是被释放的chunk块,如果每次free的chunk块都返还给操作系统,然后下次需要地时候再调用malloc函数进行分配,那这样做是非常消耗资源,非常低效的。所以ptmalloc就有了bins来管理被free的chunk块,下次再有需要malloc的时候,首先在bins中的chunk块中寻找有没有适合的堆块,这样一来,极大地降低了内存开销。

      ptmalloc一共维护了128个bin,这些bins中,fastbins是以单链表的形式存放的,其他的bins都是以双链表形式存放的。fastbins有这样的机制,是为了更快速地管理小堆块,小于64字节的堆块在释放之后会率先存储到fastbins中。

      如图所示,bins是一个数组,chunk块在bin上以链表的形式存放。

      我们在释放堆块的时候,是添加到bin的头部,重新申请堆块的时候,会从bin的尾部申请堆块。use after free和double free的时候就要留意一下堆块重新利用时候的顺序。

      bins重新分配堆块的时候,规则是:

      fastbins——>smallbins——>合并fastbins chunk块添加到unsorted bin中,查找unsorted bins中是否有适合的chunk——>large bins——>top chunk

     3.bins的一些管理策略

      一个链表上的chunk的大小是固定的,我们申请的堆块大小是不固定的,同时由于边界标记法,连续的空闲地址上的堆块会被合并,所以bins上堆块的合并是时有发生的。ptmalloc通过释放链表来实现free后空闲堆块的合并。

    用来释放链表的代码如下所示。

    void unlink(malloc_chunk *P, malloc_chunk *BK, malloc_chunk *FD)
    
    {
    
        FD = P->fd;
    
        BK = P->bk;
    
        FD->bk = BK;
    
        BK->fd = FD;
    
    }

      图示如上。

      以上代码中FD的bk指针不受限制,BK的fd指针不受限制,这时候攻击者操控这两个指针的话,就可能把堆块引向危险的位置,造成任意地址读写,这也就是常说的unlink漏洞,存在于较早版本的glibc中。

    要解决这个问题,应该对链表进行FD->bk,BK->fd指针进行一个检查,看看这两个指针是否指向当前chunk块。

    typedef struct malloc_chunk *mbinptr;
    
    /* addressing -- note that bin_at(0) does not exist */
    #define bin_at(m, i) 
      (mbinptr) (((char *) &((m)->bins[((i) - 1) * 2]))                  
                 - offsetof (struct malloc_chunk, fd))
    
    /* analog of ++bin */
    #define next_bin(b)  ((mbinptr) ((char *) (b) + (sizeof (mchunkptr) << 1)))
    
    /* Reminders about list directionality within bins */
    #define first(b)     ((b)->fd)
    #define last(b)      ((b)->bk)
    
    /* Take a chunk off a bin list */
    #define unlink(AV, P, BK, FD) {                                            
        FD = P->fd;                                      
        BK = P->bk;                                      
        if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                //
          malloc_printerr (check_action, "corrupted double-linked list", P, AV);    
        else {                                      
            FD->bk = BK;                                  
            BK->fd = FD;                                  
            if (!in_smallbin_range (P->size)                      
                && __builtin_expect (P->fd_nextsize != NULL, 0)) {              
            if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)          
            || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))    
              malloc_printerr (check_action,                      
                       "corrupted double-linked list (not small)",    
                       P, AV);                          
                if (FD->fd_nextsize == NULL) {                      
                    if (P->fd_nextsize == P)                      
                      FD->fd_nextsize = FD->bk_nextsize = FD;              
                    else {                                  
                        FD->fd_nextsize = P->fd_nextsize;                  
                        FD->bk_nextsize = P->bk_nextsize;                  
                        P->fd_nextsize->bk_nextsize = FD;                  
                        P->bk_nextsize->fd_nextsize = FD;                  
                      }                                  
                  } else {                                  
                    P->fd_nextsize->bk_nextsize = P->bk_nextsize;              
                    P->bk_nextsize->fd_nextsize = P->fd_nextsize;              
                  }                                      
              }                                      
          }                                          
    }

      管理bin的时候,ptmalloc采取分箱机制进行管理,针对大小不同的堆块,建立四个bins来进行管理:fastbins,unsortbin,smallbins,largebins。

      small bins中有62个bins,相邻bin的公差为2*SIZE_SZ=2*size_t。在32位平台上,最小的chunk大小为16字节。在64位平台上,最小的chunk大小为32个字节。

      large bins中一共包括63个bin,每个bin中的chunk大小不是一个固定公差的等差数列,而是分成6组,每组bin是一个固定公差的等差数列,每组的bin数量依次为32,16,8,4,2,1。公差依次为64B,512B,4096B,32768B,262144B。

    unsortedbin是也是双向循环链表,暂时存储free后的chunk,一段时间后将chunk放入对应的bin中。unsortedbin可以看成smallbins和largebins的cache。Unsorted bin可以看做small bins和large bins的cache,free后,所有的chunk在回收时都要先放到unsorted bin中,然后再分配时,如果在unsorted bin中没有合适的chunk,就会把unsorted bin中的所有chunk分别加入到所属的bin中,然后再在bin中分配合适的chunk。Bins数组中的元素bin[1]用于存储unsorted bin的chunk链表头。

      fast bins在32位平台下负责回收和管理小于64B的chunk,在64位平台下回收和分配小于128B的chunk。与其他bin不同,fastbins是单链表结构,后进先出(这一点在uaf漏洞分配堆块的过程中很重要),后被free的chunk块首先被malloc。fastbin存放较小的chunk块,避免每次申请chunk都要操作系统来进行,减小内存开销。

      暂时总结这么多,内存管理的有些机制还没有理的很清楚,后面再补充,先记录下来,加深印象,这部分内容可以去看《glibc内存管理ptmalloc源码分析》以及malloc.c的源码进行学习。

  • 相关阅读:
    数字图像-概述
    Python-python打包编译成pyd或者.so,保护代码。
    计算机组成原理-概述
    9大开源云管理平台(CMP)
    计算机组成原理-CPU-CPU知识科普:秒懂主频、核心、线程、缓存、架构详解
    svn git协同管理
    DEVTMPFS
    关于flash擦除的方法
    SQLServer强制保存
    360极速浏览器无法正确getHours
  • 原文地址:https://www.cnblogs.com/L0g4n-blog/p/12965187.html
Copyright © 2011-2022 走看看