tcache_dup
A double free puts the same chunk in the tcache list twice, so a later malloc hands back a pointer we control.
드림핵에서 미해결 문제를 보는데 tcache 라는 키워드가 들어간 문제가 쭈르륵 있어서 셋 다 풀어보기로 함.
tcache 가 들어간 워게임 문제는 힙(Heap) 익스플로잇 문제의 대표적인 유형이에요. 시스템 해킹 배우실 때 결국 마주치게 되는 핵심 개념 중 하나예요.
tcache 가 뭔지 먼저 간단히
tcache(Thread Local Cache)는 glibc(리눅스 표준 C 라이브러리)가 malloc/free 를 더 빠르게 처리하려고 만든 캐시 시스템이에요.
- free() 로 메모리를 해제하면, 그 메모리 조각이 바로 OS 로 반환되는 게 아니라 tcache 라는 “재활용 대기열”에 잠깐 보관돼요.
- 다음에 비슷한 크기로 malloc() 을 요청하면, 이 대기열에서 빠르게 꺼내줘서 속도가 빨라져요.
왜 tcache 가 해킹의 표적이 될까?
이 “재활용 대기열” 구조 자체에 검증이 허술한 부분이 있어서, 공격자가 여기를 조작하면 원하는 메모리 주소에 원하는 값을 쓸 수 있게 돼요. 즉, 메모리를 마음대로 조작할 수 있는 강력한 무기가 되는 거죠.
워게임에서 자주 나오는 tcache 문제 유형
- Double Free — 같은 메모리를 free() 로 두 번 해제하는 취약점. tcache 대기열에 같은 조각이 중복으로 들어가게 만들어서, 이후 이를 이용해 메모리 조작.
- Tcache Poisoning — free() 된 메모리 조각의 내부 데이터(다음 조각을 가리키는 포인터)를 조작. 그러면 다음 malloc() 요청 시, 공격자가 원하는 임의의 주소를 메모리로 받아올 수 있음. 이게 되면 GOT 테이블 변조, 스택 조작 등으로 이어져서 결국 셸 획득까지 가능.
- Use-After-Free (UAF) + tcache — free() 한 메모리를 실수로 계속 참조하는 취약점을 tcache 구조와 결합해서 공격.
실제 문제에서 보통 이런 흐름이에요.
- 프로그램이 malloc/free 를 자유롭게 호출할 수 있는 CLI 메뉴 제공 (추가/삭제/수정 등)
- 취약점(double free 등)을 이용해 tcache 대기열을 조작
- 원하는 주소(예: GOT 테이블, 특정 함수 포인터)에 임의 값을 쓰기
- 프로그램 흐름을 조작해서 셸(
system("/bin/sh")등) 실행
음 읽어봤는데도 아직도 잘 이해를 못함… 뭐 3개나 풀다 보면 이해가 되지 않을까? 먼저 젤 낮은 레베루부터.
// gcc -o tcache_dup tcache_dup.c -no-pie
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
char *ptr[10];
void alarm_handler() {
exit(-1);
}
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(60);
}
int create(int cnt) {
int size;
if (cnt > 10) { return -1; }
printf("Size: ");
scanf("%d", &size);
ptr[cnt] = malloc(size);
if (!ptr[cnt]) { return -1; }
printf("Data: ");
read(0, ptr[cnt], size);
}
int delete() {
int idx;
printf("idx: ");
scanf("%d", &idx);
if (idx > 10) { return -1; }
free(ptr[idx]);
}
void get_shell() {
system("/bin/sh");
}
int main() {
int idx;
int cnt = 0;
initialize();
while (1) {
printf("1. Create\n");
printf("2. Delete\n");
printf("> ");
scanf("%d", &idx);
switch (idx) {
case 1: create(cnt); cnt++; break;
case 2: delete(); break;
default: break;
}
}
return 0;
}
double free 취약점이 있다. delete() 를 보면, free(ptr[idx]); 후에 ptr[idx] = NULL; 를 하지 않는다. 따라서
Create -> ptr[0] = malloc(...)
Delete 0 -> free(ptr[0])
Delete 0 -> free(ptr[0]) // 같은 포인터를 또 free
가 가능하다. 아직 문제에 대해 너무 헷갈려 찾아보니까, 뜻을 이렇게 정리할 수 있었다.
크기 0x20짜리 청크를 하나 만들었다고 가정.
A = malloc(0x20);
이제 free(A); 를 호출하면 메모리가 사라지는 게 아니다. 실제로는 A 가 tcache 에 들어간다. 그리고 tcache 는 그냥 스택처럼 활용된다. 예시로 free(A); free(B); free(C); 를 하면 tcache 내부는 이렇게 된다.
Top C
B
Bottom A
그 다음 x = malloc(0x20); 을 하면, malloc 은 tcache 맨 위의 청크를 꺼내줘서 x == C 가 되는 것이다.
또한 get_shell() 함수가 있다. 보통 스택 익스플로잇 문제에서 이런 의미는, get_shell() 함수의 주소를 EIP 에 넣어 프로그램의 플로우를 컨트롤하라는 뜻이다. 하지만 힙 익스플로잇에서는 EIP 를 건드리는 게 아니라, EIP 가 나중에 참조할 포인터를 조작한다는 것이다. 그러니까 원래는 fp -> puts 인데, 힙 취약점으로 fp -> get_shell 로 바꾸면 나중에 fp(); 가 실행될 때 쉘이 뜨는 것이다.
이 문제는 gcc -no-pie 로 컴파일 됐다. PIE 가 꺼져 있으면 get_shell 주소, GOT 주소, BSS 주소 같은 주소들이 고정된다.
이 문제는 아직 풀지 못했다. tcache 구조를 더 공부한 뒤 다시 시도할 예정이다.


