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

知了姐2019-08-08802

二进制安全之堆溢出(系列)第二期来啦

鉴于本期干货够多

知了姐怕大家一时间消化不了,

特意帮大家拆分成了四节内容

以下为“堆基础 & 结构”第一节

堆基础

堆的概念

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中集成了ptmalloc2ptmalloc2主要通过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)brkmmap,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

data/bss

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

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

多线程支持

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

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

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

堆数据结构

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

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

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

l  malloc & free

宏观结构

arena & main_arena

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的结构尝试堆利用


top_chunk


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_chunksize大小

5.  // [漏洞学名]house of orange

bins

l  作用:管理freemalloc_chunk

l  种类:按照freechunk大小划分

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

u  smallbin :0x20-0x400  

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

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

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

u  largebin :0x400以上

l  对于small binslarge binsunsorted 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没有进行排序,比较杂乱。

索引从263bin称为small bin,同一个small bin链表中的chunk的大小相同。两个相邻索引的small bin链表中的chunk大小为2个机器字节,即32-->4字节,64-->8字节。

索引从64126bin被称为large binlarge bins中的每一个bin都包含一定范围内的chunk,其中的chunkfd指针的顺序从大到小排列,最靠近bin头的越大,相同大小的chunk按照最近使用顺序排列。


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

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

小的进入fastbin,大的进入unsortedbin

此时,释放掉的chunk不会马上归还系统,ptmalloc会统一管理heapmmap映射区域的空闲的chunk

当用户再一次请求分配内存时,ptmalloc分配器会试图在空闲的chunk中挑选一块合适的给用户,这样可以避免频繁的系统调用,减少内存分配的开销。


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


【特别说明】

信安干货都已为大家整理到专栏处啦,小伙伴们直接到菜单栏处领取哟!



-End-

知了堂信安就业班火热报名中!!

加知了小姐姐微信:ChillFun-Y抢占免费试听名额!



  • 培训费用

  • 上课方式

  • 开班时间

  • 就业情况