tcache attacke
0x01 什么是tcache
tcache全名thread local caching,它為每個(gè)線程創(chuàng)建一個(gè)緩存(cache),從而實(shí)現(xiàn)無鎖的分配算法,有不錯(cuò)的性能提升。性能提升的代價(jià)就是安全檢測(cè)的減少。下面先以glibc2.27進(jìn)行分析,最后再補(bǔ)充glibc2.29和glibc2.31的改進(jìn)。
1.1數(shù)據(jù)結(jié)構(gòu)
新增了兩個(gè)結(jié)構(gòu)體tcache_entry和tcache_perthread_struct來管理tcache。tcache_entry只包含一個(gè)變量next指向下一個(gè)tcache_entry結(jié)構(gòu)。tcache_perthread_struct的counts表示對(duì)應(yīng)tcache_bin的數(shù)量,tcache_entry*表示對(duì)應(yīng)的tcache_bin鏈表。每個(gè)tcache_entry鏈表最多包含7個(gè)bin。
/* We overlay this structure on the user-data portion of a chunk whenthe chunk is stored in the per-thread cache. */ typedef struct tcache_entry {struct tcache_entry *next; } tcache_entry;/* There is one of these for each thread, which contains theper-thread cache (hence "tcache_perthread_struct"). Keepingoverall size low is mildly important. Note that COUNTS and ENTRIESare redundant (we could have just counted the linked list eachtime), this is for performance reasons. */ typedef struct tcache_perthread_struct {char counts[TCACHE_MAX_BINS];tcache_entry *entries[TCACHE_MAX_BINS]; } tcache_perthread_struct;關(guān)于tcache的重要函數(shù),tcache_put()和tcache_get(),用于將tcache_bin放入對(duì)應(yīng)的鏈表中和從對(duì)應(yīng)鏈表中取出tcache_bin。只是對(duì)tc_idx進(jìn)行了最簡單的是否小于TCACHE_MAX_BINS(默認(rèn)是64)進(jìn)行檢查
/* Caller must ensure that we know tc_idx is valid and there's roomfor more chunks. */ static __always_inline void tcache_put (mchunkptr chunk, size_t tc_idx) {tcache_entry *e = (tcache_entry *) chunk2mem (chunk);assert (tc_idx < TCACHE_MAX_BINS);e->next = tcache->entries[tc_idx];tcache->entries[tc_idx] = e;++(tcache->counts[tc_idx]); }/* Caller must ensure that we know tc_idx is valid and there'savailable chunks to remove. */ static __always_inline void * tcache_get (size_t tc_idx) {tcache_entry *e = tcache->entries[tc_idx];assert (tc_idx < TCACHE_MAX_BINS);assert (tcache->entries[tc_idx] > 0);tcache->entries[tc_idx] = e->next;--(tcache->counts[tc_idx]);return (void *) e; }tcache結(jié)構(gòu)小結(jié)
1.2 tcache的使用
通過搜索tcache_get和tcache_put函數(shù)的引用來分析,tcache什么時(shí)候會(huì)被使用。tcache_get有4處,第一個(gè)為定義,總共3個(gè)地方使用了。tcache_put有5處,第一個(gè)為定義,總共4個(gè)地方使用。
tcache_get
第1處 __libc_malloc
在 __libc_malloc中對(duì)申請(qǐng)大小對(duì)應(yīng)的tcache chunk進(jìn)行判斷,如果存在對(duì)應(yīng)空閑tcache chunk則直接進(jìn)行分配,沒有則進(jìn)入_int_malloc進(jìn)行分配
第2,3處 _int_malloc
在_int_malloc:3729處for循環(huán)處理unsorted bin鏈表時(shí)如果存在將目標(biāo)大小的chunk放入tcache時(shí)會(huì)將return_cached置1,直接調(diào)用tcache_get并返回。
tcache_puts
第一處_int_free
如果釋放chunk對(duì)應(yīng)的tcache存在空間,則直接將chunk放入tcache中。
在_int_malloc中存在好多處tcache_put,將fastbin和smallbin中的bin放入tcache中
第二處_int_malloc:3620:fastbin
能執(zhí)行到這,說明原來的對(duì)應(yīng)tcache中并沒有可用bin。將第一個(gè)取到的chunk返回,并循環(huán)將fastbin中的bin放入tcache
第三處_int_malloc:3677:smallbin
類似第二次。將第一個(gè)取到的chunk返回,將剩下的smallbin放入tcache
第四處_int_malloc:3794
當(dāng)tcache,fastbin,smallbin中都沒有需要的chunk,則會(huì)進(jìn)入大的for循環(huán)處理unsortedbin。當(dāng)取出的unsortedbin大小(size)和申請(qǐng)的大小(nb)相同時(shí),會(huì)將chunk放入tcache中并設(shè)置return_cached置為1。
0x02 tcache各種漏洞利用方式
2.1 tcache poisoning
原理:通過覆蓋 tcache 中的 next,實(shí)現(xiàn)任意地址malloc。
下面是how2heap中tcache_poisoning.c簡化版,通過修改chunk_b的next為棧地址stack_var,兩次分配后得到棧地址。
2.2 tcache dup
類似 fastbin dup。但是在tcache_put時(shí),沒有進(jìn)行檢查。
/* Caller must ensure that we know tc_idx is valid and there's roomfor more chunks. */ static __always_inline void tcache_put (mchunkptr chunk, size_t tc_idx) {tcache_entry *e = (tcache_entry *) chunk2mem (chunk);assert (tc_idx < TCACHE_MAX_BINS);e->next = tcache->entries[tc_idx];tcache->entries[tc_idx] = e;++(tcache->counts[tc_idx]); }下面代碼是how2heap中的tcache_dup.c,連續(xù)兩次free chunk_a。之后連續(xù)申請(qǐng)可以申請(qǐng)到同一個(gè)chunk_a。
#include <stdio.h> #include <stdlib.h>int main() {int *a = malloc(8);free(a);free(a);//double freevoid *b = malloc(8);void *c = malloc(8);printf("Next allocated buffers will be same: [ %p, %p ].\n", b, c);return 0; }2.3 tcache perthread corruption
tcache_perthread_struct 管理 tcache 的結(jié)構(gòu),如果能控制這個(gè)結(jié)構(gòu)體,就能隨意控制malloc到任意地址。且一般tcache_perthread_struct結(jié)構(gòu)體也是使用malloc來創(chuàng)建,在heap的最前面。
常見的利用思路:
1.修改counts數(shù)組,將值設(shè)為超過8,當(dāng)free一個(gè)chunk時(shí)將不會(huì)再進(jìn)入tcache,方便泄露libc_base
2.修改entry數(shù)組,可以達(dá)到任意地址malloc的目的
2.4 tcache house of spirit
在棧上偽造fake_chunk,free(fake_chunk)將會(huì)使fake_chunk進(jìn)入tcache
2.5 smallbin unlink
當(dāng)smallbin中還有其他bin時(shí),會(huì)將剩下的bin放入tcache中,會(huì)進(jìn)入上文第三處_int_malloc:3677:smallbin分支,會(huì)出現(xiàn)unlink操作,但是缺少了unlink檢查,可以使用unlink攻擊。
2.6 tcache stashing unlink attack
1.當(dāng)tcache_bin中有空閑的堆塊
2.small_bin中有對(duì)應(yīng)的堆塊
3.調(diào)用calloc(calloc函數(shù)會(huì)調(diào)用_int_malloc),不會(huì)從tcache_bin中取得bin,而是會(huì)進(jìn)入上文第三處_int_malloc:3677:smallbin,將堆塊放入tcache中,由于缺少了檢查
4.如果可以控制small_bin中的bk為一個(gè)writeable_addr,(其中bck就是writeable_addr)則可在writeable_addr+0x10寫入一個(gè)libc地址。
下面是簡化版的how2heap
1.構(gòu)造漏洞環(huán)境,tcache_bin中5個(gè)bin,small_bin中兩個(gè)bin
2.修改chunk2->bk=stack_var,設(shè)置fake_chunk->bk,stack_var[3] = &stack_var[2]
3.calloc觸發(fā)進(jìn)入目標(biāo)分枝,unsorted_bin按照bk進(jìn)行循環(huán),則會(huì)先取到chunk0用于返回,進(jìn)入while循環(huán)將small_bin中剩余的放入tcache中,取得chunk2,再取到stack_var放入tcache中,最后一次調(diào)用bck->fd = bin會(huì)在stack_var[4]中設(shè)置libc中的地址
4.再次申請(qǐng),分配到棧上的fake_chunk。
0x03 glibc2.29的更新
3.1 結(jié)構(gòu)體改變
1.tcache_entry新增key成員(tcache_perthread_struct結(jié)構(gòu)體地址)用于防止double free
typedef struct tcache_entry {struct tcache_entry *next;/* This field exists to detect double frees. */struct tcache_perthread_struct *key; } tcache_entry;typedef struct tcache_perthread_struct {char counts[TCACHE_MAX_BINS];tcache_entry *entries[TCACHE_MAX_BINS]; } tcache_perthread_struct;3.2 tcache_get和tcache_put的改變
新增的改變都是圍繞key進(jìn)行
1.在調(diào)用tcache_put函數(shù)時(shí)設(shè)置key成員為tcache。
2.在調(diào)用tcache_get函數(shù)時(shí)設(shè)置key成員為null。
3.3 對(duì)tcache_put新增的檢測(cè)
只有** _int_free**對(duì)tcache的free新增了key值檢測(cè)是否等于tcache,防止double free。以后double free需要修改key值才能進(jìn)行
#if USE_TCACHE{size_t tc_idx = csize2tidx (size);if (tcache != NULL && tc_idx < mp_.tcache_bins){/* Check to see if it's already in the tcache. */tcache_entry *e = (tcache_entry *) chunk2mem (p);/* This test succeeds on double free. However, we don't 100%trust it (it also matches random payload data at a 1 in2^<size_t> chance), so verify it's not an unlikelycoincidence before aborting. */if (__glibc_unlikely (e->key == tcache)){tcache_entry *tmp;LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);for (tmp = tcache->entries[tc_idx];tmp;tmp = tmp->next)if (tmp == e)malloc_printerr ("free(): double free detected in tcache 2");/* If we get here, it was a coincidence. We've wasted afew cycles, but don't abort. */}if (tcache->counts[tc_idx] < mp_.tcache_count){tcache_put (p, tc_idx);return;}}} #endif0x04 glibc2.31的更新
4.1 結(jié)構(gòu)體改變
tcache_perthread_struct結(jié)構(gòu)體count數(shù)組由原來的char改成了uint16_t,結(jié)構(gòu)體大小發(fā)生了改變由原來的0x240變成0x280。
typedef struct tcache_perthread_struct {uint16_t counts[TCACHE_MAX_BINS];tcache_entry *entries[TCACHE_MAX_BINS]; } tcache_perthread_struct;4.2 tcache_get和tcache_put改變
原本的assert檢查從tcache_get和tcache_put中移除,由調(diào)用者確保函數(shù)調(diào)用的安全。
/* Caller must ensure that we know tc_idx is valid and there's roomfor more chunks. */ static __always_inline void tcache_put (mchunkptr chunk, size_t tc_idx) {tcache_entry *e = (tcache_entry *) chunk2mem (chunk);/* Mark this chunk as "in the tcache" so the test in _int_free willdetect a double free. */e->key = tcache;e->next = tcache->entries[tc_idx];tcache->entries[tc_idx] = e;++(tcache->counts[tc_idx]); }/* Caller must ensure that we know tc_idx is valid and there'savailable chunks to remove. */ static __always_inline void * tcache_get (size_t tc_idx) {tcache_entry *e = tcache->entries[tc_idx];tcache->entries[tc_idx] = e->next;--(tcache->counts[tc_idx]);e->key = NULL;return (void *) e; }0x05 總結(jié)
總體來說利用方式比之前更簡單。
參考鏈接
ctfwiki
總結(jié)
以上是生活随笔為你收集整理的tcache attacke的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 新房装修时别随便拆墙,防止结构受损
- 下一篇: [源码和文档分享]基于JavaFx的多线