Vulnserver TRUN - Vanilla EIP overwrite
by purpl3f0x
Stack buffer overflow exercise: Vulnserver.exe
I've taken quite a liking to doing basic stack buffer overflow attacks after learning out to do them in the Pentesting With Kali Linux course. I learned so much about assembly, and how to debug and analyze programs and gain a deeper understanding of how the program execution flows. This has led me to wanting to do a write-up of how I go through the process.
Vulnserver.exe, found here, is deliberately-vulnerable windows executable that is meant purely for practicing various buffer-overflow attacks. The program has a long list of commands it can execute, and several of them are vulnerable to different types of buffer overflow. After cheating a little bit by looking at some other write-ups, I decided to attack the TRUN command, as it is vulnerable to a very simple, straight-forward stack buffer overflow attack, which is the only type of attack I know how to pull off.
Let's start by checking what the program can do. I have it running on my trusty Windows XP 32-bit VM, which is bridged to my physical network, along with my Kali VM. I start by connecting with netcat:
The commands seem to be case-sensitive, so you have to use all-caps with them. I haven't done much fuzzing but from what I can tell, each command expects different input. They don't appear to do much from what I can tell, but that may not be the case. Anyway, let's start fuzzing the TRUN command.
Pictured above is my python code to do some initial fuzzing. It creates a buffer of 100 A's and sends it to the server, then as it goes through the "for" loop, it increments by 200, and then sends 300 A's, and so on. The IP address and port number are hard-coded for ease of use during this demonstration but it is possible to write the code in a way that it accepts command-line arguments.
The fuzzer is executed and this is what we see. After sending 2300 bytes, the program stops responding. Now, it's very likely that the program actually crashed after getting slammed with 2100 bytes, because I neglected to add "time.sleep(1)" to my code, so this loop executes extremely fast.
I'm using Immunity Debugger now to analyze the crash. Pictured above is what the debugger displays when it has crashed due to an "access violation", which is an error that indicates that this program attempted to access a memory location that either does not exist, or is owned by another process. This is because I overwrote the "Instruction Pointer", which is displayed as "EIP" in the debugger. This is a CPU register that stores a 4-byte value that points to the memory address of the next instruction.
Sure enough, EIP reads "41414141", which is hexidecimal for "AAAA". So far so good; this is what we want, to overwrite EIP. If you can overwrite EIP, which points to the next instruction, then you can control execution flow. But right now we don't know the exact number of bytes it takes to reach this point, so we need to figure that out before we move on.
There is a tool already in Kali Linux to help with this. It will generate a unique string that can help us determine how many bytes it took to overwrite EIP. The tool is located at:
/usr/share/metasploit-framework/tools/exploit/pattern_create.rb
We run this Ruby script, with the parameter " -l 2100" which tells it that we want a string that's 2100 bytes long. I forgot to screencap this so I'll just show what this looked like after I put it in my fuzzer:
Don't forget to modify the connection part of your code; you don't want this looping because it only needs to run once.
EIP was over-writen again, but this time by "386F4337".
Now you must be asking yourself, "How the hell am I supposed to find this in that string? Do I count the bytes??" Luckily no, Kali rescues us again:
Just run the companion Ruby script to "pattern_create", and pass it the parameter "-q 386F4337" and it tells you exactly how many bytes it took to reach this point. For us, it was 2003 bytes. But I want to test this to be absolutely sure, so I'm going to change the fuzzer again:
Here I am sending 2003 "A's", 4 "B's", and just to "pad" it, 500 "C's". The theory is that, if it takes exactly 2003 bytes to overwrite EIP, then I should be able to precisely overwrite EIP with the 4 B's here.
I run the fuzzer again after restarting the program in the debugger (Ctrl+F2 in Immunity Debugger), and EIP is now "42424242", which is hexidecimal for "BBBB". Perfect. We know we can accurately overwrite EIP now, which means we can now control execution flow with the right code. So....what do we put in EIP exactly? Well we need it to run our shellcode. Well, where is that?
Say hello to the ESP register, aka, the "Stack Pointer." This CPU register points to the current location in the stack that is being executed. Remember that 500 C's we sent? See how ESP seems to be pointing to them?
If we right-click the ESP and click "Follow in Dump", it will show us the memory dump of the system's RAM. I didn't screencap it, but trust me when I say that it's just showing a whoooole lot of C's. That means that whatever we put in place of the C's will get executed, IF we can make the program execute what's at the ESP. But we don't always know what address that is; it changes everytime the program is run. But fear not, there is a way to accurately point the EIP in a way that will execute whatever is at ESP, no matter what the address is!
Actually we're not taking a selfie, but there IS something we need to do first before we worry about EIP, because this next step is crucial to perform first. Failure to do so could cause several future steps to fail.
We need to identify the "bad characters".
Yea th- wait no! Not those bad characters.
I'm talking about hexidecimal values that the program could potentially interpret as instructions, which would truncate, or in other words, prematurely terminate our input. This could cause problems for us when deploying the shellcode, or trying to input a certain hex value to overwrite EIP with. So we need to send EVERY possible hex combination from x00 to xFF to the server to see what happens.
I leave off x00 when I do this, because x00 is NULL, which will always be truncated. So I found somewhere to copy-paste this to avoid typing it out by hand, and then sent this buffer to the target.
So now we have to manually comb through and follow the memory dump to see if any of our characters got left off. You can find this after right-clicking the ESP and clicking "Follow in Dump" as shown above. What's very nice about this particular vulnerability is that there are no bad characters aside from x00, which is nice. In other instances of buffer overflow practice, I've found characters that completely truncated the input, so I had to remove that one hex character from my python fuzzer, and then re-launch the attack to see if anything else causes truncation. You have to repeat this process until nothing else is truncated or dropped.
Now that we have taken care of this, back to figuring out what we want to put in EIP. Recall that we want to get the program to jump to ESP, and there's a way to do that. In assembly, there is function that will tell the program to jump to the address stored in ESP and resume execution. That function is "JMP ESP". But we can't just search for this in the debugger, we won't find it. We need the hex first.
You could always google it, but once again Kali makes it easy for us:
The nasm_shell.rb script is the one we want. It will take assemlby commands we give it and return the hex value.
FFE4 is what we want, with FF corresponding to JMP and E4 corresponding to ESP. Now we can search for it in Immunity Debugger. But first, we need something.
We need this python utility called Mona. Download this and copy the mona.py file into Immunity Debugger's "py commands" folder. After that's done, we can call up some of its features to help us find a JMP ESP we can use.
At the bottome of the debugger window, type "!mona modules". This will query the program for the individual modules that it has loaded up.
Now this next part is very important. Notice I highlighted "ASLR". This stands for Address Space Layout Randomization. This is a memory protection measure that randomizes where it stores objects in memory every time the application is run. If ASLR is enabled on a module we can't use it for our JMP ESP. Luckily, nothing here seems to have it.
The other highlighted box is just to show that dll's are usually a good target to go after. I decided to go after "essfunc.dll" since it is part of vulnserver.exe
I invoke mona again to search for the hexidecimal values for JMP ESP. Mona turned up 9 results, and none of them have ASLR protection enabled. I keep it simple and decide to use the very first result, "625011AF"
I use "Follow in expression" to verify that this address is pointing to a JMP ESP instruction, which it is. While I'm here, I set a "breakpoint" by pressing F2. A "breakpoint" is a point in a debugger which will halt execution if it is hit. I am doing this to verify that I am successfully redirecting execution to this instruction.
Now this looks a little wierd now that it's plugged into the exploit. This is a 32-bit application running on 32-bit Windows. That means that the system is using "Little Endian", or in other words, "Least significant byte first." So we have to add the JMP ESP address's individual bytes in reverse order as shown above.
I restart the program and send the new payload:
This is displayed at the very bottom of the debugger window. It's a message to let me know that execution hit my breakpoint and paused.
EIP has been accurately overwritten with exactly what I wanted.
If I press F7, it will "step" into the next instruction. That means the debugger advances by a single instruction and pauses again. We see here that execution immediately jumped to the C's, as indicated by hex 43. Now we know for sure that we can get our payload to execute if we put it after EIP in our exploit.
Now it's time for the fun part, the shellcode. This is going to be our actual payload, the malicious code that we want to execute. Writing shellcode from scratch is incredibly difficult, and not something that most people are expected to do, so we'll just keep using Kali's built-in tools to do it for us. My payload of choice is usually a reverse shell that connects over 443. A reverse shell means that my code will instruct the victim to make a connection back to me. I do this for two reaons:
1. If the victim has a firewall turned on, we cannot reliably use a payload that simply listens on a port for us to connect to.
2. I use port 443 in the event that the victim is protected by an egress firewall, a firewall that filters out-bound traffic. 443 is HTTPS, web traffic, so as long as the theoretical egress firewall isn't doing deep-packet inspection, this should work.
The payload is in hex and is already formatted in a way that we can copy-paste it right into our exploit. I'll break downt he command below:
MSFvenom is a utility that is part of the Metasploit-Framework that generates stand-alone payloads that can be deployed independantly of MSFconsole.
The -p flag is our payload.
The -f flag is the format, and I'm formatting it as c code. You can specify python if you want, but c works in python scripts.
The -a flag is for architecture. x86 will format the payload as 32-bit.
The -e flag is for encoding. If our payload cannot have certain "bad characters", the encoder will format the payload in a way that it will not need to use those bad characters.
Another important note; notice the '\x90\ * 20 I put in after the EIP address and before the shellcode. In assembly, x90 is NOP, or NO OPERATION. It does exactly what it sounds like, nothing. It's padding. Our encoded payload will self-decode once it executes, and we need some padding as the payload unpacks itself. If you don't add a few NOPs (often called the "NOP sled, since the program "slides" down the NOPS and hits the payload), the exploit could fail.
Do or die time. The exploit is complete. If we did this right, we will get a shell from the victim after executing this.
My breakpoint at the JMP ESP is still set, and I see that after "stepping" with F7 again, I hit my NOP sled.
I let normal execution continue by hitting F9 aaaaaand....!
Nothing. The program crashes and I get no shell. What happened? Weeeelll....
You see that there? My payload has a NULL character in it. I got too relaxed when thinking "There are no bad characters" and forgot that x00 is ALWAYS a bad character. Time to re-generate my shellcode...
Notice the new flag on the end of the command, the -b. This is where you tell MSFvenom what the bad characters are and it will use the selected encoder to make sure the shellcode does NOT have the bad characters.
The revised exploit with the new shellcode. Let's try again.
SUCCESS!! THE VICTIM HAS BEEN COMPROMISED!!
I caught the incoming reverse shell with "nc -nvlp 443", and we have a cmd.exe prompt that is sending commands to the victim. The permissions you get from this depend on which user was running the program. This was running as administratior sooooo...
We are the Administrator. The victim has been completely compromised.
Just to amuse myself I put a flag on there just so I have something to say "hey look I captured the flag!"
BUT-!
We're still not done. There's a problem. If we exit our reverse shell, the victim application crashes. Bad news! We've not only tipped off the victim, we've lost our foothold! But we can fix that.
By adding yet another parameter to the end of the MSFvenom command, EXITFUNC, we can control what the shellcode does when it finishes executing. By specifying "EXITFUNC=thread", we're telling the victim program to close a single thread, instead of just crashing and burning. I update my exploit, run it again, and then close my reverse shell:
Immunity Debugger confirms that the program merely closed a thread, and is still running.
As shown here, I can run my exploit several times in a row without having to restart vulnserver.exe because my shellcode isn't terminating the application anymore.
Just for shits 'n giggles, I decided to add in a little bonus to show how you can use any payload you want.
I made new shellcode and set the payload as "windows/meterpreter/reverse_tcp", and kept all other options the same. I fired up MSFconsole and prepared to receive the connection:
I run the exploit again:
Now we have a nice shiney meterpreter session instead of a standard shell. I won't go into a deep lesson on meterpreter but here are some cool things you can do with it:
And that, boisengurls, is how you do a standard stack buffer overflow exploit, provided you don't have to worry about memory protections or more complicated techniques.
The final exploit is here: https://github.com/purpl3-f0x/vulnserver-trun/blob/master/vuln_server_bof.py
If you want to try this yourself, stand up a Windows VM, it really doesn't matter which, but XP or 7 are the best choices, and try to get 32-bit if you can. Immunity debugger is free, and fairly easy to use once you get the hang of it.
tags: Exploit Dev - Security Research