0x00
好像这个新版本的tcache安全性更差了,有得必有失
0x01 概览
在 tcache 中新增了两个结构体,分别是 tcache_entry 和 tcache_perthread_struct
其实这个next还是相当于chunk
里的fd
1 | /* We overlay this structure on the user-data portion of a chunk when the chunk is stored in the per-thread cache. */ |
其中有两个重要的函数, tcache_get()
和 tcache_put()
:
1 | static void |
可以看出tcache_put
会检测idx是不是在tcache
的范围中,然后就没别检测了,也就是说我随便free一个chunk,不管之前是什么东西,都会直接添加到对应大小的tcache
中。所以说可以连续free
直接造成double free
,太不安全了。。。。
tcache_get
会检测idx
在不在范围内,并且相应大小的tcache
链中是否有空闲chunk,也并没有像fastbin中检测这个空闲chunk
的size
和下一块chunk
的prev_size
是不是一样。这伪造fake chunk
就很简单呀。。。。
这两个函数会在函数 int_free 和 __libc_malloc 的开头被调用,其中 tcache_put 当所请求的分配大小不大于0x408并且当给定大小的 tcache bin 未满时调用。一个 tcache bin 中的最大块数mp.tcache_count是7。
1 | /* This is another arbitrary limit, which tunables can change. Each |
free时,如果size < smallbin size:
- 会被放到对应size的tcache中,每个tcache默认最多储存7个.
- tcache存满之后,free便会存到fastbin或者unsortedbin.
- 被放入tcache的chunk不会取消其nextchunk的inuse bit,不会被合并。
malloc时,且size在tcache范围内。
- 先从tcache中取chunk,遵循FILO原则。直到对应size的tcache为空后才会从bin中找。
- tcache为空时,如果fastbin/smallbin/unsorted bin中有符合size的chunk,会先把它们放到tcache中,直到tcache满,然后再从tcache中取。因此chunk在bin中和tcache中的顺序相反。
0x02 利用
tcache poisoning
通过覆盖 tcache 中的 next,不需要伪造任何 chunk 结构即可实现 malloc 到任何地址。
1 |
|
tcache dup
类似 fastbin dup,不过利用的是 tcache_put() 的不严谨
1 | static __always_inline void |
可以看出,tcache_put() 的检查也可以忽略不计(甚至没有对 tcache->counts[tc_idx] 的检查),大幅提高性能的同时安全性也下降了很多。
因为没有任何检查,所以我们可以对同一个 chunk 多次 free,造成 cycliced list。
1 |
|
如此直白明显的double free
都不会引起crash,效率是增加了,但这真的安全性堪忧,只能依靠程序员的安全意识了,不过听说新版本的commit把这个安全性进行了加强。
tcache perthread corruption
我们已经知道 tcache_perthread_struct 是整个 tcache 的管理结构,如果能控制这个结构体,那么无论我们 malloc 的 size 是多少,地址都是可控的。
因为 tcache_prethread_struct 也在堆上,因此这种方法一般只需要 partial overwrite 就可以达到目的。
SCTF2019 one_chunk
1 | New(0x68, '\n') |
调试结果如下:
1 | pwndbg> tcachebins |
之后 free
掉 tcache_perthread_struct
,使其放入usorted bin
,然后tcache
上就会因为usorted bin
的 分配而被写上main_arena
的地址,在对main_arena
的地址进行部分覆盖,实现控制_IO_2_1_stdout_
。
1 | New(0x68, '\xFF' * 0x40 + '\n') #这里就是把count弄满,使得下次free到unsortedbin |
tcache house of spirit
拿 how2heap 的源码来讲:
1 |
|
攻击之后的目的是,去控制栈上的内容,malloc 一块 chunk ,然后我们通过在栈上 fake 的 chunk,然后去 free 掉他,我们会发现Tcache 里就存放了一块 栈上的内容,我们之后只需 malloc,就可以控制这块内存。
tcache的counts中存在的数据类型判断漏洞
counts定义为一个字符数组,记录每个tcache链中tcache的数量,在C语言中并没有char类型的常量(但是在C++中却有,字符常量都是char类型),其实是用int表示char,所以这个counts是一个有符号整型变量(-128~127)
在int_free函数中,把chunk放入tcache时,会判断待放入的tcache链是否小于mp_.tcache_count,一般为7
1 |
|
查看源码,tcache_count是一个size_t类型的变量,也就是无符号长整型,那么上述的判断就变为 char型的counts 是否小于unsigned long int型的tcache_count ,就会把counts变量转为无符号的长整型进行比较
如果此时的counts大小为-1(0xff),被转成无符号的长整型后就变成255(0xff),那么就会使上述判断失效,在tcache的counts变成-1后,就会将之后free的chunk,放入unsortedbin中
有什么用呢?
当我们double free一个chunk后,tcache会的得到一个自循环的链表,tcache的counts是2
连续申请两次后,counts会变成0
再申请一次后,发现其counts变成了0xff,此时再次free该chunk,chunk就会进入unsortedbin中