https://github.com/bminor/glibc/tree/master
여기서 git clone 및 git pull 해서 립씨 파일 살펴봐도 되고
https://elixir.bootlin.com/glibc/latest/source/malloc/malloc.c#L3946
이번 문제 풀면서는 이 온라인 사이트를 참고했다
tcache bins에 담기는 chunk는 아래와 같은 형태이다
typedef struct tcache_entry
{
struct tcache_entry *next;
/* This field exists to detect double frees. */
uintptr_t key;
} tcache_entry;
tcache bins이 아래와 같은 상황일 때
chunk의 next 포인터가 무언가 조작(?)이 되어있는 모습이다
pointer hijacking을 막기 위해 next pointer에 safe linking / safe unlinking이 적용되어 있는 상황이다
libc 코드에서 살펴보면
static __always_inline void
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_key;
e->next = PROTECT_PTR (&e->next, tcache->entries[tc_idx]);
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}
- tcache에 chunk 집어넣을 때 PROTECT_PTR() 매크로를 거치고
static __always_inline void *
tcache_get_n (size_t tc_idx, tcache_entry **ep)
{
tcache_entry *e;
if (ep == &(tcache->entries[tc_idx]))
e = *ep;
else
e = REVEAL_PTR (*ep);
if (__glibc_unlikely (!aligned_OK (e)))
malloc_printerr ("malloc(): unaligned tcache chunk detected");
if (ep == &(tcache->entries[tc_idx]))
*ep = REVEAL_PTR (e->next);
else
*ep = PROTECT_PTR (ep, REVEAL_PTR (e->next));
--(tcache->counts[tc_idx]);
e->key = 0;
return (void *) e;
}
- tcache에서 chunk를 꺼내올 때 REVEAL_PTR() 매크로를 거쳐 next 포인터를 변환해 가져온다
/* Safe-Linking:
Use randomness from ASLR (mmap_base) to protect single-linked lists
of Fast-Bins and TCache. That is, mask the "next" pointers of the
lists' chunks, and also perform allocation alignment checks on them.
This mechanism reduces the risk of pointer hijacking, as was done with
Safe-Unlinking in the double-linked lists of Small-Bins.
It assumes a minimum page size of 4096 bytes (12 bits). Systems with
larger pages provide less entropy, although the pointer mangling
still works. */
#define PROTECT_PTR(pos, ptr) \
((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr)))
#define REVEAL_PTR(ptr) PROTECT_PTR (&ptr, ptr)
- 다음 chunk의 포인터 주소와 (p->next 주소 >> 12) 값을 xor 해서 masking을 한다
- 물론 heap 주소는 ASLR에 의해 랜덤하게 매핑되므로 어느정도의 보안 효과가 있겠지만 무척이나 취약하다
공략 1) 만약 free chunk의 data를 읽어올 수 있다면 [UAF 취약점]
* PROTECT_PTR() 매크로에서 next ptr과 xor하는 값을 편하게 masking key라고 하자
* 알다싶이 tcache chunk chain의 마지막은 NULL ( next pointer = 0x0 ) 으로 끝난다
0x30 [ 3]: 0x804b200 —▸ 0x804b1d0 —▸ 0x804b1a0 —▸ 0x0
* 즉 tcache chunk chain의 마지막 chunk의 next 포인터는 masking key이다 ( a xor a = 0 이므로). 이걸 읽어옴
=> heap 영역 내에 chunk들은 서로 가까이 분포할 것이므로 상위 5바이트 주소가 동일할 것이라고 가정 가능
=> masking key가 모든 chunk에서 동일할 것
공략 2) 만약 tcache chunk의 next pointer 한 개만 읽어올 수 있다면
* tcache chunk chain의 마지막 chunk아 아니라 중간 chunk의 next pointer 한 개만 읽어와도 공략 가능
Guardian 정재영 선배릠이 알려주심
* x86-64에서 하위 48bit만 가상메모리 주소로 사용되는데, 그 48bit를 12bit씩 쪼개서 생각해보자
* heap chunk 간 상위 36bit가 동일하다고 가정한다 (서로 가까이 분포할 것이므로)
* masking key는 다음 chunk 주소 >> 12 한 값과 동일할 것이다
32bit에서도 잘 적용되더라
'security > 가디언' 카테고리의 다른 글
lab 09 - Heap: uaf & tcache poisoning in latest libc (0) | 2023.08.26 |
---|---|
lab 09 - Heap: double free bug in old libc(<2.26) (0) | 2023.08.26 |
lab 09 - Heap: unsafe unlink 공략 (0) | 2023.08.26 |
pwntools process()에서 바이너리를 특정 버전의 libc와 함께 실행시키는 법 (0) | 2023.08.24 |
lab 07 - Race Condition (0) | 2023.08.21 |