二进制安全之堆溢出(系列)第二期来啦
鉴于本期干货够多
知了姐怕大家一时间消化不了,
特意帮大家拆分成了四节内容
以下为“堆基础 & 结构”第一节
l 在程序运行过程中,堆可以提供动态内存的分配,允许程序申请大小未知的内存。
l 堆其实就是在程序虚拟地址空间的一块连续的线性区域,它由低地址向高地址生长。
l 我们一般称管理堆的那部分程序为堆管理器。
l 堆管理器位于用户程序和内核中间,主要负责 :
1. double free : 当p已经被释放后再次释放,造成乱七八糟的现象。
2. malloc
3. free
l 请求堆
1. 响应用户的申请内存请求,向操作系统申请内存,然后返回给用户程序。为了保持内存管理的高效性,内核一般会预先分配很大的一块连续的内存。
l 释放堆
1. 管理用户释放的内存。用户释放的内存并不是直接返还给操作系统,而是由堆管理器进行管理。这些释放的内存可以用来响应用户新申请的内存的请求。
l Linux中早期的堆分配和回收由Doug lea实现,但它在并行处理多个线程时,会共享进程的堆内存空间。因此为了安全性,一个线程使用堆时,会进行加锁。
l 然而,加锁会导致其他线程无法使用堆,降低了内存分配和回收的高效性。在多线程使用时,没能正确控制,也可能引起内存分配和回收的正确性。
l Wolffram Gloger在Doug Lea的基础上进行改进使其可以支持多线程,这个堆分配器就是ptmalloc。在glibc-2.3.x之后,glibc中集成了ptmalloc2。ptmalloc2主要通过malloc/free函数来分配和释放内存块。
l dlmalloc : Genral purpose allocator
l ptmalloc2 : glibc
l jemalloc : Freebsd and Firefox
l tcmalloc : Google
l libumen : Solaris
l 主要以ptmalloc2中堆的实现为主
l 只有当真正访问一个地址的时候,系统才会在虚拟内存和物理页面的映射关系。
l 所以操作系统已经给程序分配了很大的一块内存,但是这开内存其实只是虚拟内存。只有当用户使用到相应的内存时,系统才会真正分配物理内存给用户使用。
l malloc和free在动态申请或释放内存时,主要是调用(s)brk和mmap,unmmap函数实现的。
l (s)brk函数机制
# include <stdio.h> # include <unistd.h> # icclude <sys/types.h> int main() { void *cuur_bkr,*tmp_brk = NULL; printf("%d\n",getid()); tm_brk = curr_brk = sbrk(0);//给当前程序一个brk printf("%p\n",curr_brk); getchar(); brk(curr_brk+4096);//设置结尾位置,即分配了4096字节的堆块 curr_brk=sbrk(0); printf("%p\n",curr_brk); getchar(); brk(tmp_brk); curr_brk=sbrk(0); printf("%p\n",curr_brk); getchar(); return 0; }
1. 初始时,堆的起始地址start_brk以及堆的当前末尾brk指向同一地址。根据是否开启ALSR,两者的具体位置会有所不同。
2. 不开启ASMR时,start_brk以及brk会指向data/bss段的结尾。
3. 开启ASMR时,start_brk以及brk也会指向同一位置,只是这个位置是在data/bss段结尾后的随机偏移处。
4. sbrk创建的chunk紧邻数据段
l mmap函数机制
1. malloc会使用mmap来创建独立的匿名映射段。
2. 匿名映射的目的主要是可以申请以0填充的内存,并且这块内存仅被调用进程所使用,这块内存为系统随机分配。
3. munmap用于释放内存。
4. mmap创建的chunk紧邻libc
l bss段通常是指用来存放程序中未初始化的全局变量的一块内存区域。
l data段通常是指用来存放程序中已初始化的全局变量的一块内存区域。
l 在原来的dlmalloc实现中,当两个线程同时要申请内存时,只有一个线程可以进入临界区申请内存,而另外一个线程必须等待直到临界区中不再有线程。
l 这是因为所有的线程共享一个堆。
l 在glibc和ptmalloc实现中,支持了多线程的快速访问,在新的实现中,所有的线程共享多个堆。
l 宏观结构:包括堆的宏观信息,通过这些数据结构索引堆的基本信息
l 宏观结构主要是堆块之间的连接
l 微观结构:主要用于处理堆的分配与回收中的内存块
l malloc & free
l 主线程对应main_arena,管理所有堆块的结构体
l 多线程的子线程对应arena,存在于线程的控制块plt中
不是每个线程都会有对应的arena
因为每个系统的核数有限,当线程数大于核数的二倍时,就必然有线程处于等待状态,所以没有必要为每个线程分配一个arena
32bit --> arena_num = 2 * core
64bit --> arena_num = 8 * core
l chunk_size的倒数第三个标志位NON_MAIN_ARENA,多线程时为1,主线程为0
l 子线程的堆和主线程的堆不一样
l 每个线程会预分配一个堆空间
1. 线程会从这个对空间创建top_chunk和堆块
2. 当malloc的空间超过预分配的大小,会回到main_arena之前再次分配一个空间
3. 如果线程的堆存在溢出,可以之前的chunk越界写堆的arena结构
l 定位子线程的chunk的技巧
1. 向子线程的堆块输入特殊值:"0xdeadbeef"
2. 在gdb使用 search -4 0xdeadbeef
3. 搜索出来的地址即堆的地址
l 多线程利用思路
1. 在子线程中找到堆空间的地址空间A
2. 在A中找到恢复线程的arena的结构
3. 通过arena的结构尝试堆利用
l 当一个chunk处于一个arena的最顶部(最高内存地址)的时候,称之为top_chunk
l 当系统当前所有的bin都无法满足用户请求的内存大小的时候,将此chunk分配给用户使用
main_arena ---> sbrk
thread arena ---> mmap
l 如果top_chunk比用户请求的大小要大的话,就将该top_chunk分为两部分
1. 用户请求的chunk
2. 剩余的部分成为新的top_chunk
l 否则需要扩展heap获分配新的heap,原来的top_chunk划入unsortedbin
l top_chunk漏洞利用
1. 当当前的top_chunk的空间不够的时候,系统就会新创建一个top_chunk
2. 原来的top_chunk被分配到到unsortedbin里面
3. 在题目中没有free函数的时候,则无法将块进入bin链
4. off by one --> 在top_chunk之上构建一个0x88的堆块,改写top_chunk的size大小
5. // [漏洞学名]:house of orange
l 作用:管理free的malloc_chunk
l 种类:按照free的chunk大小划分
u fastbin :0x20-0x80 :注意fastbin不属于bins,是ptmalloc单独用来管理0x20-0x80的堆块的数据结构,如果free的chunk大小在0x20-0x80之间,会优先进入fashbin,
u smallbin :0x20-0x400
u unsortedbin : free掉的chunk优先进入unsortedbin,除了fastbin管理的堆块
² 存在整理过程,将所有放在unsortedbin链上的堆块按照大小整理到其它链上
² 将fastbin上的碎片整理到unsorted,再有unsorted整理到其他bin链
u largebin :0x400以上
l 对于small bins,large bins,unsorted bins来说,ptmalloc将它们维护在同一个数组中,对应的数据结构在malloc_state中
#define NBINS 128 // bins总共有128个,除了fastbin
mchunkptr bins[NBINS * 2 - 2] //mchunkptr 是指向chunk头的指针,bin = fd+bk
l 管理流程
1. malloc/free --> glibc --> arena --> fastbin/bins -->smallbin/largebin/unsortedbin
2. 从glibc找到main_arena
3. 在main_arena的管理结构体malloc_state通过固定偏移中找到fastbinsY[NFASTBINS],用以管理fastbin。
4. 找到bins[NBINS * 2 - 2],用以管理unsortedbin。
l bin的放置顺序
索引为1的是unsortedbin,这里面的chunk没有进行排序,比较杂乱。
索引从2到63的bin称为small bin,同一个small bin链表中的chunk的大小相同。两个相邻索引的small bin链表中的chunk大小为2个机器字节,即32-->4字节,64-->8字节。
索引从64到126的bin被称为large bin。large bins中的每一个bin都包含一定范围内的chunk,其中的chunk按fd指针的顺序从大到小排列,最靠近bin头的越大,相同大小的chunk按照最近使用顺序排列。
l 任意两个物理相邻的空闲chunk不能在一起,否则会合并。
l free之后的chunk,与top_chunk相邻的,会与top_chunk合并,不与之相邻的,会根据其大小进入到不同的bin
小的进入fastbin,大的进入unsortedbin
此时,释放掉的chunk不会马上归还系统,ptmalloc会统一管理heap和mmap映射区域的空闲的chunk。
当用户再一次请求分配内存时,ptmalloc分配器会试图在空闲的chunk中挑选一块合适的给用户,这样可以避免频繁的系统调用,减少内存分配的开销。
l 需要注意的是,并不是所有的chunk被释放之后立即放到bin中。ptmalloc为了提高分配的速度,会把一些小的堆块先放到fast bin的容器内。而且fast bin容器中的chunk的使用标记总是被置为1的,所以不会自动合并。
【特别说明】
信安干货都已为大家整理到专栏处啦,小伙伴们直接到菜单栏处领取哟!
-End-
知了堂信安就业班火热报名中!!
加知了小姐姐微信:ChillFun-Y抢占免费试听名额!