Vulnserver Reverse EngineeringThe devil is in the details
By Andres Roldan | July 06, 2020
In previous posts, we have been able to exploit
several vulnerabilities on
Vulnserver, a VbD (Vulnerable-by-Design)
application in which you can practice Windows exploit development.
Each article described a technique used to exploit certain
command, and the write-up followed along with the creation of the exploit.
All of those articles had something in common in the way the vulnerabilities were discovered: the use of fuzzing.
In this post, we will use reverse engineering to find the vulnerable commands
Vulnserver and some details that may be overlooked during the fuzzing
Although vulnserver source code is fully accessible, we will assume a black-box scenario on which we don’t have access to the code.
Vulnserver first analysis
The first thing to do is a binary reconnaissance of
The tools we’ll be using during this article are a debugger (
and a reverse engineering framework (
the latter using the
To check the initial characteristics of
vulnserver.exe, we can load it
As you can see, there’s some information about the file, namely the type,
the class (
PE32), OS information, etc.
Among the things to notice are the libraries used by
They are the following:
That means that
vulnserver.exe uses functions which are located directly
or indirectly in those libraries. That information is retrieved from the
IAT (Import Address Table) of the
PE (Portable Executable) header.
To see the functions used by
can go to the
Look at that. Some functions were marked as
functions are commonly the cause of vulnerabilities like Buffer Overflow.
We will check that later.
We must also check what the security characteristics of the executable
vulnserver.exe are. By checking that, we should be able to
anticipate restrictions for our exploitation phase.
To check that information, we can use our debugger. We will load
x64dbg and use the
As you can see, we have different columns here:
SafeSEH: Safe Structured Exception Handling enabled
DEP: Data Execution Prevention enabled
ASLR: Address Space Layout Randomization enabled
/GS: Buffer Security Check enabled
CFG: Control Flow Guard enabled
You can notice that both the executable
vulnserver.exe and the
modules have all those security features disabled, which means that we can
harness instructions on those files when creating our exploits.
We can now make a simple request to
vulnserver to see how it behaves:
[email protected]:~$ telnet 192.168.0.20 9999 Trying 192.168.0.20... Connected to 192.168.0.20. Escape character is '^]'. Welcome to Vulnerable Server! Enter HELP for help. HELP Valid Commands: HELP STATS [stat_value] RTIME [rtime_value] LTIME [ltime_value] SRUN [srun_value] TRUN [trun_value] GMON [gmon_value] GDOG [gdog_value] KSTET [kstet_value] GTER [gter_value] HTER [hter_value] LTER [lter_value] KSTAN [lstan_value] EXIT
It seems that all the commands receive a single parameter on the form
<command> <value>. For example, a request to the
TRUN command would be
We can see this behavior in
As you see, when the
recv() call is executed, it will iterate over
what’s received and check for every command.
When the command string is found, it will stop iterating and will execute whatever the command does.
With that information, we can start checking for the vulnerabilities.
For the sake of this exercise, I will be reversing only the
Reversing KSTET command
On the main loop,
vulnserver checks if the received buffer contains
KSTET, and if it does,
it will divert the loop and will enter the
KSTET execution is lineal and simple:
It will allocate
0x64or 100 bytes of dynamic memory.
The pointer to that region is saved in a var I renamed to
strncpy()will copy up to 100 bytes from the
ReceivedBufferis more than 100 bytes long,
strncpy()will discard the remaining bytes.
0x1000or 4096 bytes of
ReceivedBufferis set to
Then a pointer to
KstetBufferis put at
ESPas a parameter for
Function2returns, the string
KSTET_SUCCESSFULis sent back.
Now, let’s check what
After the function prologue, it will allocate
0x58or 88 bytes to the stack.
*destvariable will be at
The argument on
ESP, which is a pointer to
KstetBuffer, will be used as
strcpy(dest, src)is executed.
That means that, as
*dest is located at
ebp-0x48, if we want to overflow
KSTET command, we must inject
72 - len('KSTET ') = 66 or more bytes
to start overflowing the stack:
._________________________________.__________________.__________________. *dest Saved EBP Saved EIP epb-0x48 (72 bytes) ebp+0 (4 bytes) ebp+0x4 (4 bytes)
Let’s check it:
Great! We were able to overwrite
EIP registers with our values.
That was an easy one.
Reversing TRUN command
vulnserver receives the
TRUN <value>, it will divert the
To get clear references later, I renamed the
s1 parameter to
The first thing it does is allocate 3000 bytes of dynamic memory using
malloc and then set those 3000 bytes to
Also, at the end, a new variable
var_480h is set to value
5. I will rename
LoopCounter. It is set to
5 because at that place is where the
TRUN<space> will start:
Then, a loop is created:
The first node will move the
EAX and compare that value
recv() buffer length. If it’s greater or equal, the loop will
If not, it will move the pointer to where the
TRUN buffer command was set
and put the pointer 5 bytes forward to remove the
Then it compares the current position to
0x2e, which is the hex
representation of a dot (
If the dot is found in the buffer, then it will copy the entire
ReceivedPayloadPtr to a new variable called
dest up to
Then, a pointer to
dest is put on the stack and
Function3 is called:
Function3, we can see that
0x7e8 or 2024 bytes are allocated on
the stack, and the
*dest variable will be at
Now a pointer is set to
ESP to make it the
dest parameter of
Then the pointer to
ReceivedPayloadPtr is set to
ESP+4 to refer to the
*src parameter of
strcpy() is called:
All that means is, to overflow the
TRUN parameter, we must:
Inject a dot somewhere on the payload to trigger the
*destis located at
ebp-0x7d8, we must inject
2008 - len('TRUN ') = 2003or more bytes to start overflowing the stack. Let’s check it:
$ echo -n "$(python3 -c "print('TRUN ' + 'A'*2000)")" | nc 192.168.0.20 9999 Welcome to Vulnerable Server! Enter HELP for help. TRUN COMPLETE
$ echo -n "$(python3 -c "print('TRUN ' + 'A'*2010)")" | nc 192.168.0.20 9999 Welcome to Vulnerable Server! Enter HELP for help. TRUN COMPLETE
$ echo -n "$(python3 -c "print('TRUN .' + 'A'*2001)")" | nc 192.168.0.20 9999 Welcome to Vulnerable Server! Enter HELP for help. TRUN COMPLETE
$ echo -n "$(python3 -c "print('TRUN .' + 'A'*2002)")" | nc 192.168.0.20 9999 Welcome to Vulnerable Server! Enter HELP for help.
As you can notice, with the last command with a payload of 2002
plus a dot,
vulnserver stopped working and we got an
exception on our debugger:
That means that we started to overwrite the
saved EBP which is next to the
saved EIP on the
Function3 stack frame:
._________________________________.__________________.__________________. *dest Saved EBP Saved EIP epb-0x7d8 (2008 bytes) ebp+0 (4 bytes) ebp+0x4 (4 bytes)
So, if we inject 2016 bytes:
len('TRUN ')= 5
We should overwrite the
saved EBP with 4
B and the
saved EIP with 4
and when the vulnerable function returns,
EIP will be overwritten
by our buffer:
Indeed! We were able to identify the vulnerability on the
The examples above use the known
vulnserver command inputs to identify
the execution flow and characteristics of the vulnerable commands. That
may be the most natural way to approach a reverse engineering session since
it’s the way the application processes user input.
However, as we saw at the beginning, it’s possible to get the unsafe functions used by the application:
We can work backward from there, searching by cross-references (X refs)
to those functions. For instance, if we’d wanted to know the places on where
strcpy() function is used, we can look for the cross-references of
that function on
Vulnserver. Once we find those references, we can start
walking in reverse to see if non-validated user input reaches the call of
As you can see, we were able to get to the vulnerable
KSTET function using
Vulnerabilities can be found using static, dynamic and interactive ways.
Fuzzing is a dynamic approach to find vulnerabilities, but it is prone to
overlook details of vulnerabilities. Using reverse engineering, we apply
an interactive approach that, as you see, gives a full detailed view
of the vulnerable software. If you take a look at the
KSTET and the TRUN
articles, you can see that the analysis performed using reverse engineering
matched the one using fuzzing, with some additional details. And remember
that we use all these approaches at
Fluid Attacks to find vulnerabilities!