一、什么是堆
堆是内存中一片连续的区域,CTF中glibc的题型主要集中在栈、堆、linux内核,其中堆占很大一部分比重
内存中栈区域操作区域从高地址向低地址增长,堆相反,是从低地址向高地址增长
二、堆实现及存储机制
堆是内存中一片固定内存区域,程序通过进程申请(malloc)、释放(free)堆空间,下面提到的堆都是指进程管理的堆空间
堆的glibc实现主要通过三个结构体,struct heap_info, struct malloc_state, struct malloc_chunk,
这三个结构体由arena控制管理。每个程序中arena数量都是有限的,通常通过以下原则规定:
32位系统下:
number of aerna = 2 * cores + 1
64位系统下:
number of arena = 8 * cores + 1
一个进程只有一个arena(甚至可能没有arena,更加复杂的情况在此不作讨论),主进程的arena叫做main_arena,子进程的arena叫做thread_arena
下面我们从堆内存最小单元chunk开始,分析下struct heap_info, struct malloc_state, struct malloc_chunk, 这三个结构体作用,及堆管理机制
chunk是堆内存中的最小单元,struct malloc_chunk描述了chunk结构
struct malloc_chunk {
INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free).*/
INTERNAL_SIZE_T mchunk_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;
};
上述结构体描述的是chunk在被free状态下的结构,如果chunk在使用,则只包含pre_size, size, user_data三部分
下面分chunk在使用,未被使用两部分讨论malloc_chunk结构体
当chunk1在使用时,如果与chunk1物理相邻的上一个chunk2也在使用(pre_inuse = 1),则pre_size供chunk2使用;mchunk_size指当前chunk的大小(pre_size+size+user_data),由于页映射机制,size后三位无法表示大小,引入A、M、P三位,分别表示是否是主线程(main_arena), 是否通过mmap()函数分配(is_mmapped),上一个chunk是否在使用(pre_inused);fd, bk, fd_netsize, bk_nextsize此时被user_data填充,不发挥作用
当chunk1未被使用时,pre_size表示上一个空闲chunk的大小;size表示当前chunk大小;fd指向下一个chunk的首地址(pre_size位置),bk指向上一个chunk的首地址;fd_nextsize,bk_nextsize指向largebin链表中的chunk
当我们向堆内存申请空间时(如malloc(8)),glibc需要向用户返回一块堆内存区域(即一个chunk),那么chunk是怎样被选取的呢?
那就来到struct malloc_state的作用了
struct malloc_state
{
/* Serialize access. */
__libc_lock_define (, mutex);
/* Flags (formerly in max_fast). */
int flags;
/* Set if the fastbin chunks contain recently inserted free blocks. */
/* Note this is a bool but not all targets support atomics on booleans. */
int have_fastchunks;
/* Fastbins */
mfastbinptr fastbinsY[NFASTBINS];
/* Base of the topmost chunk -- not otherwise kept in a bin */
mchunkptr top;
/* The remainder from the most recent split of a small request */
mchunkptr last_remainder;
/* Normal bins packed as described above */
mchunkptr bins[NBINS * 2 - 2];
/* Bitmap of bins */
unsigned int binmap[BINMAPSIZE];
/* Linked list */
struct malloc_state *next;
/* Linked list for free arenas. Access to this field is serialized
by free_list_lock in arena.c. */
struct malloc_state *next_free;
/* Number of threads attached to this arena. 0 if the arena is on
the free list. Access to this field is serialized by
free_list_lock in arena.c. */
INTERNAL_SIZE_T attached_threads;
/* Memory allocated from the system in this arena. */
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};
chunk通过bins数组管理,bins数组又可以分为很多类,包括fastbins,smallbins,largrbins, unsortbin等
可以看到,malloc_state结构体中对bins数组的描述有两处,fastbinsY和bins[NBINS * 2 - 2]
fastbinsY指管理fastbins的数组,fastbin中的freechunk具有如下特点:一是fastbin索引的bin头大小确定,二是fatbin只有fd指针,单向链表(所有类型的bin中也只有fastbins是单向链表),后进先出,三是pre_inuse位总是置1
bins数组管理其余三种bins链表(smallbins, largebins, unsortbin)。一个bins数组中各个bin是连在一起的,结合下图可以分析下bins数组的工作模式
bins[0]为空,bins[1]管理unsortbin的双向循环链表,只有bins[1]管理,也因此后面没有加’s’。unsortbin管理刚释放且未分类的chunk,可以视作chunk分配前的缓冲区。
bins[2]-bins[63]为62个双向循环链表,管理smallbins中的chunk,先进先出,精准匹配chunk大小,在32位下分别是16,24…,504(64位下乘2即可)。
largebins在bins数组中占据bins[64]-bins[126],63个双向循环链表,先进先出,每一个bin管理一个范围内的chunk(32位下大于504,比如504-1024达大小的由bins[64]管理)。
bins数组再上一层就是堆段管理了 ,由struct heap_info结构体负责
typedef struct _heap_info
{
mstate ar_ptr; /* Arena for this heap. */
struct _heap_info *prev; /* Previous heap. */
size_t size; /* Current size in bytes. */
size_t mprotect_size; /* Size in bytes that has been mprotected
PROT_READ|PROT_WRITE. */
/* Make sure the following data is properly aligned, particularly
that sizeof (heap_info) + 2 * SIZE_SZ is a multiple of
MALLOC_ALIGNMENT. */
char pad[-6 * SIZE_SZ & MALLOC_ALIGN_MASK];
} heap_info;
一个线程可以包含多个堆段,而多个堆段的信息就是通过该结构体来管理实现的。该结构体不是存储堆段数据,二是解释堆段管理信息。
三、堆管理
malloc
malloc一个chunk,首先在unsortedbin中遍历,如果匹配到合适大小的直接取走,否则将unsortbin中为分类的chunk全部分类到相应的bins,再到合适的bins中去取
free
chunk在free后,如果该chunk的大小和fastbin匹配,就直接放到fastbins里,或者就放unsortbin
不足之处还请指正,欢迎师傅们留言~
参考文献:
[1] csdn_董哥的黑板报,链接:
https://so.csdn.net/so/search?spm=1010.2135.3001.4498&q=%E8%91%A3%E5%93%A5%20%E5%A0%86%E6%BC%8F%E6%B4%9E&t=&u=
[2] bili_星盟师傅,链接(第10-12节):
https://www.bilibili.com/video/BV1854y1y7Ro/?p=11&spm_id_from=333.1007.top_right_bar_window_history.content.click
[3] 《CTF竞赛权威指南》第十一章 堆利用
bili_星盟师傅,链接(第10-12节):
https://www.bilibili.com/video/BV1854y1y7Ro/?p=11&spm_id_from=333.1007.top_right_bar_window_history.content.click
[3] 《CTF竞赛权威指南》第十一章 堆利用