libc 2.32 의 Safe-linkng 을 알아보자

Osori·2021년 8월 30일
0

시스템 해킹을 공부하는 입장에서 heap 과 관련된 취약점은 굉장히 매력적이라고 할 수 있다. 대표적으로 heap spray만 봐도 그렇다. 그리고 fastbin 과 libc 2.26 부터 추가된 tcache는 보안적인 관점에서 mitigation 을 우회하기가 쉽다는 것도 유명한 이야기다.

당연히 개발자들도 이를 모를리는 없겠고, 이대로는 안되겠다고 생각하였는지 Safe-linkng 이라는 일종의 메모리 암호화의 과정을 수행하였다. 이를 통해서 결론적으로 heap leak 혹은 libc leak 없이는 heap overflow가 나는 상황에서 메모리 변조를 하는 것은 힘들어진다.

일단 libc 2.32의 malloc.c에는 아래와 같이 safe-linkng에 대해서 설명을 하고 있다.

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.

무슨 소리인지는 잘 모르겠지만 포인터 하이재킹을 막기 위한 수단으로 ASLR의 랜덤성을 사용한다는 것 같다. 코드를 보도록 하자. 아래는 safe-linkng 과 관련된 매크로 함수이다.

#define PROTECT_PTR(pos, ptr) \
  ((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr)))
#define REVEAL_PTR(ptr)  PROTECT_PTR (&ptr, ptr)

PROTECT_PTR 매크로는 pos 인자를 3바이트 오른쪽 시프트 연산을 하고 이를 ptr 과 XOR 한다. REVEAL_PTR 매크로는 ptr 의 주소와 값을 인자로 하여 PROTECT_PTR 매크로에 넣어준다.

이 매크로들이 실제로 사용되는 함수는 tcache_gettcache_put 그리고 int_malloc , int_free 함수이다.

/* Caller must ensure that we know tc_idx is valid and there's room
   for more chunks.  */
static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
  tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
  e->key = tcache;
  e->next = PROTECT_PTR (&e->next, tcache->entries[tc_idx]); //추가된 부분
  tcache->entries[tc_idx] = e;
  ++(tcache->counts[tc_idx]);
}


static __always_inline void *
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;
}

소스코드를 보면 알 수 있듯이 fastbin, tcache에 청크를 넣을 때는 PROTECT_PTR 매크로를 청크를 가지고 올 때는 REVEAL_PTR 매크로가 사용되는 것을 알 수 있다.

이 두 매크로는 XOR의 단순 원리(A^B=C 일때 A^C=B)를 이용한 암호화, 복호화이다.
PROTECT_PTR (&e->next, tcache->entries[tc_idx]) 의 첫번째 인자, 두번째 인자, 결과를 각각 A,B,C 라고 두었을 때 REVEAL_PTR (e->next) 는 결과적으로 A>>12^C이기 때문에 원래의 next에 들어있는 정상 포인터인 B를 얻을 수 있는 것이다.

코드를 보면 알겠지만 Safe-linking은 청크가 한개만 bin에 들어있을시에는 별 효과가 없다. 그러나 2개이상 free된 청크가 존재할 시에 next 에 유효한 포인터가 들어있지 않기 때문에 단순한 오버플로가 난다고 heap의 주소를 모르면 XOR 연산을 하면서 값이 뭉게지기 때문에 잘못된 포인터를 참조하게 되고, 크래시가 날 수는 있어도 exploit은 막을 수 있다.

처음에 heap 또는 libc 의 주소라고 하였는데 fastbin의 경우에는 fastbin들이 libc의 main_arena에 존재하기 때문에 힙과 libc의 주소를 둘다 알아야겠죠?

그리고 파회법들이 나오기 시작했다. 힙풍수 그리고 Tcache Stashing Unlink 공격이라는 이름은 거창한데 결국 smallbin 으로 tcache가 들어가는 과정을 악용한 공격기법을 사용해서 leak 없이 시작하여 stdout을 이용한 leak을 통해 RCE를 할 수 있다고 한다.
https://github.com/c4ebt/House-of-Rust
https://smallkirby.hatenablog.com/entry/safeunlinking

+큰일났다! pwndbg 이거 암호화 풀어줘요!!

profile
해킹, 리버싱, 게임 좋아합니다

0개의 댓글