Tuesday, February 6, 2018

MalwareFox AntiMalware (zam64.sys) - Privilege Escalation through Incorrect Access Control


In this blog post, I’ll be describing two bugs I found inside the MalwareFox AntiMalware drivers (zam32.sys/zam64.sys) that allow a non-privileged process to “authenticate” itself with the driver and issue special IOCTLs leading to privilege escalation.
This process of registration or authentication is used by the driver to know which processes to trust when receiving a device control request.  Normally, these processes should be the antimalware’s own processes.
A process that is authenticated by the driver can send special IOCTLs that cannot be sent by other non-authenticated processes.  These special IOCTLs can be used to:
-          Enable/Disable real-time protection
-          Read/Write to raw disk
-          Create full access user-mode process handles
-          …etc
Registered processes are stored in an array located in the data section of the driver. In zam64.sys, each element of the array has 0x980 bytes and the maximum number of elements is 100. An element contains information on the process such as its PID, its session id and the name of the image file name from the EPROCESS structure.
During the run-time of the anti-virus only a single process is registered with the driver, and that is MalwareFox’s own process ZAM.exe, which runs within session 1. There’s also a ZAM.exe process running as a Windows service but it doesn’t seem to be registered.

Figure 1 – MalwareFox’s entry in the registration array

So, by registering our process with the driver, we enable the god-mode capability to send special IOCTLs and basically make use of them to escalate privileges on the system.
I have found two ways to do so.

CVE-2018-6593: Register the process by connecting to the mini-filter communication port:

As shown in the figure below, a default security descriptor is built for the mini-filter communication port allowing access only to SYSTEM and the administrators. But right after that, RtlSetDaclSecurityDescriptor is called with a NULL DACL pointer. This leads to the DACL pointer, that was setup by FltBuildDefaultSecurityDescriptor, being overwritten with NULL. As a result, everyone has access to the object.
In addition, the maximum number of connections allowed to the port are 100 even though only a single connection appears to be needed by the Antimalware.
 
Figure 2

And here’s what it looks like under Windbg.

Figure 3
The interesting thing here is that when a process connects to the port, the driver automatically registers it as a trusted process in the array we saw above. The figure below displays the first and second entries in the array when our process (exploit.exe) is connected to the port.

 
Figure 4

Our process is now registered and can send special IOCTLs as it pleases.

It turns out, the developers zeroed to DACL pointer of the port’s security descriptor because their own process (ZAM.exe) doesn’t run with administrator privileges and turns at medium IL. This of course isn’t an excuse to disable all access checks and from everything we saw until now this is probably an anti-virus you don’t want on your machine; and this is not all!

CVE-2018-6606: Registering the process by sending IOCTL 0x80002010

It turns out there’s a straightforward way to register a process as trusted. Send IOCTL 0x80002010 with a process id of your choice and voilà the process with the PID you supplied is now registered and fully trusted by the driver!!!

What the driver fails to do here is check if the requestor process itself is a registered process. It does check for this when a process sends a special IOCTL, but it fails to do so if the process wants to register another process as trusted; rendering all other checks useless.

Thus, all we need to do to register our process is send IOCTL 0x80002010 with our process’s PID.

Figure 5
We can now send any special IOCTLs we want, and we’ll be using IOCTL 0x8000204C to elevate privileges.

Getting SYSTEM

MalwareFox seems to need user-mode full access handles to processes. And since we saw how its usermode process lacks the necessary privileges, it delegates this task of opening handles to its driver.

IOCTL 0x8000204C is a special IOCTL that must be sent by a registered process to the driver. The requestor simply provides a PID as an input and gets a full access handle as an output from kernel-mode; how cool is that for us ?

We use this opportunity to open a full access handle to winlogon.exe, inject a cmd.exe shellcode and then create a remote thread.

Figure 6


CVE-2018-6593:


CVE-2018-6606:



A video demonstrating the first bug (CVE-2018-6593):

Source code for CVE-2018-6593: https://goo.gl/yrxLfW
Source code for CVE-2018-6606: https://goo.gl/YtsYEo

Follow me on Twitter.

Sunday, November 19, 2017

HXP CTF 2017 - "revenge_of_the_zwiebel" Reversing 100 Writeup

revenge_of_the_zwiebel - 100 pts + 4 bonus pts ( 31 solves ):

After executing the binary it prints : "Input Key:" and waits for us to enter the flag. The routine printing the "Input Key:" message is executed at initialization alongside a sub-routine implementing the ptrace anti-debugging trick. Since we're going to debug the binary, I patched the anti-debugging sub-routine's address with nullsub_1.


After setting up remote debugging under IDA and supplying some random input to the binary we see a call to some code that was stored in executable memory.
 

