VulnHub Write-Up Brainpan 1

How I obtained root access on the Brainpan 1 virtual machine from VulnHub.

Introduction

In this long post I write a Python exploit from scratch for the Brainpan 1 vulnerable by design virtual machine from VulnHub. The post is written in a follow along kind of way to document my own buffer overflow process and in an attempt help others to understand the subject along the way. If you want to try this challenge yourself it can be downloaded here.

The process to develop the exploit in this post will follow the following eight steps:

Tools Used

Enumeration: Netdiscover

Because the VulnHub virtual machines are in a downloadable and self-hosted format the machine gets an IP address from DHCP when it starts. This means that unlike online challenges such as Hack The Box the IP address of the machine is somewhat “unknown” beforehand.

The first thing to know is the local network address by using the ifconfig command. 800x400

Knowing the network address and subnet mask Netdiscover can be leveraged to do some ARP reconnaissance and find other hosts on the local network. 800x400

The Brainpan 1 machine is hosted on the VMware software so it is safe to assume that the second entry in the list is the target as the MAC Vendor column indicates a MAC address associated with VMware. 800x400

Enumeration: Nmap

Running an initial scan with Nmap reveals ports 9999 and 10000 are open. 800x400

Running a targeted service scan against ports 9999 and 10000 reveals the Python SimpleHTTPServer and another unknown service running. 800x400

Enumeration: Firefox

Visiting the website on port 10000 with the Firefox browser reveals a page with statistics about safe coding practices. 800x400

Enumeration: Gobuster

Digging a bit deeper a Gobuster scan reveals an interesting bin directory. 800x400

Enumeration: Firefox

Visiting the bin directory with the Firefox browser reveals a downloadable executable with the name brainpan.exe. 800x400

Downloading the brainpan.exe file with wget and inspecting it with the file command reveals it is a 32-bit Windows executable file. 800x400

Enumeration: Running the Executable

Executing the application on a Windows machine reveals it is a network server waiting for connections on port 9999 indicating we most likely found the application that is listening on this port. 800x400

Enumeration: Netcat

Connecting to the application with Netcat reveals a password prompt. Entering a password reveals an access denied message, the application exits the session and returns to the command prompt. 800x400

On the Windows command prompt the application shows the password entered and the total amount of bytes copied to a buffer in memory. 800x400

Enumeration: Strings

Leveraging the strings utility on the brainpan.exe file reveals an out of place string. 800x400

Enumeration: Netcat

Leveraging Netcat to enter the discovered string in the password field reveals it is the correct password. However, like our previous attempt the application exits to the command prompt. 800x400

Debugging: Step 1 Fuzzing

Suspecting the brainpan.exe application is vulnerable to a buffer overflow attack a simple Python fuzzer can be written to test this. 800x400

The script above creates a string of 100 A characters in the variable buffer, tries to connect to the Windows host on port 9999 and sends the buffer. When done it increments the buffer with 100 A’s and then tries to connect and send the string, which is now 200 A’s again.

The fuzzer will keep increasing the buffer of A’s each time it runs until it can no longer connect to port 9999 which is an indication the application crashed and is no longer accepting connections. The script can be downloaded here.

Running the fuzzer with Python reveals it can no longer connect and shows the crash message when it sends around 600 bytes to the application. 800x400

On the Windows command prompt the buffer of A’s is displayed on the screen, followed by the bytes copied message. It is clearly visible that after this action the application exited to the command prompt and is no longer running. 800x400

Debugging: Setting Up the Debugging Environment

Now that we know the brainpan.exe application is vulnerable to a buffer overflow attack it is time to configure the debugging environment to help develop an exploit. Make sure to start Immunity Debugger as Administrator, a window looking like the one below should appear. 800x400

Take some time to get to know Immunity Debugger. Take note of the file menu, terminate and play buttons and the search button at the top of the screen. Also note the status bar all the way at the bottom. The four windows have distinct functions:

  • The top left window shows CPU instructions
  • The top right window shows the status of CPU registers
  • The bottom left window shows the memory
  • The bottom right window shows the stack

