정말 간만의 포너블 공부.. mac 사고 포너블 세팅을 미루다 미루다가 깃블로그 만든 김에 슈슈슉 해버리자 다짐했다.
free
함수로 청크 해제 -> ptmalloc2는 이를 tcache나 bins에 추가/관리
tcache나 bins를 어떠한 List라고 했을 때, free
는 청크를 추가하는 함수, malloc
은 청크를 꺼내는 함수이다. 임의의 청크에 대해 free
는 두 번 적용이 가능하다. 왜냐면 프리했다가 말록한 거 다시 프리할 수 있으니까.
만약 청크가 중복되면 duplicated 되었다고 하는데, 이 duplicated free list를 활용하면 임의 주소에 청크를 할당할 수 있다고 한다(어떻게?) 그래서 이 임의 주소에 할당한 청크 값을 읽거나 작성하는 공격을 진행할 수 있다.
단순히 요약하자면, 같은 청크를 중복해서 해제할 수 있으면 DFB로 분류한다.
Double Free Bug
ptmalloc2
에서 발생하는 버그이며, 공격자가 임의 주소 쓰기/읽기, 코드 실행 등으로 활용될 수 있다.
DFB는 보통 코드상에 아래 방법으로 살펴볼 수 있다.
- dangling pointer가 생성되는가?
- 이를 대상으로 Free 호출이 가능한가?
이 double free bug로는 duplicated free list 생성이 가능하다. ptmalloc2
에서 Free list의 각 청크는 fd와 bk로 연결된다.
- fd
- 자신보다 나중에 해제된 청크
- bk
- 자신보다 먼저 해제된 청크
해제된 청크는 각각의 공간을 그냥 데이터 저장에만 사용한다. 때문에 어떤 청크가 Free list에 중복되어 포함된다면, 첫번째 재할당에서 fd와 bk를 조작하여 임의 주소를 삽입할 수 있다.
// 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); // Free again
}
위 코드에서 malloc으로 청크 꺼내오고, 이 청크 free를 두 번 진행한다.
Mitigation for Tcache DFB
static
패치된 Tcache의 코드를 살펴보자.
tcache_entry
typedef struct tcache_entry {
struct tcache_entry *next;
+ /* This field exists to detect double frees. */
+ struct tcache_perthread_struct *key;
} tcache_entry;
해당 구조체에 double frees를 탐지하기 위한 key 포인터가 추가되었다. 이 구조체는 해제된 Tcache 청크들이 갖는 구조이다. fd가 Next로 대체된다.
tcache_put
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에 추가하는 함수가 tcache_put이다. 왼쪽에 +로 된 부분을 보면, e->key에는 tcache를 넣도록 되어 있다. 이때 tcache의 타입은 key의 포인터로 선언했던 tcache_perthread_struct이다.
tcache_get
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에 연결된 청크를 재사용할 때 사용하는 함수이다. e->key를 Null로 설정한다.
_int_free
청크를 해제할 때는 위 _int_free 함수를 호출한다. 이부분이 중요한데, 청크의 Key값을 참조하여 tcache값이 담겨 있으면 DF로 취급하여 abort시킨다.
+ 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. */
+ }
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;
}
free를 하고, chunk+8이 Key의 위치이기 때문에 이부분을 Null로 바꿔준다. 이후에 free를 하면 double free bug를 트리거할 수 있다.