Kernel Pool Overflow Exploitation In Real World – Windows 7

1) Introduction

This article will focus on a vulnerability (CVE-2017-6008) we identified in the HitmanPro standalone scan version 3.7.15 – Build 281. This tool is a part of the HitmanPro.Alert solution and has been integrated in the Sophos solutions as SophosClean.exe.

– C:/Windows/system32/drivers/hitmanpro37.sys : the driver that handle the IRP. Since the crash occurs because of a pool corruption, this driver is certainly responsible for the crash.

– IOCTL Code: 0x0022e100 : The IOCTL code provides a lot of informations, which I will explain later. We can also use it to retrieve where this IRP is processed in the above driver by reversing it.

– Outsize / Insize : Those sizes are used to allocate some buffers in the pool, and might be related to the pool corruption.

If we refer to the MSDN documentation ([2]), we can draw those informations from the IOCTL code:

DeviceType = 0x22

Access = 0x3

Function = 0x840

Method = 0x0

Method 0x0 = METHOD_BUFFERED

After a few research on the METHOD_BUFFERED, we find a quick definition:

METHOD_BUFFERED

For this transfer type, IRPs supply a pointer to a buffer at Irp->AssociatedIrp.SystemBuffer. This buffer represents both the input buffer and the output buffer that are specified in calls to DeviceIoControl and IoBuildDeviceIoControlRequest. The driver transfers data out of, and then into, this buffer.

For input data, the buffer size is specified by Parameters.DeviceIoControl.InputBufferLength in the driver’s IO_STACK_LOCATION structure.

For output data, the buffer size is specified by Parameters.DeviceIoControl.OutputBufferLength in the driver’s IO_STACK_LOCATION structure.

The size of the space that the system allocates for the single input/output buffer is the larger of the two lenght values.

Finally, we reverse the HitmanPro.exe executable, in order to find how the IOCTL is send in a normal behavior.

Using the IOCTL code and the Search tool of IDA, we quickly retrieve the function.

The Outsize and Insize given to DeviceIoControl match with the crash data.

In this way, the SystemBuffer allocated by the IRP Manager should, in a normal behavior, be at least 0x1050 bytes long.

2.2) Reversing the driver

Now that we have a bunch of informations on the crash, it’s time to reverse the driver hitmanpro37.sys, and take a look at the handle of our IOCTL.

First, we have to locate the function which dispath the IRP given its IOCTL code.

It’s usually a big function containing some switch jumps. Since this driver is not that big, we quickly find the dispatcher:

Following the jumps, we finally reach the function that handles our vulnerable IOCTL.

The SystemBuffer provided by the IRP is first used as the ObjectName argument of the function IoGetDeviceObjectPointer.

Then..

And.. here we are.

Remember the method used for this IOCTL ? METHOD_BUFFERED ?

The size of the space that the system allocates for the single input/output buffer is the larger of the two lenght values.

It means we perfectly control the size of the SystemBuffer, and the driver calls memset on it with a harcoded size of 0x1050.

If the SystemBuffer is smaller than 0x1050, the call to memset will corrupt the pool, and trigger a crash.

The vulnerability here is called a Kernel Pool Overflow.

That being said, we didn’t find any way to control the writing of this buffer.

It’s set to 0, then filled with addresses and names collected in DeviceObject and DriverObject structures, which we can’t control without beiing an administrator.

This vulnerability will remain an OS crash, which is not that bad ! The CVE-2017-6007 has been assigned to this vulnerability.

2.3) Pivoting

We couldn’t give up here, so we decided to reverse more handlers.

We picked a random handler, and it was actually interesting:

The SystemBuffer (our input) is used in a subfunction, then if the subfunction returns the right value, something is copied into the SystemBuffer, using memcpy.

The control code used to reach this function is 0x00222000:

DeviceType = 0x22

Access = 0x0

Function = 0x0

Method = 0x0

It’s the same method used : METHOD_BUFFERED.

If we’re lucky, there might be the same type of vulnerability here.

However, this part of the driver is pretty weird:

We didn’t find in the HitmanPro executable any function that launchs an IRP with the control code 0x00222000. So, as expected, when we set a breakpoint in this part of the driver, it’s never triggered !

We couldn’t identify what was the exact purpose of the functions there, but we found a vulnerability, which is enough for us.

So I started reversing.

The handler, turned into pseudo-code :

The driver uses a handle provided in the SystemBuffer to get a FILE_OBJECT. If this FILE_OBJECT is not busy, it calls ObQueryNameString to get the name of the file pointed by the FILE_OBJECT and put it in a temporary buffer. Then it copies the name from the temporary buffer to the SystemBuffer.

The driver calls memcpy with:

dest = SystemBuffer ; we control its size

src = The file name of the handle we gave ; we control both writing and size

n = The size of the src buffer. ; …

The only constraint is the ObQueryNameString function : This function is protected and doesn’t copy anything if the source is too big for the target.

Since the target is a buffer with a hardcoded size of 0x400, we can’t give a file name bigger than 0x400.

Of course, 0x400 characters is way enough in order to exploit a buffer overflow.

3) Exploitation

3.1) Introduction

Since the vulnerability is a Kernel Pool Overflow, there is a bunch of attacks we can use.

And there is no better paper than Tarjei Mandt‘s [3] on the subject. It’s a MUST read if you want to fully understand what happens next.

The attack we use here is a Quota Process Pointer Overwrite.

We chose this attack because it’s one of the most elegant, and it can be achieved on both x32 and x64 architectures.

In this attack, we have to overwrite the process pointer of the next chunk.

In the last 4 bytes of a pool header there is a pointer to a EPROCESS structure.

When a pool chunk is freed, if the Quota bit is set in the PoolType (see _POOL_HEADER structure), this pointer will be used to decrement some values related to the EPROCESS object:

The Reference Count of the Object (a process is an Object)

A value pointed by the QuotaBlock field

But since there is some checks before the decrementation, we cant use directly the ReferenceCount of the object, but we can create a fake EPROCESS structure, and put an arbitrary pointer in the QuotaBlock field to decrement an arbitrary value (even in kernel addresses) !

3.2) Overflow

In order to use the Quota Process Pointer Overwrite attack, we need to overwrite two things using our overflow:

The PoolType of the next chunk, because we need to make sure the Quota bit is set

The Process Pointer of the next chunk, and replace it with a pointer leading to a fake EPROCESS structure

Since we have to reach the process pointer of the next chunk, we have to overwrite the whole pool header of the next chunk anyway. But we can’t just put some random data in the pool header, or we will trigger a BSOD.

We have to make sure the following fields are correct:

The BlockSize

The PreviousSize

The PoolType

The only way to achieve this is to know exactly which chunk we’re going to overflow, and this can be achieved using basic Pool Spraying.