欢迎光临散文网 会员登陆 & 注册

二进制安全之堆溢出(系列)——堆基础 & 结构(一)

2020-03-25 16:20 作者:汇智知了堂  | 我要投稿

二进制安全之堆溢出(系列)第二期来啦
鉴于本期干货够多
知了姐怕大家一时间消化不了,特意帮大家拆分成了四节内容以下为“堆基础 & 结构”第一节

堆基础
堆的概念

  • 在程序运行过程中,堆可以提供动态内存的分配,允许程序申请大小未知的内存。

  • 堆其实就是在程序虚拟地址空间的一块连续的线性区域,它由低地址向高地址生长。

  • 我们一般称管理堆的那部分程序为堆管理器。

  • 堆管理器位于用户程序和内核中间,主要负责 :

    • double free : 当p已经被释放后再次释放,造成乱七八糟的现象。

    • malloc

    • free

  • 请求堆

    • 响应用户的申请内存请求,向操作系统申请内存,然后返回给用户程序。为了保持内存管理的高效性,内核一般会预先分配很大的一块连续的内存。

  • 释放堆

    • 管理用户释放的内存。用户释放的内存并不是直接返还给操作系统,而是由堆管理器进行管理。这些释放的内存可以用来响应用户新申请的内存的请求。

堆的历史

  • Linux中早期的堆分配和回收由Doug lea实现,但它在并行处理多个线程时,会共享进程的堆内存空间。因此为了安全性,一个线程使用堆时,会进行加锁。

  • 然而,加锁会导致其他线程无法使用堆,降低了内存分配和回收的高效性。在多线程使用时,没能正确控制,也可能引起内存分配和回收的正确性。

  • Wolffram Gloger在Doug Lea的基础上进行改进使其可以支持多线程,这个堆分配器就是ptmalloc。在glibc-2.3.x之后,glibc中集成了ptmalloc2。ptmalloc2主要通过malloc/free函数来分配和释放内存块。

堆的实现

  • dlmalloc : Genral purpose allocator

  • ptmalloc2 : glibc

  • jemalloc : Freebsd and Firefox

  • tcmalloc : Google

  • libumen : Solaris

  • 主要以ptmalloc2中堆的实现为主

内存管理

  • 只有当真正访问一个地址的时候,系统才会在虚拟内存和物理页面的映射关系。

  • 所以操作系统已经给程序分配了很大的一块内存,但是这开内存其实只是虚拟内存。只有当用户使用到相应的内存时,系统才会真正分配物理内存给用户使用。

