*** 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와 같은 구조체 포인터만 존재한다.
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]);
}
주소가 유효한지, 크기가 유효한지 검사하지만,
Double-Free-Bug를 확인하지 않는다.
'System Hacking > Study Notes' 카테고리의 다른 글
pwntool 함수 (0) | 2020.01.07 |
---|---|
LD 와 libc.so.6 (0) | 2019.12.29 |
Address SANitizer 미완 (0) | 2019.11.04 |
잡기술 (0) | 2019.09.11 |
Unsafe_Unlink (0) | 2019.09.05 |