Vulnserver is a multithreaded Windows based TCP server that listens for client connections on port 9999 (by default) and allows the user to run a number of different commands that are vulnerable to various types of exploitable buffer overflows.
First things first, lets enumerate VulnServer manually and try to understand the executable. The executable is 32 bit and I have fired up Windows Vista to run the binary on port 5000. As Backtrack is the OS of choice for the OSCE exam I will use it for probing VulnServer. I need to practice as much as possible with it!
From connecting to the application and examining how it operates it can be observed that the binary expects a command followed by a parameter. Commands are listed by entering HELP and valid commands are STATS RTIME LTIME SRUN TRUN GMON GDOG KSTET GTER HTER LTER KSTAN.
Straight away I am wondering , what happens if values are entered without a parameter? What happens if a number is entered? Will a negative number cause a problem? What if a string is used? Initial probing from the picture shows that the STATS command accepted an arbitrary value I choose of 60000 . STATS on its own returned a value of UNKNOWN COMMAND.
Let’s fire up a fuzzer and see if we can provide invalid, unexpected, or random data to the application. Once again the OSCE exam drives my decision here. Spike is the fuzzer of choice for the exam and as such it is the one that I am going to use. While Spike has been superseded in recent years by Sully and Boofuzz it still remains an extremely capable and relatively easy to use fuzzer.
To use Spike a spike template needs to be configured. This means that Spike will operate within the confines that we define. The code used can be seen below and replicates the functionality that was observed when the application was probed manually earlier.
root@bt:/pentest/fuzzers/spike/src/audits/custom# cat vulnserv.spk s_string("SRUN"); s_string(" "); s_string_variable("netascii"); s_string("\r\n"); sleep(1);
The observant reader will notice that the highlighted line contains _variable unlike row 3 which just contains s_string. This means that Spike will only fuzz the value that comes after the command SRUN.
The fuzzer was left to run until it fully completed fuzzing. The application handled all the fuzz attempts with no issues. Given that we did not get the crash that we were hoping for let’s move on to the next command and begin again. This time I am going to pick the command TRUN and hopefully we get a crash!
On the Vista machine Immunity Debugger was launched and attached to the running VulnServer process. The default operation in Immunity Debugger is to pause the excitable but we need it to execute freely so the play button was clicked.
The spike template was modified to use the TRUN command and a Wireshark capture filter was set to monitor packets sent to port 5000. After Spike was executed, very quickly a crash was observed in Immunity Debugger. A buffer overflow was triggered and it can be see from the picture below.
The instruction pointer (EIP) is overwritten with ‘AAAA’ which is \x41\x41\x41\x41 in hexadecimal. In addition it can be clearly seen that the stack pointer (ESP) now points towards towards a long string of ‘A’s. Examining the EAX register in this case gives a good indication as to the value that caused the overflow. It appears that the command “TRUN /.:/AAA…” is the command and value that causes the overflow to occur. Lets confirm with Wireshark.
As we can see the exact packet that causes the issue is ” “TRUN /.:/AAA…”.
To complete the fuzzing it is time to finally reproduce the crash by sending just this specific packet. I quickly wrote a simple Python script to reproduce the crash. It is important to to able to isolate the specific packet and be able to reproduce the crash from a script as this will enable us to craft an exploit payload.
#!/usr/bin/python import socket import os import sys crash = "TRUN /.:/ " buffer = "A" * 5000 print "[*] Sending evil payload request to Vulnerable Server" expl = socket.socket ( socket.AF_INET, socket.SOCK_STREAM ) expl.connect(("192.168.0.17", 5000)) expl.send(crash + buffer) expl.close()
The Python script was executed and once again EIP is overwritten. To recap we have successfully fuzzed an application and isolated a packet that causes a buffer over flow.
In my next blog post I will take the Python script to the next stage and modify the payload to return a shell to us.