IDA sometimes has trouble displaying memory under its debugger, so let's setup a manual memory region and set it as an executable 64-bit segment.

Now we should be able to view the entirety of the bytes copied to memory.
In the figure below, the code that is executed starts by checking if the 4th bit of input[0xC] is set. If it's not set, the message ":(" is printed and the process exits.


However, if the bit is set the code proceeds to decrypt the next block and also XOR the subsequent blocks that are still encrypted. (see figure below)


There's also a second test, implemented in some blocks, which involves the NOT instruction (figure below). This means that the 3rd bit of input[0x11] must not be set.

The amount of code executed is huge and doing this manually is impossible. So, we have two options : 

1.  Either dump the encrypted data, decrypt it statically and then build the flag by automatically reading the disassembly.
2.  Automate the IDA debugger to save the index plus the bits that must be set and guarantee that everything will be executed by continually patching ECX in order not to take the JECXZ jump.

Even if the 2nd attempt would take longer to complete, I chose to implement it. If I ever do the first one, I'll be sharing it here too :)

So, what the script below does is create a dictionary where the key is the character's position and the value is an array of bits that must be set. I simply ignore the NOT case, since we only care about the bits that must be set.

For example if the character at index 2 needs to have bits : 0, 4 and 7 set, the dictionary entry would look like this : {2: [1, 16, 80]}
After the process terminates, we proceed to OR the numbers of each array which gives us the flag in the end.

Here's the script that must be executed under IDA to get the flag.
Script runtime : approx. 15 minutes
Full script : here

Follow me on Twitter : here

HXP CTF 2017 - "dont_panic" Reversing 100 Writeup

dont_panic - 100 pts + 15 bonus pts ( 19 solves ):
The file is a GO binary. After locating the main function, by stepping in the debugger, I found that the binary expects a command line argument of 42 characters.
For each character it calls a sub-routine sub_47B760 that returns a calculated byte corresponding to the one supplied. This byte is then compared to the one it corresponds to in a hardcoded array of bytes, which clearly represents the encrypted flag.

I didn't really look into how the values are calculated since GO binaries tend to be messy and I didn't really have to do it in order to solve the challenge. The thing is, the program branches to the block that displays the fail message ("Nope") as soon as it finds that one character is wrong. This opens room for brute-forcing since we can brute-force the flag character by character dynamically.

I used python with GDB to do so. Here's the solution script :
full script : here


After a minute or so, we get the flag.

HXP CTF 2017 - "Fibonacci" Reversing 100 Writeup

Fibonacci - 100 pts + 6 bonus pts ( 45 solves ):
This binary is supposed to print the flag directly into the screen. However, it will take a very very long time to print the whole flag since the output is based on the calculation of fibonacci numbers recursively.
For each bit of the encoded flag (length = 33 stored at 00000000004007E0), the fibonacci number of that bit's position is calculated : this means that it will calculate fibonacci values for numbers from 0 to 263.
This is not all. Since the flag needs to be decoded, each call to the fibonacci sub-routine expects a pointer to a bit value which is XORed with a calculated bit from the resulting fibonacci number. Keep in mind that the fibonacci implementation is recursive, and thus we expects this boolean value to be XORed multiple times for greater numbers.
When the fibonacci sub-routine returns to the main function, the corresponding bit of the encrypted flag is XORed with the calculated bit value.

The solution that came in mind is to modify the fibonacci implementation so as to save both the calculated bit value and the resulting fibonacci number for a given number. So instead of recursing and re-calculating the fibonacci number of a previously calculated one (in a previous call for a previous bit of the flag), we simply load the result of the calculation and XOR the current output bit with the one we already saved.

The solution is implemented in the script below. All modifications done to the original function are commented.
Full script : here


We immediately get the flag when we run the program.

 

Monday, June 5, 2017

Exploring Virtual Address Descriptors under Windows 10

This blog post is about my personal attempt to superficially list VAD types under Windows 10. It all started when I was wondering, out of sheer curiosity, if there's any way to determine the VAD type (MMVAD_SHORT or MMVAD) other than by looking at the pool tag preceding the structure. In order to do that, I had to list all VAD types, do some reverse engineering, and then draw a table describing what I've been able to find.
You can view the full document by clicking here 



From the table above it is possible to deduce the VAD structure type from both the VadType and PrivateMemory flags.

VadType flag
PrivateMemory flag
Type
0
0
MMVAD
0
1
MMVAD_SHORT
1
1
MMVAD
2
0
MMVAD
3
1
MMVAD_ENCLAVE

To test it out, I wrote a kernel driver that prints the deduced VAD type for each node of calc.exe. It also prints the pool tag so we can check the result.


And that's all for this article.
You can follow me on Twitter : here