关闭
Hit
enter
to search or
ESC
to close
May I Suggest ?
#leanote #leanote blog #code #hello world
Mutepig's Blog
Home
Archives
Tags
Search
About Me
TCACHE
无
338
0
0
mut3p1g
从`glibc2.26`开始,添加了一个新的缓存机制叫做`TCACHE`,下面我们就来看看它所起的作用以及可能的利用方式。 参考链接: http://tukan.farm/2017/07/08/tcache/ ## 0x01 数据结构及一些定义 * tcache_entry `tcache`的基本结构,是一个单项链表,指向下一个`tcache`;每个`tcache`相当于一个缓存`chunk` ```c typedef struct tcache_entry { struct tcache_entry *next; } tcache_entry; ``` * tcache_perthread_struct 这个数据结构为全局的`tcache`数据结构,存在于每个线程中,其中`TCACHE_MAX_BINS`默认值为64,即从24~1024每16字节一个`tcache`结构 ```c typedef struct tcache_perthread_struct { char counts[TCACHE_MAX_BINS]; // 统计tc_idx中存在多少个缓存块 tcache_entry *entries[TCACHE_MAX_BINS]; } tcache_perthread_struct; ``` * 初始化 ```c static __thread bool tcache_shutting_down = false; // 表示tcache没有关闭,即开启状态 static __thread tcache_perthread_struct *tcache = NULL; // 初始化tcache为空 ``` * 宏定义 ```c # define TCACHE_MAX_BINS 64 // tcache适用的bins块数 # define MAX_TCACHE_SIZE tidx2usize (TCACHE_MAX_BINS-1) // 最大的tcache # define tidx2usize(idx) (((size_t) idx) * MALLOC_ALIGNMENT + MINSIZE - SIZE_SZ) // 通过tc_idx转换为bins的大小(不包含头大小) # define csize2tidx(x) (((x) - MINSIZE + MALLOC_ALIGNMENT - 1) / MALLOC_ALIGNMENT) // 通过bins的大小(包含头大小)转换为tc_idx # define usize2tidx(x) csize2tidx (request2size (x)) // 通过bins的大小(包不含头大小)转换为tc_idx # define TCACHE_FILL_COUNT 7 // 每个tcache最多缓存数量 ``` ## 0x02 操作函数 ### 1. 初始化 如果`tcache`为空,则分配`tcache_perthread_struct`大小的空间给`tcache`使用 ```c static void tcache_init(void) { mstate ar_ptr; void *victim = 0; const size_t bytes = sizeof (tcache_perthread_struct); // 获得tcache_perthread_struct结构大小 if (tcache_shutting_down) return; arena_get (ar_ptr, bytes); // 在分配区为对应大小上锁 victim = _int_malloc (ar_ptr, bytes); // 申请对应大小的块 if (!victim && ar_ptr != NULL) // 分配失败则再次分配 { ar_ptr = arena_get_retry (ar_ptr, bytes); victim = _int_malloc (ar_ptr, bytes); } if (ar_ptr != NULL) __libc_lock_unlock (ar_ptr->mutex); /* In a low memory situation, we may not be able to allocate memory - in which case, we just keep trying later. However, we typically do this very early, so either there is sufficient memory, or there isn't enough memory to do non-trivial allocations anyway. */ if (victim) // 分配成功则将块赋值给tcache,并清空其中的内容 { tcache = (tcache_perthread_struct *) victim; memset (tcache, 0, sizeof (tcache_perthread_struct)); } } # define MAYBE_INIT_TCACHE() \ if (__glibc_unlikely (tcache == NULL)) \ tcache_init(); #else /* !USE_TCACHE */ ``` ### 2. 插入块 这个函数用来将`chunk`插入为`tc_idx`对应的`tcache->entries`链表头,`tc_idx`不得超过系统限制,并且还存在多的空间来申请其他块 ```c 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); // tc_idx不能超过限制 e->next = tcache->entries[tc_idx]; // 挂链表头 tcache->entries[tc_idx] = e; ++(tcache->counts[tc_idx]); // count加1 } ``` ### 3. 取出块 这个函数从`tc_idx`对应的`tcache->entries`链表头中取出块,并且要去取出后余留下的链表头不为空 ```c 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]); // count减1 return (void *) e; } ``` ### 4. 关闭 关闭`tcache`即将缓存中的`chunk`都释放掉,最后将`tcache_perthread_struct`结构也释放掉 ```c static void tcache_thread_shutdown (void) { int i; tcache_perthread_struct *tcache_tmp = tcache; if (!tcache) return; /* Disable the tcache and prevent it from being reinitialized. */ tcache = NULL; // 将tcache置空 tcache_shutting_down = true; // 系统设置关闭 /* Free all of the entries and the tcache itself back to the arena heap for coalescing. */ for (i = 0; i < TCACHE_MAX_BINS; ++i) // 遍历各个tcache链表 { while (tcache_tmp->entries[i]) // 如果有剩余的缓存都释放掉 { tcache_entry *e = tcache_tmp->entries[i]; tcache_tmp->entries[i] = e->next; __libc_free (e); } } __libc_free (tcache_tmp); // 释放tcache_perthread_struct结构 } ``` ## 0x03 使用tcache 通过搜索`#if USE_TCACHE`可以找到所有使用`tcache`的位置 ### 1. malloc_par 首先在全局配置信息中多出来了一些相关的配置 ```c static struct malloc_par mp_ = { .... #if USE_TCACHE , .tcache_count = TCACHE_FILL_COUNT, // 每个bins中最大tcache数量,默认为7 .tcache_bins = TCACHE_MAX_BINS, // 最多tcache使用的bins数目,默认为64 .tcache_max_bytes = tidx2usize (TCACHE_MAX_BINS-1), // 最大的tcache使用的bins的大小 .tcache_unsorted_limit = 0 /* No limit. */ // 最多可以用几次unsorted_tcache来分配,默认为0表示没有限制 #endif }; ``` ### 2. malloc * libc_malloc 通过用户需求的大小,判断`tcache`中是否存在对应的块,如果是则直接返回 ```c 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); // 将用户需求的bytes转换为tbytes size_t tc_idx = csize2tidx (tbytes); // 获得tbytes对应下标tc_idx MAYBE_INIT_TCACHE (); // 判断tcache是否初始化,如果没有则进行初始化 DIAG_PUSH_NEEDS_COMMENT; if (tc_idx < mp_.tcache_bins // 大小不能超过系统限制 /*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */ && tcache // tcache不为空 && tcache->entries[tc_idx] != NULL) // tc_idx对应的tcache不为空 { return tcache_get (tc_idx); } DIAG_POP_NEEDS_COMMENT; #endif ``` * fastbin 当成功从`fastbin`中分配出去一个块之后,判断对应的`fastbin`中是否还有剩余的块,如果有则将他们都加入到对应的`tcache`中,直到`tcache`链表被塞满 ```c if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ())) { idx = fastbin_index (nb); mfastbinptr *fb = &fastbin (av, idx); mchunkptr pp; victim = *fb; // fb为获取idx对应的fastbin,victim为准备用来分配的chunk,之后fb变为新的fastbin头 if (victim != NULL) { if (SINGLE_THREAD_P) *fb = victim->fd; else REMOVE_FB (fb, pp, victim); if (__glibc_likely (victim != NULL)) { size_t victim_idx = fastbin_index (chunksize (victim)); if (__builtin_expect (victim_idx != idx, 0)) malloc_printerr ("malloc(): memory corruption (fast)"); check_remalloced_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); // 将nb转换为tc_idx if (tcache && tc_idx < mp_.tcache_bins) // tcache不为空切tc_idx未超过系统限制 { mchunkptr tc_victim; /* While bin not empty and tcache not full, copy chunks. */ while (tcache->counts[tc_idx] < mp_.tcache_count // tc_idx中缓存总数没超过系统限制 && (tc_victim = *fb) != NULL) // idx对应的fastbin还有剩余 { 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); // 将多余的fastbin加入tcache } } #endif ``` * smallbin `smallbin`的操作也类似,主要代码如下: ``` #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; // 从smallbin中提取块到tcache 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); } } } #endif ``` * unsorted bin 如果无法直接从`fastbin`或者`smallbin`中进行分配,那么就只能通过`unsorted bin`尝试分配了,首先初始化一下部分值 接着尝试将`tc_idx`对应的`unsorted bin`加入到`tcache`,如果加入成功则将`return_cached`设置为1表示从`tcache`中返回块,并将计数器`tcache_unsorted_count `加1 最后判断`tcache_unsorted_count `满足系统限制`mp_.tcache_unsorted_limit`(如果有限制的话,0表示没有限制),那么需要继续遍历`unsorted bin`直到满足限制(如果再次循环后发现对应`tcache`中空间不足,则直接从`unsorted bin`中返回);如果没有限制则直接返回 ```c #if USE_TCACHE INTERNAL_SIZE_T tcache_nb = 0; size_t tc_idx = csize2tidx (nb); // nb转换为tc_idx if (tcache && tc_idx < mp_.tcache_bins) // tcache不为空且大小符合系统限制 tcache_nb = nb; int return_cached = 0; // 是否通过tcache返回块 tcache_unsorted_count = 0; // 当前存在多少个unsorted_cache #endif ....... #if USE_TCACHE /* Fill cache first, return to user only if cache fills. We may return one of these chunks later. */ if (tcache_nb // 需要分配的大小不为0 && tcache->counts[tc_idx] < mp_.tcache_count) // 还有多余空间 { tcache_put (victim, tc_idx); // 将unsorted bin中tc_idx对应的块加入tcache return_cached = 1; // 是否从tcache中返回 continue; } else { #endif check_malloced_chunk (av, victim, nb); void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; #if USE_TCACHE } #endif ..... #if USE_TCACHE /* If we've processed as many chunks as we're allowed while filling the cache, return one of the cached ones. */ ++tcache_unsorted_count; if (return_cached // 如果要通过tcache返回 && mp_.tcache_unsorted_limit > 0 // 如果系统存在限制unsorted_tcache && tcache_unsorted_count > mp_.tcache_unsorted_limit) // 需要满足限制 { return tcache_get (tc_idx); } #endif ..... #if USE_TCACHE /* If all the small chunks we found ended up cached, return one now. */ if (return_cached) // 如果不存在限制mp_.tcache_unsorted_limit,则直接返回 { return tcache_get (tc_idx); } #endif ``` ### 3. free 相对`malloc`来说,`free`部分就简单了很多,直接判断释放的块大小是否在`tcache`的大小范围内,如果是的且对应`tcache`还能存放那么就直接加进去 ```c #if USE_TCACHE { size_t tc_idx = csize2tidx (size); if (tcache // tcache不为空 && tc_idx < mp_.tcache_bins // 满足限制大小 && tcache->counts[tc_idx] < mp_.tcache_count) // 有多余空间存放 { tcache_put (p, tc_idx); return; } } #endif ``` ## 0x04 利用点 可以看到,利用`tcache`分配和释放的时候都是直取直入,除了判断一下与`tcache`有关的值之外,没有任何校验,所以应该可以随便玩~ ### 1. overlapping 由于`free`的时候没有校验,那么可以篡改某个`chunk`的`size`为`fakesize`然后释放掉它,接着申请`fakesize`后就能将后面一个`chunk`包含进去了,从而构造了`overlapping` ### 2. tcache_dup 如果指针没有清0的话,那么可以多次释放同一个指针,接着该指针都会加入`tcache`中,接着申请就会申请多个相同的指针出来 ### 3. house_of_spirit 加入`tcache`没有任何校验,所以可以随便在某地构造一个设置好`size`的伪造`chunk`释放,再次申请对应`size`的话就会将地址`malloc`出来了 ### 4. tcache_poisoning 当我们释放了一个`chunk`后,它就会变成`tcache_entry`,那么如果我们修改其`next`为我们需要的位置`fake_chunk`,那么`malloc`两次对应大小后就会将`fake_chunk`给`malloc`出来 ## 0x05 注意点 `fastbin`的`fd`指向下一个块的`head` 但`tcache`的`fd`指向下一个块的`fd` # 0x06 demo 这里学习一下`baby_tcache`这道题目 ## 1. all 题目只提供了两个点 * add 最多只能同时存在9个块,可以`malloc`0x2000以下的任意大小。然后读入数据的时候有个`off by null` 最后将`malloc`出来的地址记入`chunks[i]`,输入的`size`记入`size[i]` * del 输入要删除的下标,若`chunks[i]`不为0,则将其中的内容全部设为0xda,并`free`,同时`chunks[i]`和`size[i]`都置为0 ## 2. leak 首先这里是没有打印的地方的,所以需要通过其他途径。 首先我们先构造一个`overlap`
觉得不错,点个赞?
提交评论
Sign in
to leave a comment.
No Leanote account ?
Sign up now
.
0
条评论
More...
文章目录
No Leanote account ? Sign up now.