堆相关的漏洞
1.堆介绍
- 结构示意图

- 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中所有的堆块进行合并,若依然不否,则借助系统调用来开辟新的空间进行分配,若还是不满足,则返回失败
- 将申请size按照一定的规则对齐,得到最终要分配的大小size_real
- 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 | /* |
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.堆缓冲区溢出
常规溢出
堆缓冲区溢出与栈缓冲区溢出类似
Off By One
只能溢出一子节,通常位于堆块末尾,溢出的1子节恰好能够覆盖下一堆块的size域的最低位,难以利用,一般有固定的套路
Use After Free
若堆指针在释放后未被置空,形成悬挂指针(野指针),当下次访问该指针时,依然能够访问到原始指针指向的堆内容
- 申请一段空间,并将其释放,释放后并不将指针置为空,因此这个指针仍然可以使用,把这个指针简称为p1
- 申请空间p2,由于malloc分配过程使得p2指向的空间为刚刚释放的p1指针的空间,构造恶意数据将这段内存空间布局好,既覆盖p1中的数据
- 可利用p1,一般多有一个函数指针,由于之前已经使用P2将P1中的数据覆盖了,所以此时的数据即是我们可以控制的,既存在劫持函数流的情况
Doubble Free
对指针进行多次释放.多次释放会使堆块发生重叠,前后申请的堆块可能会指向同一块内存