基础
glibc堆管理器:ptmalloc2。
堆由低地址向高地址方向增长。
如果申请了很多的堆块,不考虑释放堆块,会按申请的顺序依次从低地址到高地址排列。
堆空间申请
申请到的一块堆内存起始地址 ≠ 可以写入数据的起始地址,因为堆块头部会记录一些信息。
申请的大小 ≠ 实际申请的大小,会有一个取整的步骤。
Top Chunk / Allocated Chunk
当程序第一次进行malloc的时候,heap会被分为两块,一块给用户(allocated chunk),剩下的那块就是top chunk,再次申请堆块要是没有合适的空间便会使用top chunk的空间。
常见函数
malloc()
分配所需的内存空间,并返回一个指向它的指针。
1 | void *malloc(size_t size); |
calloc()
分配所需的内存空间,并返回一个(一组)指向它(它们)的指针。
malloc() 与 calloc() 之间的不同点是,malloc() 不会设置内存为零,而 calloc() 会设置分配的内存为零。
1 | void *calloc(size_t nitems, size_t size); |
realloc()
更改已经配置的内存空间,即更改由 malloc() 函数分配的内存空间的大小。
1 | void *realloc(void *ptr, size_t size); |
free()
释放之前调用 calloc / malloc / realloc 所分配的内存空间。
1 | void free(void *ptr); |
结构
Chunk
Allocated Chunk
- prev_size/data:临近的上一个Chunk的size或data
- size:此Chunk的size
- A(NON_MAIN_ARENA bit):是否由其他的arena管理,而非main_arena
- M(IS_MMAPPED bit):是否由mmap创出来的
- P(PREV_INUSE bit):临近的上一个Chunk是否正在使用

Free Chunk
- Free掉后的Chunk会根据Size而进到不同的Bins中
- fd:Forward Pointer,指向下一块Free的Chunk
- bk:Backward Pointer,指向上一块Free的Chunk
- 以fd/bk将各个Free Chunk串联起来

