不得不说,pwn学到堆的利用这一块确实是有点难理解,断断续续理解了一个月也处于理解了与没理解的叠加态。考虑到之前一直是面向做题的学习方式,对于glibc是如何分配和管理堆的内存也是无法像栈的题目做着做着就明白栈的机制了,于是决定先弄清楚,再学利用手法。
如果内容有疑问或是错误,还请各位师傅指出。(虽然貌似没留联系方式)
堆的数据结构
首先区分下堆的概念,堆是一种在程序运行过程中动态分配的一块内存。
那么最简单的情况下就是操作系统找了一块没人要的内存,然后把内存的首地址丢给程序(假设程序申请了0x100),程序拿到了首地址就可以对内存进行读写。这样分配时是简单了,但是内存并不是无限的,当程序结束或者这块内存完成了它的使命,程序不需要它的时候,操作系统回收这块内存的时候就会出问题。仅仅有一个首地址,不知道这个地址后面多少个字节是分配的内存。

于是乎可以在分配的内存加上一点标记,表明这一块申请的内存的大小,但是程序申请了0x100的内存总不可能给它0x90的吧,所以在0x100的基础上多申请一点(比如0x110),并为了内存对齐,约定好前0x10里面存这块内存大小,而这个表示内存大小的标记称为SIZE。

如果申请两个0x100的内存,在不考虑重复利用的情况下,由于堆的内存分配机制是从操作系统一开始分配给程序的一大块内存(这样操作系统就不用程序每次malloc都分配一块内存)上切下一块足够大小的内存下来,这两块内存往往是相邻的。

但是SIZE足足分配0x10是否有些奢侈了?在64位下,0x10的内存最多可以表示0xFFFF FFFF FFFF FFF0,而绝大部分情况下的malloc完全用不到这么多,又有很多情况下malloc申请的并非0x10,0x20这样整齐的,当申请0x108这样的内存时,为多出来的0x8专门多申请个0x10又有写浪费。于是乎可以把下一块内存分配给SIZE0x10平分,多出0x8给当前这块内存。这便是chunk的空间复用。
- 对于0x101到0x108,多的0x8内存足够分配。但是对于0x109到0x110,多的0x8内存不够分配,于是多分配0x10内存。(图可表示申请三个0x99到0x108大小内存的情况)

假如图中的0x220这块内存比0x110和0x330提前完成了任务,程序为了节约资源将0x220这块内存回收了,告诉操作系统这块内存可以被别的程序重新申请(姑且这么说吧),那么0x210到0x327这之间的内存该怎么处理?已经归属于前一个chunk的0x210到0x217总不能也回收了吧,0x218到0x31F的SIZE字段和user data自然是该回收回收,0x320到0x327呢?这一块在使用时属于0x220这块内存,但是在分配时却是为了分配0x330这块内存而存在的。既然0x330用不到,0x220也不再使用,为了后续合并零碎的内存块方便,这一块内存在回收之后就存储0x210这个chunk的大小(此处为0x110),而这个表示前一块chunk大小的标记称为pre_size。

当0x330这块内存也被程序回收的时候,0x210和0x320两块相邻的chunk都可以被重新申请,但如果申请一块0x200大小的内存(算上chunk头需要0x210),这两块各自只有0x110大小的内存都不足以满足,但是合并在一起就就可以。于是为了资源的高效,就需要这两块内存会合并成一块更大的内存。但如何确保不会合并到正在使用的chunk呢?我们可以发现,因为内存对齐,chunk的SIZE字段的低四位永远是0。
于是引入三个标志位,A、M、P
- A(NON_MAIN_ARENA),记录当前
chunk是否不属于主线程,1 表示不属于,0 表示属于。 - M(IS_MAPPED),记录当前
chunk是否是由 mmap 分配的。 - P(PREV_INUSE),记录前一个
chunk块是否被分配。一般来说,堆中第一个被分配的内存块的 size 字段的 P 位都会被设置为 1,以便于防止访问前面的非法内存。当一个chunk的 size 的 P 位为 0 时,我们能通过prev_size字段来获取上一个chunk的大小以及地址。这也方便进行空闲chunk之间的合并。
就目前来说A与M似乎并不需要了解,关键先看P。当0x210chunk被回收时,它的P标志位置为0,这时不会发生合并。当0x320chunk被回收时,它的P标志位置为0,依据pre_size索引到0x210chunk,发现这块chunk也被回收了,于是两块chunk合并,仅留下一个chunk。这时便能满足0x200大小的内存申请。

总结
总的来说,简单的chunk的数据结构包括chunk头和user data。chunk头分为pre_size与size,pre_size在该chunk未被回收时空间复用为上一块chunk的user data,在该chunk被回收时记录前chunk的size。size字段存储该chunk的大小与三个标志位。

Comments NOTHING