Recently I had the chance to create a custom exploit from scratch, to exploit a remote code execution vulnerability in a FireBird server (CVE-2007-3181). Now granted, this is an old exploit from more than 10 years ago. However, I still see this vulnerability in the wild at the time of writing and even though this vulnerability has been disclosed for several years now, metasploit does not have a working exploit module that successfully exploits this RCE for Windows 10.
This blog post aims to explain the process and steps taken during the creation of the custom exploit.As this is a simple stack buffer overflow vulnerability, I thought it would be a perfect candidate to use as an example.
So what is an ethical hacker with some free time to do? That’s right, let’s get our pwnage on!
Create a reliable working exploit to obtain RCE for firebird 2.0.0 installations on Windows 10 x64 PRO 1803 build 17134.165
The first step in creating exploits from scratch is information gathering, finding out what the vulnerability is and how to reliably exploit the vulnerability to crash the application. Now, it’s not always the case that the application crashes, but since we know this is a buffer overflow, the chances are pretty high that the application will actually crash.
So let’s take a look at the CVE information:
Buffer overflow in fbserver.exe in Firebird SQL 2 before 2.0.1 allows remote attackers to execute arbitrary code via a large p_cnct_count value in a p_cnct structure in a connect (0x01) request to port 3050/tcp, related to “an InterBase version of gds32.dll”
So we got a couple of information tidbits here, we know that we need to send a connect request to port 3050 and that the vulnerability has something to do with using a large p_cnct_count value in a p_cnct structure.
Now, we need to know how a connect request is made exactly and what the packet looks like in order for us to create a custom packet that crashes the application and thus allows us to exploit the vulnerability.
We have two options here to find out what the structure looks like, since it is an open source project we could try to look for the FireBird 2.0.0 source code files or option two, we could download the firebird 2.0.0 final build and use wireshark to take a look at the package structure when making a connection.
FireBird uses the GDS DB procotol which wireshark can decode without a problem, so in this case, going for option two is definitely the easier choice as finding the source files for an application released in 2007 can be troublesome.
Option 2 it is then! Let’s download the firebird 2.0.0 final server and install it on our windows 10 x64 pro 1803 virtual machine (be sure to allow incoming tcp/3050 on the windows firewall).
The next step requires us to send a connect command to the server. Luckily for us, a python module exists to connect to the database, so let’s boot up our Kali linux, install this module and create our simple python script.
Our simple python script:
We don’t care about the user and password here, so you can fill in anything you want.
Open up wireshark and run the script
We can see that wireshark successfully decoded the packet; we see no mention of a p_cnct_count value though. However, by doing some deductive reasoning we can see a version option count that is set to 6 and we can also see 6 preferred version listed. Coincidence? I think not! This let’s believe that the p_cnct_count field is actually named version option count in wireshark.
The version option count field is 4 bytes in length (DWORD), as can be seen in the following screenshot.
As you might already now, 4 bytes is not enough space to exploit a RCE and run arbitrary code, but once again I put my mind-blowing deductive reasoning skills to work and assumed that the version count field is used to load in the version information at the end of the packet.
We know from wireshark that each version field is 20 bytes long (select it and it’ll show the size at the bottom). If we wanted to append, let’s say, 5000 characters, we would need to set the version count field to 5000/20 = 250 (0xFA). So lets make it 0xFF to be sure.
Next we copy the original packet from wireshark without the version data at the end, append 5000 ‘A’ and change the count to 0x000000FF to see if it’s possible to exploit the vulnerability and crash the fbserver.
Start Immunity debugger as Administrator and attach it to the fbserver.exe instance. Why do you need to run Immunity Debugger as Administrator you might ask, well, because the fbserver.exe by default runs as local SYSTEM. The latter is a vulnerability in itself, you have no idea how many times I have to tells clients to not do this. But I digress, back to the exploit!
After running our exploit script we can see that the server indeed crashes, but not at a point we were expecting it to crash as we have no EIP control and no way to pivot in any way that we can execute our own code.
You can see the program crashes because it is trying to read from an invalid memory address (ECX + 18), ECX at this point is filled with 0x41414141, which makes the memory address the application is trying to mov data from 0x41414159, which in this case is not readable and thus an access violation is thrown.
Now, I did some research and found out that this weird behavior only happens when the fbserver service has not been restarted since first boot. For some reason, when the server is booted up and the service is started for the first time, you see this behavior.
The good thing is that fbserver comes with the fbguard service started by default. Fbguard continuously checks if the fbserver service is still running, so once we have our working exploit we can simply bypass this behavior by running it twice. The first time will crash the service and restart it, the second time will execute our code.
To bypass this behavior during debugging and testing, just make sure you restart the service at least once before launching the exploit.
After the second exploit attempt, we have EIP control and EDX points to a location in our buffer, all the things we need for a successful exploit!
All right, so we can reliably crash the application, now it is time to actually force it run our own shellcode. In order to achieve this though, we need to be able to pivot to our shellcode. From the previous screenshot we can see that EDX points to our buffer, thus we need a way to pivot to EDX.
Because we have EIP control, we can just overwrite EIP with an address that points to a CALL EDX or JMP EDX instruction. There is no DEP protection or stack canaries in place, so we don’t have to bother with ROP or EGG hunting shenanigans.
To continue building our exploit, we need to know two things:
Metasploit provides us with a very handy feature that allows us to generate a pattern of X characters long, using mona we can then find that pattern in memory and see where it is found on the stack / in the registers. Use the !mona findmsp command for this.
Let’s create our pattern first:
/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 5000
Copy the output to your exploit script and use it as buffer instead of the A’s
Run the exploit and take a look at the debugger on the victim. We can see that the application crashes as we expect, now we can run !mona findmsp to get the offsets.
In the log we can now find all offsets we need. We can see that the EIP offset is at 1204 and EDX points to offset 944. Next step is to overwrite EIP with an arbitrary value to see if we got it right! Be careful here that you do not accidentally enter a valid memory address as the application will not crash at EIP then, so it’s usually best to use alpha characters, such as FFFF.
We fill the buffer with ‘A’ until the offset for EIP, fill EIP with FFFF and then fill the remainder of the buffer with ‘B’ until the total length reaches 5000.
Execute the exploit and check the debugger again
Bingo, EIP is FFFF (or 0x46464646 in hexadecimal)!
Next step is to pivot into our buffer through CALLING or JUMPING to EDX. In short, we need to find a CALL EDX/JMP EDX instruction and use that address in EIP.
Luckily, we can use the !mona jmp command to help us out here.
Since ASLR is enabled by default in Windows 10, we need to find a CALL EDX/JMP EDX instruction in a module that is not ASLR/REBASE enabled, easy enough !mona jmp by default only searches in modules which are not ASLR/REBASE enabled. For all other mona command that not have this default behaviour, you can use the -cm aslr=false, rebase=false arguments.
(see https://www.corelan.be/index.php/2011/07/14/mona-py-the-manual/ for more information).
Additionally, we need to know what the bad characters are, these are characters that need to be avoided in our exploit, as they would potentially cut off the buffer and thus would make our exploit fail. We can use mona.py for this as well. Since there are already a million write-ups on this, I’m not going to include it in this write-up. I can tell you now, there are no bad characters for this exploit and thus we do not need to worry ourselves with them.
After running !mona jmp -r edx command we find the following addresses that could help us pivot into EDX
Since there are no bad characters to worry about and all of these instructions are basically the same, it does not matter what memory address you choose here. We might as well go for the first one then, which is 0x0051477d. If we had a bad character 0x00, for example, we could not use this address as it contains 0x00.
Now we need to edit our exploit and change the arbirary FFFF into this address, however Python by default uses little endian when you use the 0x…….. notation, as python sees this as an int.
In short, when you use this notation in python:
Buf = 0x0051477d
Python will actually send this out over the socket as 0x7d 0x47 0x51 0x00, this will crash the exploit because this memory address might not be valid or it jumps to a set of instructions at that memory address. Either way, it won’t hit our CALL EDX.
We can solve this in two ways:
Options two tells python that we want Big Endian or Network notation (>) and that the data type is an Unsigned int (I).
Because we want to make sure we actually got the EDX offset right from before, we will fill the offset location with a software breakpoint (‘\xcc\xcc\xcc\xcc’). We can then set a breakpoint in the debugger at address 0x0051477d, our CALL EDX instruction, and then step through to make sure we actually hit the calculated offset point and thus our placed software breakpoint.
This how the exploit looks now:
Let’s run it and see what happens. Make sure you put a breakpoint at 0x0051477d in the debugger before running the exploit.
We correctly hit our breakpoint, now we step through the instructions with F7 to see if we actually hit our target EDX offset!
Damn! It seems we missed our found offset by 0x2A. This is a good lesson on why you should never blindly trust anything.
The fact we missed our offset is either due to a miscalculation in mona.py or to the application doing something that pushes EDX 0x2A further before hitting the breakpoint. I did not find, nor did I really care, why exactly this happens, the only thing I know for sure is that we need to subtract 0x2A or 42 from our EDX_offset in the exploit code.
After doing so, we run the exploit again and TADAAA, we hit our software breakpoint.
Great! We have all puzzle pieces in places to execute our own code now!
STEP 4: SPACE, THE FINAL FRONTIER
Now since we don’t need a ROP chain and have no bad characters to worry about we can start with writing our shellcode right away! The only question we need to ask ourselves here is, where do we place the shellcode? If we position the shellcode directly at the EDX offset we have 1204-902=302 bytes of space.
This is usually enough for very simple POC payloads such as executing CALC.exe (193 bytes), however for a payload such as meterpreter/reverse_https we need around 550 bytes of space. As you can clearly see, there is not enough space to place the payload at the EDX offset directly.
Damn, after all this hard work, could it be we are stuck?
NO, ofcourse not!
As you might remember our total buffer is 5000 bytes, and EIP is overwritten at position 1204, which means we can put our shellcode at position 1209 and have 5000-1209=3791 bytes of space. Which is almost enough to create a whole new application ;).
How do we pivot to this shellcode from our EDX offset thought? Simple, we jump into the unknown…er… or the known in this case..ahum. Anyyyyyyhow, let’s do some calculations here on how far we need to jump to hit our shellcode!
EDX offset is at 302, EIP offset is at 1204 and EIP is 4 bytes, thus our shellcode needs to start at EIP offset+4 bytes, which is 1208. We need a jump of 1208-902 = 306 bytes decimal or 0x132 bytes hexadecimal.
Let us generate byte code that achieves this, you can use MASM or any other assembler for this task. I usually opt for https://defuse.ca/online-x86-assembler.htm#disassembly.
In assembly, jumping 0x132 bytes can be done with the following assembly code:
So enter this in MASM or the online assembler and voila, our bytecode is produced!
The assembler generated a 5 byte bytecode to achieve the jump, we now need to place this bytecode at the EDX offset. For testing purposes, because we want to see what ahappens and to double check if this is correct (never trust anything) we will put a sofware breakpoint (0xcccccccc) at EIP+4, or in short our jump target.
Put a breakpoint at the EDX offset in the debugger and run the exploit.
As you can see, we hit our jump commando, let’s step one instruction further with F7.
The only thing left to do is actually generate our payload, as POC I generated the execution of notepad.exe using msfvenom, but the sky is the limit here. Remember that since FBSERVER.exe is running as SYSTEM every application or command you run, runs as SYSTEM as well. The user you are logged on with, will note see the notepad.exe windows appear, but if the user has administrator privileges you can check that your exploit worked by checking the TaskMgr, there should be a notepad.exe running as SYSTEM in the list.
Alright, next step is generating the payload with msfvenom:
msfvenom -p windows/exec CMD=notepad.exe -f python[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload[-] No arch selected, selecting arch: x86 from the payloadNo encoder or badchars specified, outputting raw payloadPayload size: 196 bytesFinal size of python file: 954 bytes
Let’s use this payload in our exploit and see what happens, you don’t need to attach the debugger in this case, unless you made a mistake somewhere and need to debug where you went wrong ;) (trust nothing).
Execute the exploit and check the taskmgr on the victim
Winner Winner, Chicken Dinner!!!
We just exploited a vulnerability starting by just reading the CVE information and doing our own research, how cool is that?!
We just exploited this vulnerability for Windows 1803 build 17134.165, but if you run this exploit on version 1709 or even a different sub build of 1803, you will notice your exploit does not work.
This is because the EDX offset point is different for each build. You have two options here, create an exploit for each build. This will take you a long time and is not future proof, so I strongly STRONGLY advise against this (work smart, not hard).
The other option is to exploit the fact that the EIP offset stays to same for all windows 10 versions and builds (previous Windows versions are untested), thus you can be sure that the location EIP – x bytes also will be the same for all builds.
Maybe you could make sure that you include a jump at a location you know doesn’t change for an amount you know that doesn’t change ;).
Hint: EIP is 4 bytes and a jump can be a ‘skip’ if it has to be.
For my next blog post I will talk about how to create a metasploit module that can be ran directly from msfconsole, this is generally always what you want to be aiming for.
So check back soon if you’re interested in that!
Greetings and salutations!!!!