When Immunity Debugger is started we need to attach the brainpan.exe executable to the debugger to start debugging the application. In the top left menu choose File > Open and navigate to the brainpan.exe executable. When found select brainpan.exe and choose Open. 800x400

Immunity Debugger will launch the brainpan.exe application in a paused state as can be seen in status bar at the bottom right corner of the screen. 800x400

A Windows command prompt will be launched by Immunity Debugger displaying an empty window for the brainpan.exe application. This is normal because the application is still in a paused state within the debugger and is not running at the moment. 800x400

Clicking the play button in the top left will start the brainpan.exe application and display a running status in the status bar in the bottom right of the Immunity Debugger window. 800x400

Navigating back to the Windows command prompt we can see brainpan.exe is indeed running and waiting for connections. 800x400

Setting up the debugging environment as explained above should be repeated each time a new step in the debugging process is performed or when the brainpan.exe application has crashed.

Debugging: Step 2 Replicating the Crash

We know from fuzzing the brainpan.exe application in Step 1 that it crashes when around 600 bytes are sent. We will replicate this crash while the brainpan.exe application is attached to the debugger to verify what happens. 800x400

The script above is a modified version of the fuzzing script and will be used and edited in the remaining steps to develop a working exploit. A variable buffer is created which contains a string of 600 A’s. The script then connects to the application on port 9999 and sends the buffer of 600 A’s. The script can be downloaded here.

Running the python script while brainpan.exe is attached to the debugger and in a running state. 800x400

Returning to the debugger the status bar on the bottom of the screen shows an access violation. In the top right window, we see the EDX and ESP register filled with A’s and the EIP register which displays the value 41414141 which is the hexadecimal representation of the letter A stored in the buffer variable. Looking at the stack window in the bottom right of the screen we see that memory address 005FF910 which is ESP is filled with A’s as well. 800x400

Returning to the Windows command prompt where brainpan.exe was running we clearly see the buffer of A’s and the action to copy the A’s to the buffer in memory. 800x400

Now that the brainpan.exe application is in a crashed state we have to reattach and restart it again to further debug the application. Click the little cross in the top left. When asked to terminate the brainpan.exe process click yes. 800x400

Reattach the brainpan.exe application to the debugger by navigating to File > Open and clicking Play like we did in the Debugging: Setting Up the Debugging Environment step. Before continuing make sure the application is in a running state as shown in the bottom right of the screenshot below. 800x400

Debugging: Step 3 Finding the Offset to the EIP Register

To control the execution flow of the application it is important to control the EIP register. To gain control of this register the exact offset to EIP has to be found so we can fill it with whatever data we want. The ruby script pattern_create.rb can be leveraged to create a unique string of characters to determine the exact offset to the EIP register.

To do this we copy the 2-crash.py script and modify it with the output of pattern_create.rb. We make the string which will be the new buffer 650 bytes long, a bit bigger than the 600 we got from fuzzing the brainpan.exe application. 800x400

Modifying the script 3-pattern.py we add a variable called pattern and fill it with the string created by pattern_create.rb. Furthermore we modify the buffer variable to include the pattern variable we just added. The script can be downloaded here. 800x400

Running the 3-pattern.py script with Python. 800x400

The debugger again shows an access violation in the status bar at the bottom of the screen and is in a paused state. This time the EIP register is filled with a unique value instead of just A’s. The value in EIP is 35724134 and should be noted for later use. 800x400

The companion ruby script pattern_offset.rb can be leveraged to find the exact offset to the EIP register by combining it with the unique value from EIP we noted earlier. Running the script with the -l 650 and -q 35724134 parameters shows an exact offset of 524 bytes. 800x400

Make sure to reattach the brainpan.exe application to the debugger by navigating to File > Open and clicking the Play button again as explained in the Debugging: Setting Up the Debugging Environment step. Before continuing make sure the application is in a running state.

Debugging: Step 4 Controlling the EIP Register

To make sure we have the correct offset to the EIP register we will modify the 3-pattern.py script and try to put four B’s in the EIP register. If the offset of 524 is correct running the modified script 4-control-eip.py should display the four B’s in the EIP register instead of the A’s or the unique string from the previous steps. For good measure and clarity, we will pad the buffer variable with some C characters to clearly demonstrate how the buffer variable from our script is represented in memory within Immunity Debugger.

