Brainpan 1 - TryHackMe Writeup
The link for this lab is located here: https://tryhackme.com/room/brainpan
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.
Full Walkthrough
First, we run an Nmap scan to determine what ports are open with the following parameters:
-p- for all ports
10.10.173.102 for the target IP
-oN allports.scan to output to Nmap format.
This reveals two strange ports - 9999 and 10000. To gather more information about what exactly is running, we can run a more detailed Nmap scan.
It seems like port 9999 is running some sort of program called Brainpan. More interestingly, port 10000 seems to running SimpleHTTPServer which means it is probably hosting something - potentially a web page.
First, we can try navigating to that webpage and see what is there.
It seems to just be a static image - nothing really useful. As a good methodology, we can run a tool like Feroxbuster to try and find hidden directories.
An interesting result comes back for a directory of /bin.
However, before visiting that directory, we can try and use Netcat to connect to that strange service running on port 9999.
It gives us a nice banner and then asks us for a password. Entering a random password (test123) just tells us that access is denied.
Now, we can check out that /bin directory.
It seems as if the brainpan program that is running on port 9999 is the same executable that we see here.
Knowing this is a buffer overflow box, we know that we have to play with this executable file.
To start, we can download brainpan.exe and host it using a Python3 web server. Then, move to a Windows VM (in my case, Windows 10), navigate to that IP address of our Kali machine and see the file being hosted.
We can download it and start analyzing and debugging it using your favourite software - for me, I will be using Immunity Debugger located here.
Once downloaded, we simply “attach” brainpan.exe to Immunity Debugger and we get this lovely, insanely confusing looking window layout in Immunity.
After running brainpan.exe, it actually pops up a CMD terminal which we can see is actually listening on port 9999 indicating that this is the same executable running on the TryHackMe target.
The first thing to do is to fuzz the application’s inputs and see where it breaks. To do this, there are many ways, but I like using Python scripts to speed up the process.
The first script is as follows:
I won’t go into detail about what each thing does in this script. The very basics of what this script is doing is as follows:
We import the sys and socket modules in order to connect to a remote machine
We also import the sleep function so we can introduce a wait timer between sending things
A variable called “buffer” is created that contains 100 A’s
A while statement is made - while we have a connection (i.e. the program has not crashed)
A variable called “payload” is also created that contains buffer (100 A’s) but also sends a carriage return (hitting Enter on your keyboard)
A variable of s is created that simply contains the IPv4 address (INET) and port (SOCKSTREAM) which we then connect to
Print out to the user that we are sending the payload and say how many bytes we send
Encode the payload, wait a second (sleep) and then increase the buffer variable by 100
Once the program crashes (except), then report back at what bytes it crashed at
With this script made, we can then execute it via “python3 fuzzer.py”.
Here, it actually freezes after sending 900 bytes. Hitting CTRL+C reveals that the fuzzing crashed at 1000 bytes.
To verify this, we can look in Immunity and see that in the bottom right the program is paused (i.e. crashed) and the EBP and EIP (two very important registers) both have values of 41414141.
What is 41414141? This is the A’s that we sent - a capital A in hexadecimal is 41. This means that these registers now contain 4 A’s.
Now, we take this further and find out where we are overwriting the EIP at. In short, we have to control the pointer (EIP). If we can control the pointer, we can point to something malicious and allow us to execute code.
NOTE: It’s important to note that every time the program crashes, it MUST be reattached and reopened with Immunity Debugger.
In order to identify where it is overwritten, we can utilize msf-pattern_create to generate 1000 unique bytes:
With this, we can then modify the script by removing the While statement, the sleep statements, the print statement and simply modify the buffer to be this newly modified string instead of those A’s.
This new script basically just sends that new 1000 byte string to the program to see what value EIP is overwritten as.
Then, we can simply execute it.
Inside Immunity, we can see the value of EIP has become 35724134.
With this, we can then find what specific byte this is in our pattern by using msf-pattern_offset and specifying that number that was in EIP.
This tells us that there is an exact match at offset 524 - this means that the EIP is at the 525th byte.
The next thing to do is confirm this before going any further. We can edit the fuzzer.py again (fuzzer3.py) and this time we will send 524 A’s and send 4 B’s - this should lead us up to the EIP and then overwrite the EIP with 4 B’s (or 42424242).
With this script, we can execute it once again.
Then, we can look at the EIP value and see if it is what we expected.
Bingo! We see that The A’s were sent but more importantly the EIP has 42424242 - this is the 4 B’s that we sent in our payload meaning we now control the EIP.
The next step is looking for bad characters. There is already a bad character list on GitHub located here. Additionally, it already has the x00 removed which is typically a bad character.
Once again, we modify the script again and paste in the badchars under a variable and change the payload which is equal to the buffer + badchars + carriage return.
All this is doing now is sending this to the program to be able to look through and see if any characters get removed before we generate our shellcode.
NOTE: There are automated methods of doing this but some certifications (cough OSCP cough) require manual methods so that’s what I’ll cover.
Once again, we attach the program to Immunity and then run it.
After running it, the next step is to go to the ESP, right click it and select Follow in Dump.
This will go to the Hex Dump and you can see it starts from 01, 02 etc…. all the way down to FF in hex. You are looking to see if there are any bad characters in this application - takes a long time manually, but it’s better to know the manual methods.
For this program, there are none.
Before progressing, you will need something called Mona modules for Immunity Debugger which can be found here. Simply place mona.py into the folder it specifies - the GitHub has instructions.
After loading and reattaching it, we can search for any modules/protections that this program has using Mona.
To that, we can simple type “!mona modules”.
As you can see under everything for brainpan.exe we see “False” which means there is no protections. Another thing to look at and find is to find a good return address.
To do this using Mona, we can type “!mona find -s “\xff\xe4” -m brainpan.exe”
\xff\xe4 is a JMP ESP instruction
This comes back with one pointer located at 0x311712f3.
With this address, we can click the following button at the top of Immunity to open up a box where we enter the expression to follow.
After entering 0x311712f3, it takes us to a JMP ESP instruction - this is what we want.
Now, we simply select that line and hit F2 - this sets a breakpoint. This is just to ensure that we can trigger this breakpoint and that we hit the JMP instruction. Once we hit it, then we can get malicious.
Now, we can modify the Python script once again and add that address - 0x311712f3 - to our buffer string. Additionally, we get rid of the badchars section of our payload variable.
NOTE: I was having errors with the payload.encode inside my script so I manually added b in front of everything to specify bytes
Please be careful and remember that the address MUST be entered in backwards because of something called Little Endian.
Now, we can send this through and execute it. Once we execute it, in Immunity we hit the breakpoint.
This means that we are indeed hitting the JMP ESP instruction - that is perfect! Now, we simply start the server again but this time as administrator.
The next step is to generate some shellcode using MSFVenom with a reverse TCP payload on Windows. We also set the LHOST and LPORT, the bad characters of \x00 and the file type of C.
Once generated, we modify the script again by creating a new variable called shellcode and pasting in that shellcode. We also make sure the shellcode is being sent in the payload variable.
Because payload.encode was broken, I had to add a b at the start of every line.
We also add some NOPs - in this case I chose 32. A really good explanation of this can be found here.
In short, we send over 524 A’s, then we send our return address, then we send 32 NOPs of padding. Finally, we then send our shellcode.
Before running this, we start a netcat listener on the same port we used for MSFvenom shellcode generation - in my case, 7777.
Finally, we execute the final Python script.
BOOM! It worked, we got a reverse shell - we successfully owned this machine. But we’re not done.
Remember, I did this all on a local Windows machine - now we have to apply it to the TryHackMe instance.
To do that, we have to generate new shellcode using our TryHackMe IP this time.
Once generated, we have to change the shellcode variable to the new generated shellcode and also change the IP address in the Python script to the IP address of the TryHackMe target.
Once done, we can execute this bad boy.
And it works! We successfully get a reverse shell on this TryHackMe instance inside /home/puck it looks like. However, it looks a bit weird.
Our payload was a Windows payload yet this looks like a Linux filesystem.
Listing out the contents of the directory, we see a BASH script file.
Additionally, looking inside the Z: directory itself, we see a clear Linux file system structure.
This is very weird. However, we can solve this by instead using a Linux payload instead of Windows. Once again, we generate new shellcode.
With the new shellcode, we simply replace the variable inside our Python script.
NOTE: This exploit typically only runs once so the IP has changed. If you f*** up like I did, you may need to spawn a new instance.
After executing the script one more time, we get a Linux shell running as puck.
Then, just as a precaution, we stabilize the shell.
With a fully functioning shell, we can now begin privilege escalation stuff. The first couple of things I like to do is check the history and the SUDO permissions.
Checking the history reveals nothing, but we have SUDO permissions to execute something called /home/anansi/bin/anansi_util with no password.
Let’s see what it does.
Running it seems to list out options that we can run it with. We can run it with network.
Running with network seems to print out the results of the “ip a” command. What about the proclist option?
This seems to just list out the processes running. Finally, what about the manual [command] option?
This seems to open up a manual page for a specified command - in my case, ifconfig.
Knowing the commands this takes, we can search GTFOBins for these commands.
It seems as though we can spawn a shell out of the man page. Even better, because this program is running as root, it will be a root shell!
After executing the command with the man page option, we can simply enter the following to spawn a shell.
Once we hit Enter, we get a BASH shell running as the root user. From here, we can grab the final flag!