Bin
Fastbin
- Free掉 Chunk Size <= global_max_fast 的Chunk,会回收至Fastbin
- 预设 global_max_fast = 0x80
- Fastbin共有7个,分别为 [0x20,0x30,0x40,…,0x80]
- 为单向链表,LIFO,fd指向下一块Free Chunk的Chunk Header
- 例如:Free掉 Chunk Size = 0x20 的Chunk,会进到代表0x20 Fastbin的链表
- Free这类Chunk时,不会清除临近的下一块Chunk的P bit
- 在Free时,如果下一块是Top Chunk,并不会被合并进去
Tcache
- 从libc 2.26开始使用
- 为了再加速程序效率而诞生
- Tcache有许多,分别为 [0x20,0x30,0x40,…,0x410]
- 为单向链表,LIFO,fd指向下一块Free Chunk的Chunk Data
- 每个Tcache最多收7个Chunks
- Free这类Chunk时,不会清除临近的下一块Chunk的P bit
- 用结构 tcache_perthread_struct 管理 Tcache(指向此结构的指针存在于TLS中),0x291大小的Chunk
Smallbin
每个chunk的大小与其所在的bin的index关系:chunk_size=2*SIZE_SZ*index。
- 共有62个循环链表,每个链表中存储的chunk大小都一致
- 每个bin对应的链表采用FIFO的规则,同一个链表中先被释放的chunk会被分配出去
注:
在smallbin中有很大一部分大小与fastbin重合,在后期触发某些操作时,会导致分配器刷新fastbin,将chunk清理到smallbin里。
Largebin
- 共有63个bin,每个bin中chunk的大小不一致,而是处于一定区间范围内
- 分为6组,每组bin中chunk大小之间的公差一致
Unsortedbin
可以视为空闲chunk回归其所属bin之前的缓冲区。
- 处于bin数组下标1处,只有一个链表,采用FIFO的规则
- bin中的空闲chunk处于乱序状态
- 两个来源
- 当一个较大的chunk被分割成两半后,如果剩下的部分(last reminder)大于MINSIZE,就会被放到unsortedbin中(且可被多次切分);如果小于MINSIZE,就会直接返回
- 释放一个不属于fastbin的chunk,并且该chunk不和top chunk紧邻时,该chunk会被首先放到unsortedbin中;如果该chunk和top chunk紧邻,则优先和top chunk合并
Hooks
glibc < 2.34
在 malloc/free/calloc/realloc 进到主要分配算法之前,若有设定hook函数,则会先执行hook函数。
在写exploit时,是个很好的利用对象。朝hook函数中写入,就能控制执行流程。写入One Gadget就能得到shell。
UAF
全名 Use After Free。把一个指针当做参数传给free,会释放指针指向的Chunk,此时指针变成悬空指针,后续程序又从此指针写/读数据,就是UAF。
Double Free
Free同一块Chunk两次。
若成功,则链表上会出现此Chunk两次。再一次malloc,得到此Chunk,此时这个Chunk在链表中还是存在。
bin攻击
Fastbin Attack
基于fastbin机制的漏洞利用方法,利用前提:
- 存在堆溢出、UAF等能控制chunk内容的漏洞
- 漏洞发生于fastbin类型的chunk中
常见分类:
- Fastbin Double Free
- House of Spirit
- Alloc to Stack
- Arbitrary Alloc
其中,前两种主要漏洞侧重于利用free函数释放真的chunk或伪造的chunk,然后再次申请chunk进行攻击;后两种侧重于故意修改fd指针,直接利用malloc申请指定位置的chunk进行攻击。
后面几种在新版本利用比较困难,glibc加入了更多的保护。
fastbin attack存在的原因在于fastbin是使用单链表来维护释放堆块的,并且由fastbin管理的chunk即使被释放,其next_chunk的prev_inuse位也不会被清空。
House of Spirit
主要思想是改写在free状态的fast chunk的fd指针,在下一次分配时拿到目标地址的读写权。
步骤:
- 释放一个fast chunk,并且不会被tcache回收或干扰,最好禁用tcache或绕过
- fast chunk被放进了fastbin,假设此时该chunk位第一个被放进bin的chunk,那么ptmalloc为其fd赋值为零
- 利用漏洞(如UAF)改写fd指针为目标地址(如
__free_hook) - 申请一个被释放大小堆块一样的chunk
Tcache Attack
源代码中tcache机制的实现在 _int_malloc 前,导致 _int_malloc 的很多安全检查被忽略。
主要分配和释放函数:tcache_get(),tcache_put()
操作过程
- 第一次malloc时,会先malloc一块内存用来存放tcache_perthread_struct
- free内存,且size小于smallbin size时
- tcache之前会放到fastbin或者unsortedbin中
tcache后:
- 先放到对应的tcache中,直到tcache被填满(默认是7个)
- tcache被填满之后,再次free的内存和之前一样被放到fastbin或者unsortedbin中
- tcache中的chunk不会合并(不取消inuse bit)
malloc内存,且size在tcache范围内
- 先从tcache取chunk,直到tcache为空
- tcache为空后,从bin中找
- tcache为空时,如果fastbin/smallbin/unsortedbin中有size符合的chunk,会先把fastbin/smallbin/unsortedbin中的chunk放到tcache中,直到填满。之后再从tcache中取;因此chunk在bin中和tcache中的顺序会反过来
另外,在tcache中,大部分指针操作都是依靠用户指针进行的。
Tcache Dup
Unsortedbin Attack
(预留)
Largebin Attack
(预留)
off by攻击
off by one
(预留)
off by null
(预留)
_IO_FILE结构体
FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。
FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中,然后以链表的形式串联起来,但系统一开始会自动创建的三个文件即 stdin、stdout、stderr,它们是在libc上。
_IO_FILE_plus 结构体
一个定义引用:
1 | extern struct _IO_FILE_plus *_IO_list_all; |
_IO_list_all 是一个 _IO_FILE_plus 结构体定义的一个指针,它存在在符号表内。
所有 FILE 文件结构都是这样的一个结构体,结构体内部:
1 | struct _IO_FILE_plus |
1 | struct _IO_FILE { |
1 | struct _IO_jump_t |

unlink
用来将一个双向链表(只存储空闲的chunk)中的一个元素取出来,可能在以下地方使用:
malloc
从恰好大小合适的largebin中获取chunk。
注:fastbin没有使用unlink,这就是为什么漏洞会经常出现在这里的原因。
从比请求的chunk所在的bin大的bin中取出chunk。
free
后向合并,合并物理相邻低地址空闲的chunk。
前向合并,合并物理相邻高地址空闲的chunk(除top chunk)。
malloc_consolidate
后向合并,合并物理相邻低地址空闲的chunk。
前向合并,合并物理相邻高地址空闲的chunk(除top chunk)。
realloc
前向扩展,合并物理相邻高地址空闲chunk(除top chunk)。
由于unlink使用非常频繁,所以unlink被实现为一个宏。
在新版本libc中,unlink操作已经被更改为函数定义,且防护力加强。简单unlink攻击只能存在于低版本的libc中)(如libc-2.23)。
house of系列
house of force
(预留)
house of spirit
(预留)
house of einherjar
(预留)
house of roman
(预留)
house of orange
(预留)
house of lore
(预留)
house of rabbit
(预留)
house of kiwi
(预留)
house of banana
(预留)
house of apple
(预留)
house of cat
(预留)
house of emma
(预留)

