[Dreamhack] Double Free Bug: 1 - Double Free Bug

securitykss·2023년 1월 31일
0

Pwnable 강의(dreamhack)

목록 보기
33/58

https://dreamhack.io/lecture/courses/116 을 토대로 작성한 글입니다.

1. Introduction

free 함수로 청크를 해제하면, ptmalloc2는 이를 tcache나 bins에 추가해서 관리한다.

이 후, malloc으로 비슷한 크기의 동적 할당이 발생하면, 이 연결리스트들을 탐색하여 청크를 재할당해준다.

tcache와 bins를 free list라고 한다면, free list의 관점에서 free는 청크를 추가하는 함수, malloc은 청크를 꺼내는 함수이다.

그러므로, 임의의 청크에 대해 free를 두 번이상 적용을 할 수 있다는 것은, 청크를 free list에 여러 번 추가할 수 있다.

청크가 free list에 중복해서 존재하면 청크가 duplicated됐다고 한다.

이는 duplicated free list를 이용하면 임의 주소에 청크를 할당할 수 있음을 알 수 있었다.(해커들이 발견함)

이렇게 할당한 청크의 값을 읽거나 조작함으로써 해커는 임의 주소 읽기 또는 쓰기를 할 수 있다.

이런 이유로, 같은 청크를 중복해서 해제할 수 있는 코드는 보안상의 약점으로 분류되어 Double Free Bug라고 부른다.

2. Double Free Bug

DFB는 같은 청크를 두 번 해제할 수 있는 버그를 말한다.

ptmalloc2에서 발생하는 버그 중 하나이며, 공격자에게 임의 주소 쓰기, 임의 주소 읽기, 임의 코드 실행, 서비스 거부 등의 수단으로 활용될 수 있다.

Dangling Pointer는 Double Free Bug를 유발하는 대표적인 원인이다.

코드 상에서 Dangling Pointer가 생성되는지, 그리고 이를 대상으로 free를 호출하는 것이 가능한지 살피면 Double Free Bug가 존재하는지 가늠할 수 있다.

Double Free Bug를 이용하면 duplicated free list를 만드는 것이 가능한데, 이는 청크와 연결리스트의 구조때문이다.

ptmalloc2에서, free list의 각 청크들은 fd와 bk로 연결된다.

fd는 자신보다 이후에 해제된 청크를, bk는 이전에 해제된 청크를 가리킨다.

해제된 청크에서 fd와 bk 값을 저장하는 공간은 할당된 청크에서 데이터를 저장하는 데 사용된다.

그러므로 만약 어떤 청크가 free list에 중복해서 포함된다면, 첫 번째 재할당에서 fd와 bk를 조작하여 free list에 임의 주소를 포함시킬 수 있다.

예전에는, Double Free에 대한 검사가 제대로 이뤄지지 않아 Double Free Bug가 있으면 트리거를 할 수 있었다.

특히, tcache와 관련 보호 기법이 없어 최근까지도 접근이 가능했다.

하지만, glibc에 관련 보호 기법이 구현되면서, 이를 우회하지 않으면 같은 청크를 두번 해제하는 즉시 프로세스가 종료된다.

Tcache Double Free

// Name: dfb.c
// Compile: gcc -o dfb dfb.c

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

int main() {
	char *chunk;
    chunk = malloc(0x50);
    
    printf("Address of chunk: %p\n", chunk);
    
    free(chunk);
    free(chunk);
}

tcache에 대한 Double Free가 감지되어 프로그램이 비정상 종료되는 것을 확인할 수 있다.

3. Mitigation for Tcache DFB

3.1 정적 패치 분석

3.1.1 tcache_entry

tcache에 도입된 보호 기법을 분석하기 위해, diff 를 살펴 보자

밑에 코드를 보면 double free를 탐지하기 위해 key 포인터가 tcache_entry에 추가 되었다.

tcache_entry는 해제된 tcache 청크들이 갖는 구조이다.

일반 청크의 fd가 next로 대체되고, LIFO로 사용되므로 bk에 대응되는 값은 없다.

