tcache attack

Tcache attack|Tcache bin的结构

glibc用tcache_entry和tcache_perthread两个结构体来管理tcache bin

1、tcache_entry

2、当我们分配的chunk释放后进入tcache bin时,就会按这个结构体来按链表存储,next指向 下一个tcacgebin,key指向当前线程的tcache perthread struct(低版本的libc没有这个key),主要是用来防范double free

比较新的版本的tcache_entry如下(glibc-2.33)

 

 

tcache_perthread_struct如下

 

 

  • Tcache perthread struct在 每- - 个线程中均有一个,用来管理当前线程中所有的tcache_ bins

  • Entries用来存储各个大小的tcache bin链表,TCAHE_ MAX_ _BINS默认值为64,也就是0x20, 0x30....0x4 10(64位)大小的chunk被释放后都有可能放入tcache bins里面

  • counts结构体用来记录各个大小的tcache bin数量,最大为7

Tcache attack|分配机制

我们malloc时,会将size转化为对 应大小的tcache idx,然后判断这个idx是否有tcache bin,如果有,会将tcache 取出,然后将他的next链表指向的值赋值给tcache->entries[tc_ idx] (使其指向下一-个tcache bin或null),再递减当前大小tcache counts(数量)

_libc_malloc的函数

__libc_malloc (size_t bytes)
{
 mstate ar_ptr;
 void *victim;

 _Static_assert (PTRDIFF_MAX <= SIZE_MAX / 2,
                 "PTRDIFF_MAX is not more than half of SIZE_MAX");

 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;
 if (!checked_request2size (bytes, &tbytes))
  {
     __set_errno (ENOMEM);
     return NULL;
  }
 size_t tc_idx = csize2tidx (tbytes);

 MAYBE_INIT_TCACHE ();

 DIAG_PUSH_NEEDS_COMMENT;
 if (tc_idx < mp_.tcache_bins
     && tcache
     && tcache->counts[tc_idx] > 0)
  {
     victim = tcache_get (tc_idx);
     return TAG_NEW_USABLE (victim);
  }
 DIAG_POP_NEEDS_COMMENT;
#endif

 if (SINGLE_THREAD_P)
  {
     victim = TAG_NEW_USABLE (_int_malloc (&main_arena, bytes));
     assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
     &main_arena == arena_for_chunk (mem2chunk (victim)));
     return victim;
  }

 arena_get (ar_ptr, bytes);

 victim = _int_malloc (ar_ptr, bytes);
 /* Retry with another arena only if we were able to find a usable arena
    before. */
 if (!victim && ar_ptr != NULL)
  {
     LIBC_PROBE (memory_malloc_retry, 1, bytes);
     ar_ptr = arena_get_retry (ar_ptr, bytes);
     victim = _int_malloc (ar_ptr, bytes);
  }

 if (ar_ptr != NULL)
   __libc_lock_unlock (ar_ptr->mutex);

 victim = TAG_NEW_USABLE (victim);

 assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
         ar_ptr == arena_for_chunk (mem2chunk (victim)));
 return victim;
}

其中有一个判读语句

 if (tc_idx < mp_.tcache_bins
     && tcache
     && tcache->counts[tc_idx] > 0)
  {
     victim = tcache_get (tc_idx);
     return TAG_NEW_USABLE (victim);
  }

tcache_get中的代码如下

tcache_get (size_t tc_idx)
{
 tcache_entry *e = tcache->entries[tc_idx];
 if (__glibc_unlikely (!aligned_OK (e)))
   malloc_printerr ("malloc(): unaligned tcache chunk detected");
 tcache->entries[tc_idx] = REVEAL_PTR (e->next);
 --(tcache->counts[tc_idx]);
 e->key = NULL;
 return (void *) e;
}

_libc_free函数如下

