想要了解堆的机制利用方法必须要先了解堆的基本机制以及结构
目前主要使用的内存管理库是ptmalloc,而在ptmalloc中,用户请求的空间由名为chunk的数据结构表示
下面就是一个标准的chunk结构
该chunk中,**prev_size参数为前一chunk(如果未被使用)的大小,size参数为该chunk的大小,而P参数(pre_insue)为标志位,标志前一个chunk的使用情况。**而上述的三个参数组成了chunk的header部分,该部分一般不会被用户直接访问
用户能够访问的空间为mem部分,如果一个chunk正被使用,则data部分为用户储存内容的空间,此时fd、bk指针并无实际意义。如果一个chunk未被使用,则mem部分的fd与bk储存的分别是上一个和下一个未被使用的chunk的地址。而这样的一个由未被使用的chunk组成的链表被称为bin。
一般而言,不同大小的free chunk会被分类到不同的bin中,**而bin的类型可以被分为fast bin, small bin,large bin以及unsorted bin。**其中,fast bin的操作效率最高,为单向链表,其他的都是双向链表。较高的操作效率意味着较低的安全性(传统艺能——牺牲安全换效率),所以fastbin机制产生的漏洞也是堆区漏洞的最重要的组成部分之一。
诸多的bin链由指针数组进行管理与保存,数组里头装的就是不同大小的bin链的头尾结点指针:
fastbinY数组:大小为10,为fastbin的专用数组
bins数组:大小为129,其中unsorted bin占1,small bin占2~63,large bin占64~126
bin数组的结构大致如图
而我们的fastbinY数组为了追求效率,直接舍弃了对bk指针的管理,使得fastbin形成了一个单链表结构(而非一般的双链表),在进行添加删除操作时使用的是LIFO原则,结构大致如图
默认情况下,对于size_t为4B的平台, 小于64B的chunk分配请求;对于size_t为8B的平台,小于128B的chunk分配请求,程序会根据所需的size首先到fastbin中去寻找对应大小的bin中是否包含未被使用的chunk,如果有,则直接从bin中返回该chunk。而释放chunk时,也会根据chunk的size参数计算fastbin中对应的index,如果存在对应的大小,就将chunk直接插入对应的bin中。
32位平台 size_t 长度为 4 字节,64 位平台的 size_t 长度可能是 4 字节,也可能是 8 字节,64 位Linux平台 size_t 长度为 8 字节
而且为了追求效率,fastbin不仅使用单链表进行维护,由fastbin管理的chunk即使在被释放后chunk的p参数也不会被重置,而且在释放时只会对链表指针头部的chunk进行校验。
以下图为例,在释放掉chunk1之后,结构如图:
此时如果用户想再释放一次chunk1,程序会对单链表的头部chunk进行验证,发现用户对同一个chunk连续进行了两次释放操作,此时程序会报错并停止运行。
但是如果我们在释放chunk1之后释放一个chunk2,此时fastbin结构如图:
此时头节点就变成了chunk2,此时我们就可以对chunk1再一次进行释放操作,这就是fastbin攻击四类型的一种——double free攻击。
既然对于堆机制中的fastbin已经有了基本的了解,我们可以尝试看看对于fastbin的攻击方式。
当我们申请了两块chunk,分别命名为chunk1和chunk2,然后依次释放chunk1和chunk2,此时fastbin结构如图
此时我们利用fastbin的特性,再次释放chunk1,此时会将fastbinY数组的fd指针指向chunk1,把chunk1的fd指针指向chunk2,导致最先进入fastbin的chunk1本应指向0x00的fd指针指向chunk2,此时chunk块结构如图:
此时我们如果再次申请一块大小与chunk1大小相同的堆块,我们就能从fastbin中取出chunk1,并对chunk1的fd指针进行修改。注意此时当chunk1的fd被修改后,整个单链表的表尾就指向了修改后的fd,此时先连续malloc两次,把chunk2与chunk1依次取出,在下一次malloc的时候,我们就可以在我们的指定地址申请堆块,间接实现了任意地址写操作。***(当然有前提,需要操作目标地址使得size通过fastbin的校验)***
其实感觉house of spirit和double free方法大差不差,只不过更仔细地说明了fake chunk的格式要求,并且修改的不是指定地址的内容,而是指定位置的前后内容,以下内容摘自ctf wiki
要想构造 fastbin fake chunk,并且将其释放时,可以将其放入到对应的 fastbin 链表中,需要绕过一些必要的检测,即
2 * SIZE_SZ
,同时也不能大于av->system_mem
。可以看出,想要使用该技术分配 chunk 到指定地址,其实并不需要修改指定地址的任何内容,关键是要能够修改指定地址的前后的内容使其可以绕过对应的检测。
原理与前二者基本相同,还是挟持fastbin链表中chunk的fd指针(除了double free,还可以用堆溢出等技巧),从而把chunk分配到栈上,从而控制栈上的关键数据(如校验值或者关键的返回地址),当然同时需要栈上存在有满足条件的 size 值。
arbitrary alloc 其实与 alloc to stack 是完全相同的,唯一的区别是分配的目标不再是栈中。 事实上只要满足目标地址存在合法的 size 域(这个 size 域是构造的,还是自然存在的都无妨),我们可以把 chunk 分配到任意的可写内存中,比如 bss、heap、data、stack 等等。
写到这里,这个fastbin机制的漏洞利用方式已经基本上讲完了(当然是基于libc2.23的),这篇文章参考了很多大佬的博客以及ctf wiki,但终究是我自己一个字一个字码出来的。写完这篇文章后,我总算是对于堆区的一些机制有了一点最浅薄的理解。以前做堆区题就是看着大佬们的wp “读下去,记住它,“粤自盘古”呵!“生于太荒”呵!”然后一步一步地跟着调试,知其然而不知其所以然,直到今天,我才真正战胜自己,走出了对堆区纷繁复杂的漏洞机制抽丝剥茧的第一步。因为自己本身有点菜,所以文章也许会错漏百出,还请各位师傅不吝赐教。要是点赞比较多的话,我就尽快找时间把libc2.26及以后的版本(存在tcache机制)的堆区机制利用肝出来。毕竟“战胜恐惧最好的方法就是面对他!”,冬泳怪鸽诚不欺我~~~