The buffer variable is modified to include 524 A’s then 4 B’s and 122 C’s. 524 + 4 + 122 = 650 keeping our buffer length the same as before. The script can be downloaded here. 800x400

Running the modified script 4-control-eip.py with Python. 800x400

As expected the application crashes and Immunity Debugger shows an access violation in the status bar at the bottom of the screen. Note however how the EDX register is now filled with A’s while the ESP register is filled with C’s. Also note the EIP register which is filled with 42424242 which represent our four B’s in hexadecimal format. The stack window in the bottom right clearly displays how our A’s are cleanly followed by four B’s and nicely continues with C’s as expected. 800x400

Following the memory dump by right clicking on the ESP register and then clicking Follow in Dump in the context menu shows how the memory is built up and clearly indicates a clean transition from A’s to the four B’s and continuing with C’s. This clearly shows how the buffer variable from our script is represented in memory within Immunity Debugger. 800x400

Make sure to reattach the brainpan.exe application to the debugger by navigating to File > Open and clicking the Play button again as explained in the Debugging: Setting Up the Debugging Environment step. Before continuing make sure the application is in a running state.

Debugging: Step 5 Finding Space for Shellcode

Now that we have confirmed control over the EIP register, can fill it with data of our choosing and know how our buffer variable is built up in memory we need to find space for our shellcode. A Windows payload is usually about 350 to 450 bytes while our C’s currently only represent 122 bytes in our buffer variable, to small of a space for 450 bytes of shellcode. The simplest way to find space is to just increase the amount of C’s in our buffer variable and test if the application still behaves the same as before.

To do this we modify the 4-control-eip.py script and increase the C’s in the buffer variable by 400 creating a total of 522 C’s. Plenty of space for a Windows reverse shell payload and some extra padding. The script can be downloaded here. 800x400

Running the 5-find-space.py script with Python. 800x400

As expected the debugger again shows an access violation. Following the memory dump by right clicking on the ESP register and then clicking Follow in Dump in the context menu again shows how the buffer is built up in memory. It is clear we now have more C’s than before and successfully increased the space needed to store our shellcode. 800x400

Make sure to reattach the brainpan.exe application to the debugger by navigating to File > Open and clicking the Play button again as explained in the Debugging: Setting Up the Debugging Environment step. Before continuing make sure the application is in a running state.

Debugging: Step 6 Finding Bad Characters

Some hexadecimal characters cannot be used in shellcode because they interfere with executing the shellcode correctly. An example of a character that is always bad is \x00 also known as a NULL character or NULL byte. This character signifies the end of a string thus cutting off the string stored in our buffer variable and cutting off the shellcode before it can fully execute.

Other bad characters depend on the application and should be found before shellcode can be generated. We know from the previous steps how the buffer variable is represented in memory as the follow in dump function clearly shows this. We can use this technique to find bad characters that should be excluded from our shellcode.

To find bad characters the 5-find-space.py script is modified with a variable badchars that includes all characters in hexadecimal format apart from the \x00 character. The buffer variable is modified to include the badchars variable instead of the C’s from the previous step. The script can be downloaded here, a file with all hex characters can be found here. 800x400

Running the 6-find-bad-characters.py script with Python. 800x400

Looking at the debugger we are greeted by the access violation again. To find bad characters we again have to leverage the follow in dump function for the ESP register and look for signs of our buffer variable being truncated anywhere. The screenshot below shows all hex characters from \x01 all the way through \xFF in memory without any truncation meaning the brainpan.exe application does not have any more bad characters. 800x400

If the string looks truncated or garbled in memory the bad character should be removed from the badchars variable in the 6-find-bad-characters.py script. When removed the script should be run again until no other bad characters are found truncating the output of the buffer variable.

Make sure to reattach the brainpan.exe application to the debugger by navigating to File > Open and clicking the Play button again as explained in the Debugging: Setting Up the Debugging Environment step. Before continuing make sure the application is in a running state.

Debugging: Step 7 Jumping to the ESP Register

