一
Tcache简介
#if USE_TCACHE
/* 最大64个bins */
#define TCACHE_MAX_BINS 64
#define MAX_TCACHE_SIZE tidx2usize (TCACHE_MAX_BINS-1)
#define tidx2usize(idx) (((size_t) idx) * MALLOC_ALIGNMENT + MINSIZE - SIZE_SZ)
#define csize2tidx(x) (((x) - MINSIZE + MALLOC_ALIGNMENT - 1) / MALLOC_ALIGNMENT)
#define usize2tidx(x) csize2tidx (request2size (x))
/* 每个bins最多缓存7个chunk */
#define TCACHE_FILL_COUNT 7
#endif
typedef struct tcache_entry {
struct tcache_entry *next;
} tcache_entry;
/*
* tcache_entry 用于链接空闲的 chunk 结构体,其中的 next 指针指向下一个大小相同的 chunk。
* 需要注意的是这里的 next 指向 chunk 的 user_data ,而 fastbin 的 fd 指向 chunk 开头(prev_size)的地址。
* 而且,tcache_entry 会复用空闲 chunk 的 user_data 部分。
*/
// tcache_perthread_struct位于堆的开头,大小为0x250。
typedef struct tcache_perthread_struct {
char counts[TCACHE_MAX_BINS]; //用于存放bins中的chunk数量。
tcache_entry *entries[TCACHE_MAX_BINS]; //用于存放64个bins地址
} tcache_perthread_struct;
static __thread tcache_perthread_struct *tcache = NULL;
/*
* 每个 thread 都会维护一个 tcache_perthread_struct,一共有 TCACHE_MAX_BINS 个计数器和 TCACHE_MAX_BINS 项 tcache_entry,
* ·tcache_entry 用单向链表的方式链接了相同大小的处于空闲状态(free后)的 chunk。
* ·counts 记录了 tcache_entry 链上空闲 chunk 的数目,每条链上最多可以有 7 个 chunk。
*/
二
Tcache实现
Tcache初始化
static void
tcache_init(void)
{
mstate ar_ptr;
void *victim = 0;
const size_t bytes = sizeof (tcache_perthread_struct); //大小为0x240
if (tcache_shutting_down)
return;
arena_get (ar_ptr, bytes);
/* 使用_int_malloc为 tcache_perthread_struct 分配内存 */
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);
}
/* __libc_lock_unlock 是一个宏,用于释放一个互斥锁 */
if (ar_ptr != NULL)
__libc_lock_unlock (ar_ptr->mutex);
if (victim)
{
/* 转换为tcache_perthread_struce结构 */
tcache = (tcache_perthread_struct *) victim;
/* 初始为0 */
memset (tcache, 0, sizeof (tcache_perthread_struct));
}
}
分配堆块时
/* glibc2.26没有对放入的chunk进行严格校验的,也没有把P位置零 */
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);
/* 放在头部,和插入fastbin的插入形式是一致的 */
e->next = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}
/*
* malloc出来的chunk为fast chunk,
* 那么fastbin中相应大小的chunk会被放入tcache相应大小的tcache bins中,
* 直到相应的tcache bins满7个或者相应的fastbins为空。
* chunk在tcache bin中顺序与fastbin相反
*/
#if USE_TCACHE
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (pp = *fb) != NULL)
{
REMOVE_FB (fb, tc_victim, pp);
if (tc_victim != 0)
{
tcache_put (tc_victim, tc_idx);
}
}
}
#endif
/*
* malloc出来的chunk是small chunk。和fast chunk类似。
* 但是会对每一个chunk的next_chunk的prev_inuse位设置为1。
* chunk在tcache bin中顺序与small bin中顺序相同。
*/
#if USE_TCACHE
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;
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);
}
}
}
#endif
/*
* 如果unsorted chunk跟要用户所需要chunk大小一致,那么会优先将该chunk挂入对应的tcache中,并不直接返回
*/
if (size == nb)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
#if USE_TCACHE
if (tcache_nb
&& tcache->counts[tc_idx] < mp_.tcache_count)
{
tcache_put (victim, tc_idx);
return_cached = 1;
continue;
}
else
{
#endif
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
#if USE_TCACHE
}
#endif
}
从Tcache取出堆块
/* glibc2.26取出chunk并没有严格的检查,由于tcache优先级很高,所以其他的检查机制并没有对tcache发挥过多作用 */
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);
/* 取出chunk */
tcache->entries[tc_idx] = e->next;
/* counts记录相应bins的chunk数量,取出时减一 */
--(tcache->counts[tc_idx]);
return (void *) e;
}
/*
* 如果用户需要的chunk size 属于 non-large chunk并且 tcache 已经初始化并且对应tcache bins中有符合chunk则取出
* 注意从tcache中取出块是在进入_int_malloc()之前的
*/
if (tc_idx < mp_.tcache_bins
&& tcache
&& tcache->entries[tc_idx] != NULL)
{
return tcache_get (tc_idx);
}
/*
* 在unsorted bin最后,如果找到了可以返回的块,
* 并且mp_.tcache_unsorted_limit次数小于处理unsorted count(即tcache中装满了对应的chunk)
* 那么就会从其中拉出一个chunk出来返回
*/
.tcache_unsorted_limit = 0
#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
&& mp_.tcache_unsorted_limit > 0
&& tcache_unsorted_count > mp_.tcache_unsorted_limit)
{
return tcache_get (tc_idx);
}
#endif
/*
* 在unsorted bin的遍历之后 如果unsorted bin中存在可以返回的chunk
* 那么在遍历unsorted bin之后则调用一次tcache_get返回给用户使用
*/
#if USE_TCACHE
if (return_cached)
{
return tcache_get (tc_idx);
}
#endif
释放堆块时
/*
* 如果tcache已经初始化
* 并且free的chunk属于non-large chunk
* 如果free的chunk对应的tcache链未满7个
* 那么就将chunk放入到tcahce中缓存
*/
#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
总结
三
安全分析
cve-2017-17426
#include <stdio.h>
#include <stdlib.h>
int main() {
void *x = malloc(10);
printf("malloc(10): %pn",x);
free(x);
void *y = malloc(((size_t)~0) - 2);
printf("malloc(((size_t)~0) - 2): %pn",y);
return 0;
}
double free check
四
经典赛题(已提供相关附件)
HITB CTF 2018: gundam
__int64 Build()
{
unsigned int v1; // [rsp+0h] [rbp-20h] BYREF
unsigned int i; // [rsp+4h] [rbp-1Ch]
void *s; // [rsp+8h] [rbp-18h]
void *buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v5; // [rsp+18h] [rbp-8h]
v5 = __readfsqword(0x28u);
s = 0LL;
buf = 0LL;
if ( (unsigned int)dword_20208C <= 8 )
{
s = malloc(0x28uLL);
memset(s, 0, 0x28uLL);
buf = malloc(0x100uLL);
if ( !buf )
{
puts("error !");
exit(-1);
}
printf("The name of gundam :");
//buf记录名字,没有'x00'限制可能泄露
read(0, buf, 0x100uLL);
// (s+8)位置 -> buf
*((_QWORD *)s + 1) = buf;
printf("The type of the gundam :");
__isoc99_scanf("%d", &v1);
//type < 3
if ( v1 >= 3 )
{
puts("Invalid.");
exit(0);
}
// (s+16) -> type
strcpy((char *)s + 16, &aFreedom[20 * v1]);
// s->1 标记为在使用。
*(_DWORD *)s = 1;
for ( i = 0; i <= 8; ++i )
{
if ( !qword_2020A0[i] )
{
//Factory[9],工厂数组。
qword_2020A0[i] = s;
break;
}
}
// 换原为NumOfGundam,记录gundam的数量
++dword_20208C;
}
return 0LL;
}
struct gundam{
int flag;
char *buf;
char type[60];
}gundam;
struct gundam *factory[9]
__int64 Visit()
{
unsigned int i; // [rsp+4h] [rbp-Ch]
if ( NumOfGundam )
{
for ( i = 0; i <= 8; ++i )
{
//将每个gundma的buf和Type打印出来。
if ( factory[i] && *(_DWORD *)factory[i] )
{
printf("nGundam[%u] :%s", i, *(const char **)(factory[i] + 8LL));
printf("Type[%u] :%sn", i, (const char *)(factory[i] + 16LL));
}
}
}
else
{
puts("No gundam produced!");
}
return 0LL;
}
__int64 Destroy()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
if ( NumOfGundam )
{
printf("Which gundam do you want to Destory:");
__isoc99_scanf("%d", &v1);
if ( v1 > 8 || !factory[v1] )
{
puts("Invalid choice");
return 0LL;
}
// 使用标记置为0
*(_DWORD *)factory[v1] = 0;
// name存在UAF漏洞。
free(*(void **)(factory[v1] + 8LL));
}
else
{
puts("No gundam");
}
// 并没有将NumOfGundam数量-1
return 0LL;
}
unsigned __int64 BlowUp()
{
unsigned int i; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
for ( i = 0; i <= 8; ++i )
{
if ( factory[i] && !*(_DWORD *)factory[i] )
{
free((void *)factory[i]);
factory[i] = 0LL;
// 只把标记为置为0,存在uaf。
--NumOfGundam;
}
}
puts("Done!");
return __readfsqword(0x28u) ^ v2;
}
BCTF2018-houseofatum
int alloc()
{
int i; // [rsp+Ch] [rbp-4h]
// 只允许两个堆块同时存在
for ( i = 0; i <= 1 && *((_QWORD *)¬es + i); ++i );
if ( i == 2 )
return puts("Too many notes!");
printf("Input the content:");
// 利用notes[i]管理note,实际大小为0x50。
*((_QWORD *)¬es + i) = malloc(0x48uLL);
readn(*((void **)¬es + i), 0x48uLL);
return puts("Done!");
}
ssize_t __fastcall readn(void *a1, size_t a2)
{
return read(0, a1, a2);
}
int edit()
{
signed int v1; // [rsp+Ch] [rbp-4h]
printf("Input the idx:");
v1 = getint();
if ( (unsigned int)v1 > 1 || !*((_QWORD *)¬es + v1) )
return puts("No such note!");
printf("Input the content:");
// 读取0x48可能存在泄露
readn(*((void **)¬es + v1), 0x48uLL);
return puts("Done!");
}
unsigned __int64 del()
{
signed int v1; // [rsp+0h] [rbp-10h]
char v2[2]; // [rsp+6h] [rbp-Ah] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("Input the idx:");
v1 = getint();
if ( (unsigned int)v1 <= 1 && *((_QWORD *)¬es + v1) )
{
free(*((void **)¬es + v1));
printf("Clear?(y/n):");
// 输入n,可以导致UAF漏洞。
readn(v2, 2uLL);
if ( v2[0] == 'y' )
*((_QWORD *)¬es + v1) = 0LL;
puts("Done!");
}
else
{
puts("No such note!");
}
return __readfsqword(0x28u) ^ v3;
}
int show()
{
signed int v1; // [rsp+Ch] [rbp-4h]
printf("Input the idx:");
v1 = getint();
if ( (unsigned int)v1 > 1 || !*((_QWORD *)¬es + v1) )
return puts("No such note!");
printf("Content:");
puts(*((const char **)¬es + v1));
return puts("Done!");
}
看雪ID:jelasin
https://bbs.kanxue.com/user-home-958172.htm
#
原文始发于微信公众号(看雪学苑):Tcache安全机制及赛题详细解析
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论