tcache浅谈
Heap基础知识
[toc]
tcache attack
先略记录一下,以后可能会回来补充细节。
什么事tcache
这是glibc 2.26以后引入的一种为了加快内存分配速度的机制,但同时也产生了很多安全漏洞。由于ubuntu18.04已经开始使用glibc 2.27,因此ubuntu 18.04版本存在tcache机制的使用。由于tcache机制也逐渐进行了诸多更新,修复了部分漏洞,本文暂时站在tcache最初始的版本进行讲解。
若存在tcache机制,那么当一个非large chunk被free后,它不再会被直接添加到unsorted bin或者fast bin,而是被添加到对应的tcache bin中。tcache bin是一个单向链表结构,只有fd指针,结构可以说是非常简单。
同一个大小的tcache中只能存在7个chunk(默认情况下)。因此,若你想将一个chunk申请到unsorted bin中,不妨申请8个属于unsorted bin的chunk,由此也可以使用unsorted bin leak来泄露libc基地址。
值得注意的是,tcache指向的直接是用户地址,而不是之前bin指向的是header的地址。
tcache perthread struct
对于tcache,glibc会在第一次申请堆块的时候创建一个tcache_perthread_struct的数据结构,同样存放在堆上。它的定义如下所示:
1 | |
tcache poisoning及其变迁
glibc2.27低版本
若存在tcache机制时,若申请一个属于tcache中的chunk,使用到的函数是tcache_get()函数,该函数在初始版本没有任何的安全机制,因此只需要简单地将某个tcache中的chunk的fd指针修改为想要分配到的地方,即可在目标地址申请一个chunk。
glibc2.27高版本-glibc2.29
在glibc2.27的高版本开始,tcache chunk的bk将不再为空,而是tcache_perthread_struct的用户地址。然而这和tcache poisoning没有任何关系,我们仍然可以没有任何限制地使用tcache poisoning attack。
glibc2.31
在glibc2.31,glibc添加了对tcache的count的检查,即在对应index的count小于等于0时,无法从该tcache中申请chunk。这意味着我们在tcache中只有一个chunk时修改其fd指针来完成攻击,而是至少需要两个chunk位于同一个tcache bin中。
总结:
- 至少两个
chunk位于同一个tcache bin(或者别的方式,总之有count检查)
glibc2.32-glibc2.38
自从glibc2.32开始,glibc引入了相当多对于tcache的检查。我们这里只说poisoning,不说double free,这一部分在后面。
首先,tcache bin会检查待申请的地址是否对齐,即末位是否为0。若不为0,则会报错。
其次,tcache的fd指针将会被加密。假设要申请的地址为target,chunk的用户地址为address,则:
1 | |
值得注意的是,在glibc2.27高版本到glibc2.33,我们可以通过泄露bk的方式来获得堆地址。
总结:
- 至少两个
chunk位于同一个tcache bin(或者别的方式,总之有count检查) - 待申请地址必须对齐
chunk -> fd = target ^ (address >> 12)
tcache double free
若没有tcache的时候,double free不能简单地连续对一个chunk进行free两次这个机制略显复杂的话,那么tcache double free就显得单纯许多了。在初始版本下tcache的释放操作是使用的tcache_get()函数,该函数同样没有任何安全机制,因此可以简单地直接对一个chunk进行两次free,因此可以申请回该chunk,对其修改后再次申请,完成任意地址写/任意地址chunk分配的目的。
高版本
从glibc2.27高版本开始简单的tcache double free已经成为了历史。
patch方式为tcache chunk的bk指针处将会有一个key,对于每一个释放的chunk,若其bk指针等于其key,说明该chunk属于tcache,便会循环tcache来遍历检查其是否已经位于tcache。因此若你能修改其bk指针为任意值(只要不等于key),也可以进行double free。(或者你可以尝试house of botcake)
在
glibc2.27高版本-glibc2.33,tcache的key为tcache_perthread_struct的用户地址。在
glibc2.33-glibc2.38,tcache的key为随机数。
tcache house of spirit
与fastbin的house of spirit是相当类似的,不同的是,tcache的house of spirit更加简单,可以直接在任意地方伪造一个chunk然后进行free。fast bin的house of spirit还需要控制要free的下一个chunk的size域。
tcache_stash_unlink_attack
tcache stash unlink可以达成两个目的:
- 任意地址申请一个
fake chunk - 往任意地址写一个
main_arena地址
看起来就像fastbin attack和unsortedbin attack的结合,威力很强,但是同时利用条件也非常苛刻:
- 需要保证同一种大小的
chunk在tcache中有5个,而在smallbin中有2个。(一般利用) - 至少进行一次
calloc - 需要
UAF来对smallbin中的chunk进行修改
其原理是:
首先需要知道calloc。calloc和malloc有两个不同点,其一是会自动清零申请到的chunk,其二是calloc不会申请tcache中的chunk。而smallbin有一个特点,那就是当smallbin中的chunk被申请后,其通过bk相连的所有chunk都会被挂入tcache。
由此,我们可以修改后插入的chunk(它的bk指针指向main_arena)的bk指针,使其指向我们控制的fake_chunk。只要进行一次calloc,glibc会使得其从smallbin中申请一个chunk,然后将剩下的smallbin中的chunk都挂入tcache(包括我们修改后的smallbin chunk)。当我们修改后的chunk被挂入tcache后,由于其是通过bk指针来寻找下一个chunk的,因此会将fake_chunk也挂入tcache。
然后,其会试图再将fake_chunk的bk也挂入tcache,其中会使得:bck->fd=bin,会使得fake_chunk的bk指针指向的chunk的fd写一个main_arena地址。因此,若我们想要在target_addr写一个main_arena的值,我们需要控制fake_chunk的bck的值为target_addr-0x10。
tcache_perthread_struct hijacking
上面我们提到了tcache_perthread_struct数据结构的形式为:
1 | |
在程序初始化堆的时候,会创建一个对应大小的tcache_perthread_struct。其中:
counts数组存放了每一个大小的chunk目前存放了多少个。entries是一个链表,它里面存放的值是在下一次申请这个大小的tcache chunk的时候,应该分配哪个位置的chunk。
要注意,counts和entries的对应关系是按顺序来的,例如前0x40个字节中,第0个字节指示大小为0x20的tcache chunk的已分配数量,第1个字节指示大小为0x30的tcache chunk的已分配数量。又例如,0x40字节处的8字节表示下一个要分配的0x20大小的tcache chunk要分配的地址。
若我们能够控制tcache_pertrhead_struct,则这两个值都可以被篡改。效果分别为:
- 若我们控制了
counts,对指定地方大小的count设置为7,则再次分配该大小的chunk时,就不会分配到tcache中。例如可以分配一个unsorted chunk来泄露libc。 - 若我们控制了
entries,相当于实现了任意大小的chunk的tcache poisoning,即可以在任意地址分配chunk,威力巨大。