Is running legacy software with no publicly known exploits safe?

There is a lot of legacy software running all over the network. This is an excellent example of technological debt. And the debt means that we are borrowing. We borrow time before compromise.

It’s quite easy to identify that some software or system is outdated and no longer supported. Yet, it seems that no one has the will or power to make the change. The need for an upgrade is obvious, so what is the problem? Well, it’s usually one of these situations where the solution:

  • was implemented by a third party, and locally no one has knowledge of its inner workings,
  • was created by a company that is out of business and there is simply no upgrade path,
  • upgrade is so complicated and time consuming that it is unprofitable (so the solution was inadequate in the first place).

This is the technological debt in its simplest form. Many companies are stuck with old and unsupported software. Also this solution is doing its job on a daily basis, so no one feels the urge to do something about it.

What about pentest reports that describe it as a vulnerability? Can it be neglected?

Well, let’s find out.

A simple, classic stack-based buffer overflow

We will take a closer look at CVE-2007-5245:

“Multiple stack-based buffer overflows in Firebird LI 1.5.3.4870 and 1.5.4.4910, and WI 1.5.3.4870 and 1.5.4.4910, allow remote attackers to execute arbitrary code via (1) a long service attach request on TCP port 3050 to the SVC_attach function or (2) unspecified vectors involving the INET_connect function.”

Firebird default installation has two interesting properties:

  • fbguard service looks for Firebird process and if it crashes, it will be started again (this will become very handy in a moment),
  • fbserver process is running with NT AUTHORITY\SYSYTEM privileges, so in case of a successful exploitation, the shellcode will run with the same privileges.

So let’s create a virtual machine running Windows Server 2012 R2 and Firebird 1.5.3.4870 (reasonable production setup, just trust me on this one) and take a look around for an existing exploit. Hey! There is a Metasploit module fb_svc_attach by Risesecurity, but their site seems to be down. Quick look at the Internet Archive reveals some more details:

“Vendor was notified, but did not answer to our reports”

Well, wouldn’t you know? Let’s give it a spin!

So the exploit was executed, and I can confirm this by watching Wireshark (to get details, what’s on wire), the Firebird process has crashed but nothing else happened. There is no calc.exe process.

We will need more tools:

  • Immunity Debugger – this is my weapon of choice in this case, but any debugger will be sufficient,
  • mona – this is a Python script that can automate some work in Immunity.

The hex code 0x48 0x99 0x41 0xFD 0x98 … corresponds to the metasploit encoded shellcode (the one that runs calc.exe). So the EIP (Extended Instruction Pointer) register was overwritten, effectively taking control over the program execution but there was an access violation, so the code was not executed.

This is the DEP (Data Execution Prevention) mechanism. DEP is enabled by default on Windows Server 2012 R2.

This would probably work on Windows 10, which by default sets this option to “Turn on DEP for essential Windows programs and services only” but not in this case.

DEP is a simple and clever way of marking memory regions with a Read, Write and eXecute flags or some combination of them. As you can probably guess, stack space lacks the eXecute flag, so no code can be run from it.

There are some techniques to bypass DEP but they usually require that the program uses some libraries that are not ASLR enabled. I’m getting way ahead of myself here but quick recon shows that the only one non-ASLR binary used is msvcp60.dll which unfortunately does not contain any useful gadgets.

ASLR (Address Space Layout Randomization) is another security mechanism that loads binaries in the process address space at random locations. So if a bad actor does not know the right address to jump to, it is not possible to exploit any existing function.

It is also worth noting that the function that copies our data to the stack is strncpy. It is therefore not possible to use so-called null-bytes in the exploit. strncpy function stops copying the data after 0x00 value is met.

Does that mean that this setup is safe and no exploitation can be achieved? Well, no, not really. Let’s take this exploit to a whole new level!

Proof of Concept

So, to quickly recap:

  • DEP is blocking execution of the shellcode on the stack,
  • We will have to use some ASLR enabled binary to bypass DEP,
  • The exploit cannot use any null-bytes,
  • The goal is to overwrite the memory on the stack at a location that will be loaded into the EIP register.

From the original metasploit module we know that EIP is overwritten at offset 308. It is not too much considering that we need to bypass DEP and execute shellcode. So, let’s overwrite some more of the memory and we will find out if it is reliable.

It is common to overwrite memory with hex 0x41 but in case of success (that means, some register will be loaded with modified memory content), it may be hard to identify which 0x41 it is. So let’s create some cyclic, non-repeating pattern to overwrite with.

/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 1500

The 1500 bytes should be enough to accommodate the full payload.