系统调用

  • malloc和free在动态申请或释放内存时,主要是调用(s)brk和mmap,unmmap函数实现的。

  • (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,两者的具体位置会有所不同。

    1. 不开启ASMR时,start_brk以及brk会指向data/bss段的结尾。

    2. 开启ASMR时,start_brk以及brk也会指向同一位置,只是这个位置是在data/bss段结尾后的随机偏移处。

    3. sbrk创建的chunk紧邻数据段

  • mmap函数机制

  1. malloc会使用mmap来创建独立的匿名映射段。

  2. 匿名映射的目的主要是可以申请以0填充的内存,并且这块内存仅被调用进程所使用,这块内存为系统随机分配。

  3. munmap用于释放内存。

  4. mmap创建的chunk紧邻libc

data/bss

  • bss段通常是指用来存放程序中未初始化的全局变量的一块内存区域。

  • data段通常是指用来存放程序中已初始化的全局变量的一块内存区域。

多线程支持

  • 在原来的dlmalloc实现中,当两个线程同时要申请内存时,只有一个线程可以进入临界区申请内存,而另外一个线程必须等待直到临界区中不再有线程。

  • 这是因为所有的线程共享一个堆。

  • 在glibc和ptmalloc实现中,支持了多线程的快速访问,在新的实现中,所有的线程共享多个堆。

堆数据结构

  • 宏观结构:包括堆的宏观信息,通过这些数据结构索引堆的基本信息

    • 宏观结构主要是堆块之间的连接

  • 微观结构:主要用于处理堆的分配与回收中的内存块

    • malloc & free

宏观结构
arena & main_arena

  • 主线程对应main_arena,管理所有堆块的结构体

  • 多线程的子线程对应arena,存在于线程的控制块plt中

不是每个线程都会有对应的arena
因为每个系统的核数有限,当线程数大于核数的二倍时,就必然有线程处于等待状态,所以没有必要为每个线程分配一个arena
32bit --> arena_num = 2 * core
64bit --> arena_num = 8 * core

  • chunk_size的倒数第三个标志位NON_MAIN_ARENA,多线程时为1,主线程为0

  • 子线程的堆和主线程的堆不一样

  • 每个线程会预分配一个堆空间

  1. 线程会从这个对空间创建top_chunk和堆块

  2. 当malloc的空间超过预分配的大小,会回到main_arena之前再次分配一个空间

  3. 如果线程的堆存在溢出,可以之前的chunk越界写堆的arena结构


  • 定位子线程的chunk的技巧

  1. 向子线程的堆块输入特殊值:"0xdeadbeef"

  2. 在gdb使用 search -4 0xdeadbeef

  3. 搜索出来的地址即堆的地址


  • 多线程利用思路

  1. 在子线程中找到堆空间的地址空间A

  2. 在A中找到恢复线程的arena的结构

  3. 通过arena的结构尝试堆利用


top_chunk

  • 当一个chunk处于一个arena的最顶部(最高内存地址)的时候,称之为top_chunk

  • 当系统当前所有的bin都无法满足用户请求的内存大小的时候,将此chunk分配给用户使用

main_arena ---> sbrk
thread arena ---> mmap

  • 如果top_chunk比用户请求的大小要大的话,就将该top_chunk分为两部分

  1. 用户请求的chunk

  2. 剩余的部分成为新的top_chunk


  • 否则需要扩展heap获分配新的heap,原来的top_chunk划入unsortedbin

  • top_chunk漏洞利用

  1. 当当前的top_chunk的空间不够的时候,系统就会新创建一个top_chunk

  2. 原来的top_chunk被分配到到unsortedbin里面

  3. 在题目中没有free函数的时候,则无法将块进入bin链

  4. off by one --> 在top_chunk之上构建一个0x88的堆块,改写top_chunk的size大小

// [漏洞学名]:house of orange
bins

  • 作用:管理free的malloc_chunk

  • 种类:按照free的chunk大小划分


  • fastbin :0x20-0x80 :注意fastbin不属于bins,是ptmalloc单独用来管理0x20-0x80的堆块的数据结构,如果free的chunk大小在0x20-0x80之间,会优先进入fashbin,

  • smallbin :0x20-0x400

  • unsortedbin : free掉的chunk优先进入unsortedbin,除了fastbin管理的堆块

    • 存在整理过程,将所有放在unsortedbin链上的堆块按照大小整理到其它链上

    • 将fastbin上的碎片整理到unsorted,再有unsorted整理到其他bin链

  • largebin :0x400以上


  • 对于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

  • 管理流程

  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。


  • 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按照最近使用顺序排列。

  • 任意两个物理相邻的空闲chunk不能在一起,否则会合并。

  • free之后的chunk,与top_chunk相邻的,会与top_chunk合并,不与之相邻的,会根据其大小进入到不同的bin

小的进入fastbin,大的进入unsortedbin
此时,释放掉的chunk不会马上归还系统,ptmalloc会统一管理heap和mmap映射区域的空闲的chunk。
当用户再一次请求分配内存时,ptmalloc分配器会试图在空闲的chunk中挑选一块合适的给用户,这样可以避免频繁的系统调用,减少内存分配的开销。

  • 需要注意的是,并不是所有的chunk被释放之后立即放到bin中。ptmalloc为了提高分配的速度,会把一些小的堆块先放到fast bin的容器内。而且fast bin容器中的chunk的使用标记总是被置为1的,所以不会自动合并。

后续内容请锁定第二期哦~~


二进制安全之堆溢出(系列)——堆基础 & 结构(一)的评论 (共 条)

分享到微博请遵守国家法律