As should be evident by now the ESP register is consistently filled with the data we want whether it be our buffer of A’s, C’s or the bad characters from the previous step and can conveniently store our shellcode. If we want to execute the shellcode stored in the ESP register we should find a way to redirect the execution flow of the brainpan.exe application to jump to that location in memory. This is where control of the EIP register comes into play.

To jump to ESP we should find a memory location that contains a JMP ESP instruction either within the brainpan.exe application itself or one of its loaded modules. However, before we can do this we should find the hexadecimal equivalent of a JMP ESP instruction. The nasm_shell.rb script can help with this. 800x400

Entering the instruction JMP ESP into nasm_shell.rb reveals the hexadecimal equivalent of a JMP ESP instruction is \xFF\xE4. Now we need to find a module that has no memory protections such as SafeSEH or ASLR enabled. This can be achieved with the mona.py script.

In the command window at the bottom of Immunity Debugger type !mona modules. A screen like the one below appears with all the loaded modules, their memory address and memory protections. We are looking for a module that has false across the board. False means the protection is not enabled. The only module that satisfies these criteria is the brainpan.exe application itself. 800x400

Now that we have identified a module without memory protections enabled we can leverage mona.py again to look for a memory location with a JMP ESP instruction. This can be achieved with the command !mona find -s “\xff\xe4” -m brainpan.exe. Fortunately, Mona finds a JMP ESP instruction at memory address 311712F3. 800x400

To verify if the memory address 311712F3 indeed contains a JMP ESP instruction we can search for the memory address within Immunity Debugger by clicking on the search button at the top of the screen, entering the memory address 311712F3 and then clicking OK. The debugger jumps to the address and we can see that it indeed contains a JMP ESP instruction. 800x400

To verify if we can indeed jump to ESP using this memory address the 6-find-bad-characters.py script is modified to include the memory address with the JMP ESP instruction we discovered. The buffer variable is modified with the memory address that contains the JMP ESP instruction instead of our four B’s we also add back the 522 C’s at the end of the buffer variable instead of the bad characters from the previous step. 800x400

Note that the x86 architecture uses memory address in little endian format, this means we have to enter the memory address in reverse. In other words, the memory address 31 17 12 F3 should be noted in hexadecimal format as follows \xF3 \x12 \x17 \x31 within our buffer variable. The modified script can be downloaded here.

Before we run the 7-jump-to-esp.py script we will set a breakpoint on the memory address that contains the JMP ESP instruction within Immunity Debugger. We do this to instruct the debugger to pause before executing instructions beyond that point. This is so we can follow exactly what happens. In Immunity Debugger click on the memory address with the JMP ESP instruction and press the F2 button to set a breakpoint. 800x400

Running the 7-jump-to-esp.py script with Python. 800x400

Once the breakpoint is reached Immunity Debugger enters a paused state, the status bar indicates a breakpoint is reached at address 311712F3 that contains the JMP ESP instruction. If the application executes further we should expect it to jump to the beginning of the ESP register that contains our C characters from our buffer variable. 800x400

Using the F7 key to step into the next instruction should bring us at the beginning of our C’s at memory address 005FF910 confirming the buffer variable is well aligned and the memory address with the JMP ESP instruction does exactly what we want it to do, jump to ESP. 800x400

Make sure to reattach the brainpan.exe application to the debugger by navigating to File > Open and clicking the Play button again as explained in the Debugging: Setting Up the Debugging Environment step. Before continuing make sure the application is in a running state.

Debugging: Step 8 Writing the Exploit

Now that we control the EIP register, found a memory location with a JMP ESP instruction and confirmed the JMP ESP instruction works as expected and brings us to the beginning of our C’s in the buffer variable it is time to finish the exploit by generating and adding some shellcode instead of the innocent C’s we have been using as padding until now.

Msfvenom can be leveraged to generate a Windows reverse shell shellcode that connects back to a listener on our attacking machine. Make sure to exclude any bad characters that where found in Step 6 with the -b option. The generated shellcode is 351 bytes long which neatly fits in the 522 C’s we have added to our buffer variable. 800x400

Now that the shellcode is generated it should be copied so that it can be pasted in the exploit script. 800x400