void
__libc_free (void *mem)
{
 mstate ar_ptr;
 mchunkptr p;                          /* chunk corresponding to mem */

 void (*hook) (void *, const void *)
   = atomic_forced_read (__free_hook);
 if (__builtin_expect (hook != NULL, 0))
  {
    (*hook)(mem, RETURN_ADDRESS (0));
     return;
  }

 if (mem == 0)                              /* free(0) has no effect */
   return;

#ifdef USE_MTAG
 /* Quickly check that the freed pointer matches the tag for the memory.
    This gives a useful double-free detection. */
 *(volatile char *)mem;
#endif

 int err = errno;

 p = mem2chunk (mem);

 /* Mark the chunk as belonging to the library again. */
(void)TAG_REGION (chunk2rawmem (p), CHUNK_AVAILABLE_SIZE (p) - CHUNK_HDR_SZ);

 if (chunk_is_mmapped (p))                       /* release mmapped memory. */
  {
     /* See if the dynamic brk/mmap threshold needs adjusting.
Dumped fake mmapped chunks do not affect the threshold. */
     if (!mp_.no_dyn_threshold
         && chunksize_nomask (p) > mp_.mmap_threshold
         && chunksize_nomask (p) <= DEFAULT_MMAP_THRESHOLD_MAX
 && !DUMPED_MAIN_ARENA_CHUNK (p))
      {
         mp_.mmap_threshold = chunksize (p);
         mp_.trim_threshold = 2 * mp_.mmap_threshold;
         LIBC_PROBE (memory_mallopt_free_dyn_thresholds, 2,
                     mp_.mmap_threshold, mp_.trim_threshold);
      }
     munmap_chunk (p);
  }
 else
  {
     MAYBE_INIT_TCACHE ();

     ar_ptr = arena_for_chunk (p);
     _int_free (ar_ptr, p, 0);
  }

 __set_errno (err);
}

其中调用了_int_free,其中有部分的代码如下

#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 in
  2^<size_t> chance), so verify it's not an unlikely
  coincidence before aborting. */
if (__glibc_unlikely (e->key == tcache))
{
   tcache_entry *tmp;
   size_t cnt = 0;
   LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
   for (tmp = tcache->entries[tc_idx];
tmp;
tmp = REVEAL_PTR (tmp->next), ++cnt)
    {
if (cnt >= mp_.tcache_count)
 malloc_printerr ("free(): too many chunks detected in tcache");
if (__glibc_unlikely (!aligned_OK (tmp)))
 malloc_printerr ("free(): unaligned chunk detected in tcache 2");
if (tmp == e)
 malloc_printerr ("free(): double free detected in tcache 2");
/* If we get here, it was a coincidence. We've wasted a
  few cycles, but don't abort. */
    }
}

if (tcache->counts[tc_idx] < mp_.tcache_count)
{
   tcache_put (p, tc_idx);
   return;
}
    }
}
#endif

在上述代码的最后一个if语句中,成立后调用tcache_put,其代码如下

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 will
    detect a double free. */
 e->key = tcache;

 e->next = PROTECT_PTR (&e->next, tcache->entries[tc_idx]);
 tcache->entries[tc_idx] = e;
 ++(tcache->counts[tc_idx]);
}

我们free时,首先会检测当前chunk大小是否属于tcache bin,再检测key值,防止double free,最后再看当前大小tcache数量,如果小于7(默认),就会调用tcache_ puts将 这块chunk放入对应大小的tcahce,放入时,首先会设置好key值,然后再将next指针指向原当前大小的tcache链表,再结合malloc的代码,我们可以知道tcache bin也是- -个先进后出的结构

Tcache attack|利用原理

Tcache bin攻击方式较为简单,只 要能篡改next指针,malloc 下没有对指针的合法性和堆块大小检测,所有只要能篡改next指针, 就能申请下任意的内存,达到任意地址写

posted @ 2022-03-07 21:54  vi0let  阅读(546)  评论(0编辑  收藏  举报