System Hacking/Study Notes

Tcache_Duplicate

S!_Jmini 2019. 10. 8. 12:14

*** tcache 를 통해 heap이 관리되는 버전인 glibc-2.27 (Ubuntu 18.04↑) 을 기준으로 작성되었습니다 ***

 

Tcache는 heap을 처리하는데 들어가는 시간을 줄이기 위해 만들어졌다.

 

이전 버전 glibc의 fastbin 동작원리와 비슷하다.

 

 

 

#include <stdio.h>
#include <stdlib.h>

int main()
{
	fprintf(stderr, "This file demonstrates a simple double-free attack with tcache.\n");

	fprintf(stderr, "Allocating buffer.\n");
	int *a = malloc(8);

	fprintf(stderr, "malloc(8): %p\n", a);
	fprintf(stderr, "Freeing twice...\n");
	free(a);
	free(a);

	fprintf(stderr, "Now the free list has [ %p, %p ].\n", a, a);
	fprintf(stderr, "Next allocated buffers will be same: [ %p, %p ].\n", malloc(8), malloc(8));

	return 0;
}

 

 

위의 코드가 간단해서 보면 알겠지만 Tcache에서도 Double-Free-Bug가 발생한다.

 

왜 발생하는지 Tcache에 대해서 궁금해지지 않는가?! 

 

 

 

< tcache_init >

 

static void
tcache_init(void)
{
  mstate ar_ptr;
  void *victim = 0;
  const size_t bytes = sizeof (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_perthread_struct *) victim;
      memset (tcache, 0, sizeof (tcache_perthread_struct));
    }
​
}

 

 

 

< tcache_entry >

 

typedef struct tcache_entry
{
  struct tcache_entry *next;
} tcache_entry;
​
/* There is one of these for each thread, which contains the
   per-thread cache (hence "tcache_perthread_struct").  Keeping
   overall size low is mildly important.  Note that COUNTS and ENTRIES
   are redundant (we could have just counted the linked list each
   time), this is for performance reasons.  */
typedef struct tcache_perthread_struct
{
  char counts[TCACHE_MAX_BINS];
  tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;
​
static __thread char tcache_shutting_down = 0;
static __thread tcache_perthread_struct *tcache = NULL;

 

 

 

 

fastbin과 같이 다음 청크를 가르키는 fd와 같은 구조체 포인터만 존재한다.

 

 

 

#define TCACHE_MAX_BINS 64;

 

tcache_entry 는 tcache_perthread_struct 에 의해 관리된다

 

 

 

 

 

 

 

 

1. tcache를 통해 관리되는 청크는 Prev_size 가 존재하지 않는다        

2. fd(next) 는 다음 청크의 data(next) 부분을 가르킨다                    

3. 병합을 하지 않기때문에 Prev_inuse bit 를 설정하지 않는다            

4. tcache_entry를 관리하는 tcache_perthread_struct 는 힙에 존재한다

 

 

 

 

 

 

 

< _int_free USE_TCACHE & _int_malloc USE_TCACHE >

 

//-----free-----

static void *
_int_free(mstate av, mchunkptr p, int have_lock)
{
	//skip

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

//...
//...

//-----malloc-----

static void *
_int_malloc (mstate av, size_t bytes)
{
	//skip
    
#if USE_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&& (pp = *fb) != NULL)
        {
          REMOVE_FB (fb, tc_victim, pp);
          if (tc_victim != 0)
          {
          	tcache_put (tc_victim, tc_idx);
          }
        }
    }
#endif
	//skip    
}

 

 

 

 

 

< tcache_put >

 

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

 

 

 

 

_int_free 내부에 있는 조건문

 

주소가 유효한지, 크기가 유효한지 검사하지만,

Double-Free-Bug를 확인하지 않는다.