堆相关的漏洞

1.堆介绍

  • 结构示意图
    • image-20201117102955545
    • N位:define NON_MAIN_ARENA 用于表示是否属于主线程,0表示主线程的堆块结构,1表示子线程的堆块结构
    • M位:define IS_MAPPED 0X2 用于表示是否由mmap分配,0表示由堆块中的top chunk分裂产生,1表示由mmap分配
    • P位:define PREV_ISUSE 0x1 用于表示上一堆块是否处于空闲状态,0表示处于空闲状态,1表示处于使用状态。主要用于来判断free是否能够上一堆块进行合并
  • 堆空闲块管理结构bin
    • 当allocated chunk被释放以后,会放入bin或者top chunk中去。bin的作用是加快分配速度,其通过链表方式(chunk结构体中的fd和bk指针)进行管理
    • fast bin
      • 单链表结构进行组织,用fd指针指向下一堆块,采用LIFO机制
      • 它将堆块的p标志为都设为1,处于占用状态,以防止释放时fast bin进行合并,用于快速分配小内存
  • malloc基本规则
    • 将申请size按照一定的规则对齐,得到最终要分配的大小size_real
      • X86:size+4 按照0x10字节对齐
      • X64:size+8按照0x20字节对齐
    • 检查size_real 是否符合fast bin 的大小,若是则查看fast bin中对应的size_real 的那条链表中是否存在堆块,若是则分配,否则进行下一步
    • 检查size-real 是否符合small bin 的大小,若是则查看fast bin中对应的size_real 的那条链表中是否存在堆块,若是则分配,否则进行下一步
    • 检查size-real 是否符合large bin 的大小,若是则调用malloc_consolidate函数对fast bin中所有堆块进行合并,其过程为将fast bin中的堆块取出,清除下一块的p标识位并进行堆块合并,将最终的堆块放入unsorted bin。然后在small bin 和large bin中找到合适size_real大小的块。若找到则分配,并将多余的部分放入unsorted bin ,否则下一步
    • 检查top chunk的大小是否符合size_real的大小,若是则分配前面一部分,并重新设置top chunk,否则调用malloc_consolidate函数对fast bin中所有的堆块进行合并,若依然不否,则借助系统调用来开辟新的空间进行分配,若还是不满足,则返回失败
  • free 基本规则
    • 首先会检查地址是否对齐,并根据size找到下一块的位置,检查其p标识位是否为1
    • 检查释放块的size是否符合fastbin的大小区间,若是则直接放入fast bin,并保持下一堆块中的p标识位为1不变(这样可以避免在前后块释放时进行堆块的合并,以方便快速分配小内存),否则进行下一步
    • 若本堆块size域中的p标识位为0(前一堆块处于释放状态)则利用本快的pre_size找到前一堆块的开头,将其从bin链表中摘除(unlink),并合并这两个块,得到新的释放块
    • 根据size找到下一堆块,如果是top chunk,则直接合并到top chunk中,直接返回.否则检查最后一堆块是否处于释放状态(通过检查下一堆块的p标识位是否为0).将其从bin链表中摘除(unlink),并合并这两块,得到新的释放块.
    • 将上述合并得到的最终堆块放入unstorted bin中去
  • tcache
    • 作用
      • 提高堆的使用效率
    • 注意点
      • tcache的管理是单链表,采用LIFO原则
      • tcache的管理结构存在于堆中,默认有64个entry,每个entry最多存放7个chunk
      • tcache的next指针指向chunk的数据区
      • tcache的某个entry被占满以后,符合该entry大小的chunk被free后的规则和原有机制相同

2. 相关的数据结构

微观结构

malloc_chunk

结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
This struct declaration is misleading (but accurate and necessary).
It declares a "view" into memory allowing access to necessary
fields at known offsets from a given base. See explanation below.
*/
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;
};
Bin
  • ptmalloc 根据chunk的大小以及使用状态将chunk分为4类:fast bin, small bin, large bin, unstorted bin

  • 一个bin相当于一个chunk链表

  • fast bin 采用LIFO策略,支持的最大的chunk的数据空间大小为64字节,最大支持的bin的个数位10,inuse位始终置为1,防止被合并
  • small bins中每个chunk的大小与其所在bin的index的关系为:chunk_size = 2*size_sz*index
Top Chunk

程序第一次进行malloc的时候,heap会被分成两块,一块给用户,剩下的那块就是Top Chunk(处于当前堆的物理地址最高的chunk,不属于任何bin),它的作用是在当前所有bin都无法满足用户的请求大小时,如果其满足大小,就进行分配,将剩下的部分作为新的TopChunk.否则对heap进行扩展后再进行分配.

宏观结构

heap_info

​ 程序刚开始执行时是没有heap区域的,当其申请内存时,就需要一个结构记录对应的信息,而且一般当前的heap资源被用完后,重新申请的heap一般是不连续的,因此需要记录不同heap之间连接结构,而heap的作用就是这个

malloc_state

该结构用于管理堆,记录每个arena当前申请内存的具体状态,比如是否有空闲的chunk等,他是一全局变量存储在libc.so中

3.深入理解堆的实现

宏观角度

- 创建堆
- 堆初始化
- 删除堆

微观角度

  • 申请内存块
  • 释放内存块

3.相关的漏洞

1. 最基本的堆漏洞

  • 由于对堆内容类型判断不明而形成的错误引用,通常情况下,可以使用堆块存储复杂的结构体,其中可能会包括函数指针、变量、数组等成员.如果一个结构体数据按照其他结构体格式来解析,那么只要在特定的域布置好数据,就会导致漏洞的发生.

2.堆缓冲区溢出

  1. 常规溢出

    堆缓冲区溢出与栈缓冲区溢出类似

  2. Off By One

    只能溢出一子节,通常位于堆块末尾,溢出的1子节恰好能够覆盖下一堆块的size域的最低位,难以利用,一般有固定的套路

  3. Use After Free

    若堆指针在释放后未被置空,形成悬挂指针(野指针),当下次访问该指针时,依然能够访问到原始指针指向的堆内容

    1. 申请一段空间,并将其释放,释放后并不将指针置为空,因此这个指针仍然可以使用,把这个指针简称为p1
    2. 申请空间p2,由于malloc分配过程使得p2指向的空间为刚刚释放的p1指针的空间,构造恶意数据将这段内存空间布局好,既覆盖p1中的数据
    3. 可利用p1,一般多有一个函数指针,由于之前已经使用P2将P1中的数据覆盖了,所以此时的数据即是我们可以控制的,既存在劫持函数流的情况
  4. Doubble Free

    对指针进行多次释放.多次释放会使堆块发生重叠,前后申请的堆块可能会指向同一块内存