Double Free Bug란
동적 할당한 메모리를 free()를 통해 메모리 해제시, ptmalloc에 의해 tcache 또는 bins(free list)에 청크가 들어가게 된다.
임의의 청크에 대해 free()를 여러번 하게 되면, 같은 청크가 free list에 여러번 추가되게 되고, 해당 청크는 duplicated 상태가 된다.
duplicated free list 이용시 임의 주소에 청크를 할당하여 값을 읽거나 조작할 수 있다.
-> 임의 주소 읽거나 쓰기, 임의 코드 실행 가능 = Double Free Bug
chunk 구조와 DFB 취약점 찾기


DFB 취약점 또한 UAF취약점 처럼 댕글링 포인터(동적으로 할당되었다 해제된 메모리를 가리키는 포인터)의 존재 여부를 확인하는게 좋다.
찾은 댕글링 포인터가 가리키는 청크를 대상으로 한번더 free()가 가능한지 확인해보자. free list안에서 각 청크들은 fd와 bk로 연결이 되는데, 해제된 청크에서 fd와 bk를 저장하는 공간은 할당된 청크에서 데이터를 담는 데 사용된다.
-> 어떤 청크가 free list에 중복하여 포함된다면, fd와 bk를 조작하여 임의 주소를 free list에 포함시키는 것이 가능해진다.
Glibc 2.27 의 tcache bin dup
이번 포스팅에서는 glibc 2.27에서 tcache에 일어나는 DFB를 알아보려고 한다.
glibc 2.26이상의 버전부터 도입된 tcache에는 DFB를 보호하는 기법이 전혀 없어서 공격하기 쉬운 대상이었으나, 시간이 지나며 보호기법이 등장하였다. 이를 우회하지 않을 경우 같은 청크를 두번 free()하면 프로그램이 종료되게 된다.
tcache에 추가된 DFB 보호 기법을 확인해보자.
typedef struct tcache_entry
{
struct tcache_entry *next;
+ /* This field exists to detect double frees. */
+ struct tcache_perthread_struct *key;
} tcache_entry;
tcache_entry 구조체는 tcache에 들어가는 청크들이 가지는 구조로, fd가 next포인터로 사용되며,
DFB를 검사하기 위해 key 포인터가 추가되었다.
tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
assert (tc_idx < TCACHE_MAX_BINS);
+
+ /* Mark this chunk as "in the tcache" so the test in _int_free will
+ detect a double free. */
+ e->key = tcache;
+
e->next = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}
tcache_put은 해제된 청크를 tcache에 추가하는 함수이다.
tcache_entry 구조체 내의 key에 tcache_perthread 라는 구조체를 가리키는 tcache라는 값을 넣어준다.
tcache_get (size_t tc_idx)
assert (tcache->entries[tc_idx] > 0);
tcache->entries[tc_idx] = e->next;
--(tcache->counts[tc_idx]);
+ e->key = NULL;
return (void *) e;
}
tcache_get은 tcache에 연결된 청크를 재할당 할 때 사용하는 함수이다.
할당시 기존 key에 들어있던 값을 NULL로 초기화해주고 있다.
_int_free (mstate av, mchunkptr p, int have_lock)
#if USE_TCACHE
{
size_t tc_idx = csize2tidx (size);
-
- if (tcache
- && tc_idx < mp_.tcache_bins
- && tcache->counts[tc_idx] < mp_.tcache_count)
+ if (tcache != NULL && tc_idx < mp_.tcache_bins)
{
- tcache_put (p, tc_idx);
- return;
+ /* 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;
+ LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
+ for (tmp = tcache->entries[tc_idx];
+ tmp;
+ tmp = tmp->next)
+ 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
_int_free는 청크를 해제할 때 호출되는 함수로, 재할당 하려는 청크의 key 값이 tcache이면 DFB가 발생한것으로 보고 프로그램 실행을 강제로 종료시킨다.
-> e->key == tcache 이 부분만 우회하면 되므로, key의 값을 어떤 값으로 라도 덮어써서 tcache와 달라지게 한다면 DFB가 발생하게 할 수 있다.
'PWN > 개념' 카테고리의 다른 글
| Pwntools를 이용한 ROP 페이로드 작성 방법 (2) | 2025.07.30 |
|---|---|
| Format String Bug(FSB) 취약점 (1) | 2025.07.27 |
| Use After Free(UAF) 취약점 (2) | 2025.07.25 |
| ptmalloc2 - Memory Allocator in Linux (1) | 2025.07.24 |
| one_gadget (0) | 2025.07.23 |