To add the shellcode and finish the exploit the 7-jump-to-esp.py script should be modified with a shellcode variable that contains the shellcode generated by Msfvenom. 800x400

The buffer variable is modified to contain 32 NOP’s and the new shellcode variable, the NOP’s are added to give the shellcode some room to expand if needed. The 32 bytes of NOP’s and the 351 bytes that contain she shellcode should be subtracted from the 522 C’s in the buffer variable to keep the total size of the buffer the same as it has been until now. 522 - 32 - 351 = 139 so we should pad the buffer with another 139 C’s after we added in the NOP’s and the shellcode variable. 800x400

The exploit is now finished and ready for testing. Before executing the exploit an Ncat listener is prepared to catch the reverse shell connection. 800x400

Running the 8-exploit.py script with Python. 800x400

The shellcode in the exploit executes and connects back to the Ncat listener. We now have a working exploit to try on the Brainpan 1 machine. 800x400

Exploitation: Initial Shell

Until now we have been developing and testing the exploit on our Windows machine because we needed Immunity Debugger and the mona.py script to help us develop the exploit. To run the exploit against the Brainpan 1 machine the IP address has to be modified to the one of Brainpan 1. The modified script can be downloaded here. 800x400

A new Ncat listener should be prepared to catch the reverse shell connection. 800x400

Running the modified 8-exploit.py script with Python against the IP address of Brainpan. 800x400

The shellcode in the exploit executes and connects back to the Ncat listener. We now have a low privilege shell on the Brainpan 1 machine as the user puck. 800x400

Privilege Escalation

Investigating Puck’s home directory reveals a checksrv.sh script. 800x400

Investigating the checksrv.sh script reveals the brainpan.exe application is executed with WINE, a program that creates a compatibility layer to run Windows applications on UNIX like operating systems and explains why the Windows reverse shell shellcode within our exploit actually worked on a Linux target. 800x400

Because Brainpan 1 is a Linux target it is advisable to try a native Linux reverse shell within our exploit. We can leverage Msfvenom again to generate Linux reverse shell shellcode and replace the Windows shellcode within our exploit. As can be seen the Linux shellcode is only 95 bytes, a lot smaller than the 351 bytes needed for our Windows shellcode. 800x400

Now that the Linux shellcode is generated it should be copied so it can be pasted within the exploit. 800x400

The 8-exploit.py script should be copied and the shellcode variable within the new 8-exploit-linux.py script should be modified with the newly generated shellcode for our Linux target. Furthermore, because the Linux reverse shell shellcode is only 95 bytes long the buffer variable, specifically the C’s that are used for padding should be modified accordingly. The padding of C’s was originally 522 bytes, the 32 NOP’s and 95 bytes for the Linux shellcode should be subtracted leaving a padding of 395 C’s in our buffer variable. The modified Linux script can be downloaded here. 800x400

A new Ncat listener should be prepared to catch the reverse shell connection. 800x400

Running the modified 8-exploit-linux.py script with Python. 800x400

The shellcode in the exploit executes and connects back to the Ncat listener creating a native Linux reverse shell as the user puck. 800x400

Upgrading the Ncat shell with some Python magic. 800x400

Running sudo -l reveals the user Puck can run the /home/anansi/bin/anansi_util binary without a password as the root user. 800x400

Investigating further reveals the anansi_util binary can execute the manual command. 800x400

Exploitation: Root

Using a custom command to escape the binary with a command such as /bin/bash does not seem to work as the anansi_util binary seemingly tries to display the manual page through the less command. However, escaping less is possible by executing !/bin/bash as explained in the following guide for escaping restricted shells from the Exploit Database. 800x400

Running !/bin/bash indeed escapes the program and gains a root shell on the target resulting in a full compromise of the Brainpan 1 machine. 800x400

Conclusion

Brainpan 1 is a fantastic challenge to practice basic buffer overflow attacks and exploit development and I thoroughly enjoyed completing this machine.

This challenge helped me understand the process behind buffer overflows and what goes on under the hood a lot better. Documenting the process in a blog post helped me refine my process and I hope this post helps others on the same journey in understanding the basic concepts behind them as well.


© 2018. All rights reserved.