在堆溢出的利用中,随着GLIBC的不断更新,添加了越来越多的保护,对size与presize的检测,对链表完整性的检测等。使得堆上的利用难度不断增大。
这里开始分析部分ptmalloc的实现部分(64位),为新版本GLIBC堆利用,以及设计内存池提供一些思路。
首先是祖传的_int_malloc函数。
其中av是堆的全局信息,包括被释放的堆内存,堆已分配大小,剩余空间指针,已向系统分配但未使用的堆指针等,主线程会有一份放置在libc的全局变量中(叫main_arena):bytes表示通过malloc传入的欲分配的大小。
mstate:
首先大致写出ptmalloc的堆块管理的一些机制:
我们知道malloc在分配小内存的时候不会申请多少就分配多少,而是先分配出一大块内存。因为频繁向系统申请内存是相当耗费时间的。
然后将其切下一小块递给用户。而随着分配释放的频率、大小、数量的变化,使得在维护这些不断变动、切割的内存块变得比较困难。保证效率的情况下还需要保证内存碎片尽量少。因此ptmalloc添加了若干个缓冲池(bin):
tcache bin
fast bin
unsorted bin
small bin
large bin
等。如果分配的内存过大,可能会直接考虑向系统分配,释放则也是直接归还系统。
堆块(chunk):
堆块指使用malloc真正划分出的一块内存结构。程序在使用malloc的时候指定的size以表示需要多少内存。而在free中并没有要求用户指定free的大小,那么glibc怎么知道应该释放多少内存的呢?其实这个长度的信息就在分配给用户的内存上方。malloc分配的内存并不是用户指定的大小,而比这要大一些,然后将当前分配的一些信息写入开头,随后将属于用户的那段空间返回给用户。头部的那些信息对使用者是透明的。
相关代码:
那么堆块头信息除了堆块长度包含着些什么呢?glibc为了节约空间,堆块在不同的环境下的结构信息含义是不一样的。
最简单的情况就是已分配出去时堆块头信息(64位):
假如这里分配一个0x30大小的堆空间:
presize |
size | A | M | P | |
??????? |
0x4? |
用户空间(0x30大小) |
presize:在内存地址相邻的上一个堆块是被free的情况下表示上一个堆块大小,在使用的情况下作为上一个堆块的一部分。
size:表示本堆块大小(不是用户区域大小),由于分配堆块需要按照指定内存对齐(64位16字节,32位8字节),因此堆块大小的值总是8的倍数最低为都为0,为了避免浪费。最低3位用来分别表示A、M、P标识。
A(NON_MAIN_ARENA) :表示当前堆块是否不是用主线程的堆块信息分配出来的。
M(IS_MMAPPED): 表示当前堆块是否是直接通过mmap分配出来的。(如果是,在free时检查到这个位直接使用unmap归还系统)
P(PREV_INUSE): 表示内存地址相邻上一个堆块是否在被使用中。可以暂时理解为被malloc的堆块,但实际上某些情况下free之后仍然会保留自身的INUSE位。
以及另一种情况,例如分配两个0x28的长度堆块,分别填满A与B,可以发现当第一个堆块属于在被使用时,下一个堆块的prev_size作为用户空间分给了第一个堆块。(off by one 重点)
堆块1
prevsize |
size | A | M | P | |
???????? |
0x3? |
(用户数据)AAAAAAAA |
AAAAAAAA |
AAAAAAAA |
AAAAAAAA |
堆块2
prevsize |
size | A | M | P | |
AAAAAAAA |
0x31 |
BBBBBBBB |
BBBBBBBBB |
BBBBBBBB |
BBBBBBBB |
BBBBBBBB |
0x????1 |
被释放后的堆块根据所在的bin结构有所差异。
以及两个特殊的chunk(堆块):
这两个chunk是直接由arena进行管理的。
top chunk:用于在任何bin中都找不到合适的堆块进行分配时,划分的堆块,如果这个堆块也不够,则会分配一个更大的top chunk进行切割,原先的top chunk会变成free的堆块被挂在适当的bin中。
last remainder chunk:当发生堆块的切割时,记录最近一次切割剩下的堆块,如 释放了0x1000的堆块,随后又分配了0x500,glibc会为剩下的0xa00构造成一个被释放的堆块,last remainder指针将会指向这个堆块。
本文始发于微信公众号(锋刃科技):GLIBC 2.31(ptmalloc) 堆管理器--基础知识
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论