typedef struct tcache_entry {

  struct tcache_entry *next;

+ /* This field exists to detect double frees.  */
+ struct tcache_perthread_struct *key;

} tcache_entry;

3.1.2 tcache_put

tcache_put은 해제한 청크를 tcache에 추가하는 함수이다.

밑에 코드를 보면 tcache_put 함수는 해제되는 청크의 key에 tcache라는 값을 대입하도록 변경되었다.

여기서 tcache는 tcache_perthread라는 구조체 변수를 가리킨다.

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

}

3.1.3 tcache_get

tcache_get은 tcache에 연결된 청크를 재사용할 때 사용하는 함수이다.

밑에 코드를 보면 tcache_get함수는 재사용하는 청크의 key값에 NULL을 대입하도록 변경되었다.

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;

 }

3.1.4 _int_free

_int_free은 청크를 해제할 때 호출되는 함수이다.

밑에 코드를 보면, 재할당하려는 청크의 key 값이 tcache이면 Double Free가 발생했다고 보고 프로그램을 abort시킨다.

그 외의 보호 기법은 없고, "if (__glibc_unlikely (e->key == tcache))"부분만 통과 하면 Double Free를 일으킬 수 있다.

3.2 동적 분석

gdb로 분석해 보자

1. main에서 malloc으로 메모리를 할당 받은 후에 breakpoint를 걸고 실행

2. heap으로 할당된 청크 확인(0x555555756260 주소)

3. 청크가 담고 있는 내용 확인, 아직 텅 비어있다.

4. set으로 gdb 변수로 chunk 설정, 이 후 청크의 내용을 보기 위해서

5. free된 후에 breakpoint를 걸고 계속

chunk를 출력해보면, chunk의 key값을 확인할 수 있다.

key 값은 0x555555756010

5. tcache_perthread 확인

이 주소(0x555555756010)의 메모리 값을 조회하면, 해제한 chunk의 주소 0x555555756260이 entry에 포함되어 있는데, 이는 tcache_perthread에 tcache들이 저장되기 때문이다.

이 상태에서 실행을 진행하면 key값을 변경하지 않고, 다시 free를 호출하므로 abort가 발생한다.

3.3 우회 기법

"if (__glibc_unlikely (e->key == tcache))"만 통과하면 tcache 청크를 Double Free 시킬 수 있다.

+       /* 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;
+         }
       }

해제된 청크의 key값을 1비트만이라도 바꿀 수 있으면, 이 보호 기법을 우회할 수 있다.

4. Tcache Duplication


// Name: tcache_dup.c

// Compile: gcc -o tcache_dup tcache_dup.c

#include <stdio.h>

#include <stdlib.h>

int main() {

  void *chunk = malloc(0x20);

  printf("Chunk to be double-freed: %p\n", chunk);

  free(chunk);

  *(char *)(chunk + 8) = 0xff;  // manipulate chunk->key

  free(chunk);                  // free chunk in twice

  printf("First allocation: %p\n", malloc(0x20));

  printf("Second allocation: %p\n", malloc(0x20));

  return 0;

}

이 코드는 tcache에 적용된 double free 보호 기법을 우회해서 Double Free Bug를 트리거하는 코드이다.

실행 결과는 이렇다.

chunk가 tcache에 중복 연결되어 연속으로 재할당되는 것을 확인할 수 있다.

마치며

tcache_entry: 해제된 tcache 청크를 나타내는 구조체. 각 청크는 next라는 멤버 변수로 연결됨. Double free 보호 기법이 적용되면서, key라는 멤버 변수가 추가됨.

tcache_perthread_struct: tcache를 처음 사용하면 할당되는 구조체.

Double Free Bug: 한 청크를 두 번 해제할 수 있는 버그.

Tcache Duplication: tcache에 같은 청크가 두 번 연결되는 것. double free bug로 발생시킬 수 있으며, tcache poisoning으로 응용될 수 있음.

Reference

https://dreamhack.io/lecture/courses/116

profile
보안 공부를 하는 학생입니다.

0개의 댓글