tcache attacke
0x01 什么是tcache
tcache全名thread local caching,它為每個線程創建一個緩存(cache),從而實現無鎖的分配算法,有不錯的性能提升。性能提升的代價就是安全檢測的減少。下面先以glibc2.27進行分析,最后再補充glibc2.29和glibc2.31的改進。
1.1數據結構
新增了兩個結構體tcache_entry和tcache_perthread_struct來管理tcache。tcache_entry只包含一個變量next指向下一個tcache_entry結構。tcache_perthread_struct的counts表示對應tcache_bin的數量,tcache_entry*表示對應的tcache_bin鏈表。每個tcache_entry鏈表最多包含7個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;關于tcache的重要函數,tcache_put()和tcache_get(),用于將tcache_bin放入對應的鏈表中和從對應鏈表中取出tcache_bin。只是對tc_idx進行了最簡單的是否小于TCACHE_MAX_BINS(默認是64)進行檢查
/* 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結構小結
1.2 tcache的使用
通過搜索tcache_get和tcache_put函數的引用來分析,tcache什么時候會被使用。tcache_get有4處,第一個為定義,總共3個地方使用了。tcache_put有5處,第一個為定義,總共4個地方使用。
tcache_get
第1處 __libc_malloc
在 __libc_malloc中對申請大小對應的tcache chunk進行判斷,如果存在對應空閑tcache chunk則直接進行分配,沒有則進入_int_malloc進行分配
第2,3處 _int_malloc
在_int_malloc:3729處for循環處理unsorted bin鏈表時如果存在將目標大小的chunk放入tcache時會將return_cached置1,直接調用tcache_get并返回。
tcache_puts
第一處_int_free
如果釋放chunk對應的tcache存在空間,則直接將chunk放入tcache中。
在_int_malloc中存在好多處tcache_put,將fastbin和smallbin中的bin放入tcache中
第二處_int_malloc:3620:fastbin
能執行到這,說明原來的對應tcache中并沒有可用bin。將第一個取到的chunk返回,并循環將fastbin中的bin放入tcache
第三處_int_malloc:3677:smallbin
類似第二次。將第一個取到的chunk返回,將剩下的smallbin放入tcache
第四處_int_malloc:3794
當tcache,fastbin,smallbin中都沒有需要的chunk,則會進入大的for循環處理unsortedbin。當取出的unsortedbin大小(size)和申請的大小(nb)相同時,會將chunk放入tcache中并設置return_cached置為1。
0x02 tcache各種漏洞利用方式
2.1 tcache poisoning
原理:通過覆蓋 tcache 中的 next,實現任意地址malloc。
下面是how2heap中tcache_poisoning.c簡化版,通過修改chunk_b的next為棧地址stack_var,兩次分配后得到棧地址。
2.2 tcache dup
類似 fastbin dup。但是在tcache_put時,沒有進行檢查。
/* 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,連續兩次free chunk_a。之后連續申請可以申請到同一個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 的結構,如果能控制這個結構體,就能隨意控制malloc到任意地址。且一般tcache_perthread_struct結構體也是使用malloc來創建,在heap的最前面。
常見的利用思路:
1.修改counts數組,將值設為超過8,當free一個chunk時將不會再進入tcache,方便泄露libc_base
2.修改entry數組,可以達到任意地址malloc的目的
2.4 tcache house of spirit
在棧上偽造fake_chunk,free(fake_chunk)將會使fake_chunk進入tcache
2.5 smallbin unlink
當smallbin中還有其他bin時,會將剩下的bin放入tcache中,會進入上文第三處_int_malloc:3677:smallbin分支,會出現unlink操作,但是缺少了unlink檢查,可以使用unlink攻擊。
2.6 tcache stashing unlink attack
1.當tcache_bin中有空閑的堆塊
2.small_bin中有對應的堆塊
3.調用calloc(calloc函數會調用_int_malloc),不會從tcache_bin中取得bin,而是會進入上文第三處_int_malloc:3677:smallbin,將堆塊放入tcache中,由于缺少了檢查
4.如果可以控制small_bin中的bk為一個writeable_addr,(其中bck就是writeable_addr)則可在writeable_addr+0x10寫入一個libc地址。
下面是簡化版的how2heap
1.構造漏洞環境,tcache_bin中5個bin,small_bin中兩個bin
2.修改chunk2->bk=stack_var,設置fake_chunk->bk,stack_var[3] = &stack_var[2]
3.calloc觸發進入目標分枝,unsorted_bin按照bk進行循環,則會先取到chunk0用于返回,進入while循環將small_bin中剩余的放入tcache中,取得chunk2,再取到stack_var放入tcache中,最后一次調用bck->fd = bin會在stack_var[4]中設置libc中的地址
4.再次申請,分配到棧上的fake_chunk。
0x03 glibc2.29的更新
3.1 結構體改變
1.tcache_entry新增key成員(tcache_perthread_struct結構體地址)用于防止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進行
1.在調用tcache_put函數時設置key成員為tcache。
2.在調用tcache_get函數時設置key成員為null。
3.3 對tcache_put新增的檢測
只有** _int_free**對tcache的free新增了key值檢測是否等于tcache,防止double free。以后double free需要修改key值才能進行
#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 結構體改變
tcache_perthread_struct結構體count數組由原來的char改成了uint16_t,結構體大小發生了改變由原來的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中移除,由調用者確保函數調用的安全。
/* 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 總結
總體來說利用方式比之前更簡單。
參考鏈接
ctfwiki
總結
以上是生活随笔為你收集整理的tcache attacke的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 新房装修时别随便拆墙,防止结构受损
- 下一篇: js 的查询语句