I will start by writing an exploit base.

So, let’s try to execute it.

Well, we overwrite EIP for sure, but also something else. It seems that the ESI register value was also on the stack and our process does some other work before returning. Hence the access violation on write.

We can only overwrite contiguous blocks of data on stack, so there is no possibility to skip this address. But we can put something meaningful there, like for example some address in msvcp60.dll which is not ASLR enabled, so the address will be static.

A good candidate would be 0x7811b012 which lives in the .data segment of msvcp60.dll.

This is a good moment to use mona. As we used a cyclic pattern, mona can tell us which part of the pattern was placed in the ESI register. This is done by typing

!mona findmsp

And we can see it found “ESI contains normal pattern : 0x336b4132 (offset 308)”. Great, let’s put that into our code (candidate address at offset 308).

Another run and the same situation but this time the code wants to read from the ESI pointed address. Let’s find another candidate in msvcp60.dll, from the .rdata section.

Finally! We can overwrite the EIP register value. And our whole payload is copied to the stack.

So, knowing all the offsets, we can refactor this code.

There is the 0x780c724e address from msvcp60.dll, this will become clear in a moment.

The next step is to bypass DEP. A common technique for this is the ROP Chain. We cannot execute code on the stack, but we can put any address on it. Like the one that points to a code that already exists in the server memory and is located in a segment that is marked with an eXecute flag. This code should be only a few instructions long and must end with RET opcode. This way we will jump to different locations of the process image, execute a few instructions and return to our stack, to jump again and again.

This way no code is executed on stack, but we control the program flow, so it’s executing what we want. This short instruction batch with RET at the end is called gadget.

The idea is to execute the VirtualProtect() function, to mark the memory just after our ROP Chain with the eXecute flag, this is the place where our shellcode lives.

This ROP Chain can be assembled by hand or we can ask mona to generate one.

!mona rop -m msvcrt.dll -cp nonull

Mona quickly creates the following ROP Chain:

Mona will also create a file with interesting gadgets. Remember the 0x780c724e address? After we overwrite EIP there is also an ESI register twice on the stack. We need to jump over it, straight into our ROP Chain. This gadget is equivalent to:

# ADD ESP,0C # RETN

So we will use it twice.

There is clear information that those gadgets come from ASLR enabled binary and instruction addresses are rebased but we will worry about this later.

So, the final missing piece is shellcode. We can generate one with msfvenom. Just don’t forget to inform it, that we have bad-chars.

msfvenom -p windows/exec CMD=”calc.exe” -f py –bad-chars=’\x00’

And the output is ready to copy&paste it into our exploit:

So we can put a breakpoint at 0x780c724e and step through the code.

And I can step through our shellcode that lives on a stack. No access violation now. It works! Except it doesn’t.

These addresses in ROP Chain are valid only until the next reboot: ASLR, remember? Windows-based operating systems change base addresses on every reboot.

But wait, we can make some clever observations. This is a 32-bit application. So valid addresses for msvcrt.dll are somewhere between 0x74F00000 and 0x77500000 (those are not the exact numbers, but you got the point). It’s not much of a space… Something like 0x2530000. And they can change every 0x10000 bytes. So we have less than 600 combinations!

Remember our friend fbguard? We can crash the Firebird process as much as we like and it will be brought back like… Fenix (Firebird) from the ashes.

So time for the last, final piece of code. We will add a command line parameter to our exploit, an integer. It will be added or subtracted from the addresses of ROP Chain.

A careful reader could notice that there is a corner case. If msvcrt.dll is placed on an address that will contain 0x00 byte, the exploit will not work. This is true, but we can always rewrite our ROP Chain in a different way, to compensate for this.

The final code looks as follows:

And we can now call it as below (the base address of msvcrt.dll in the moment of creating ROP Chain was 0x758d0000, hence the values in curly braces, to cover the entire possible range).

for i in {-157..451}; do; ./exploit.py $i; sleep 10; done

So… keep calm and wait for the correct base address…

And just under half an hour, here it is. The Almighty Calculator, running with NT AUTHORITY\SYSTEM privileges. It’s kinda scary. Well, if you see calc.exe on your server running with these privileges, you know that something is going on…

Of course this does not have to be calc.exe, it can be a reverse shell as well.

Conclusion

In this article I demonstrated that even very old vulnerabilities, despite security mechanisms like DEP or ASLR, can still bite. Running legacy software is almost alway a very, very bad idea.

The main takeaway here is that creating new systems is relatively easy, managing them is the tricky part.

Keep your stuff up to date, and see you on another vuln.

~Krzysztof Bierówka