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
,威力巨大。