Posts

Analysing the NULL SecurityDescriptor kernel exploitation mitigation in the latest Windows 10 v1607 Build 14393

We recently discovered a new and quietly released Windows kernel exploitation defence. Exploiting a kernel bug by setting the pointer to the SecurityDescriptor to NULL in the header of a process object running as SYSTEM won’t work from Windows 10 v1607 (Build 14393).  If you want to know why, keep reading. Read more

Exploiting a Kernel Paged Pool Buffer Overflow in Avast Virtualization Driver

CVE-2015-8620

We discovered this vulnerability in the Avast Virtualization driver (aswSnx.sys) that handles some of the ‘Sandbox’ and ‘DeepScreen’ functionality of all the Avast Windows products. We initially found this issue in versions 10.x (10.4.2233.1305) of those products and later confirmed that the latest 11.x versions were still affected by this issue up to, and including v11.1.2245. Upon successful exploitation of this flaw, a local attacker can elevate privileges from any account type (guest included) and execute code as SYSTEM, thus completely compromising the affected host.

Affected Products

  • Avast Internet Security v11.1.2245
  • Avast Pro Antivirus v11.1.2245
  • Avast Premier v11.1.2245
  • Avast Free Antivirus v11.1.2245

Earlier versions of the aforementioned products are also affected.

Technical Details

The Avast virtualization kernel mode driver (aswSnx.sys) does not validate the length of absolute Unicode file paths in some of the IOCTL requests that receives from userland, which are later copied on fixed length paged pool memory allocations. This allows exploit code to overflow the associated kernel pagedpool allocated chunk and corrupt an adjacent kernel object that the attacker controls (Figure 1).

Figure 1. Attacker-Controlled Directory Object

Figure 1. Attacker-Controlled Directory Object

In the following figure we can see a call to nt!memmove performed by the aswSnx.sys driver without validating the size of the data to be copied against the available size in the allocated pagedpool chunk. This information was taken from version 10.x, but you can easily locate the same call in version 11.x of the driver.

Figure 2. The Bug!

Figure 2. The Bug!

Exploiting Heap Overflows

As with most cases dealing with dynamic memory allocation based buffers, also known as heap overflows, we firstly need to be able to predict where the allocation will occur so that we can take control of the execution flow as reliably as possible. This is even more important when we exploit bugs in code running in the kernel address space, as usually if the exploit fails then the whole system goes down with it. Corrupting a random kernel object that you don’t control, is indeed a really bad idea.

In order to achieve this, we need to overcome another challenge which is to create a desirable layout of dynamic memory allocations based on the size of the chunk that we can overflow. If we can control the size of that chunk, then it is easier to achieve this since we don’t have to limit ourselves to a much smaller subset of objects. However, when we deal with fixed-size chunks (0x418 bytes in this case) it can be very challenging to find a suitable object of that size in order to spray the heap reliably. Finding a kernel object that could fit this requirement was a bit tricky, but thanks to this article by ‘j00ru’, I managed to get the one I needed.

Spraying the Kernel Paged Pool

Private Namespaces are indeed a useful way of creating paged pool objects of which we can control the size and this fact makes them ideal for exploiting this bug.
By creating multiple private namespaces with boundary descriptor names that have a well-crafted length, we can achieve the following memory layout:

Figure 3. Heap-Spraying

Figure 3. Heap-Spraying

So in this case, we can’t control the size of the paged pool chunk that we can overflow, but we can control the size of a paged pool object to a certain extent. What is shown in Figure 3 actually refers to just one memory page (of size 4kb) in order to demonstrate what the paged pool starts to look like.

As you can see, we have one object that we control at the beginning of the memory page, then we have some free space of size 0x3b8 bytes, and finally two contiguous objects that we control until the end of the memory page. By crafting boundary descriptor names with variable length, we can even occupy the entire memory page with objects that we control:

Figure 4. Heap-Spraying #2

Figure 4. Heap-Spraying #2

However, since the buffer that we can overflow is of a fixed-size (0x418 bytes) and we are targeting for corruption the last allocated object in the memory page, it doesn’t really matter what there is inside the space at page_allocation_base + 0x418 since the size of this chunk is of size 0x3b8, which is not of our interest. In other words, we can allow the kernel to use it at will.

By using Process Explorer from Sysinternals we can have a better view of how the paged pool memory allocations look like after spraying the heap, but before punching memory holes to create room for ‘SnxN’ tagged allocations (Figure 5).

Figure 5. Pagedpool memory allocations layout

Figure 5. Pagedpool memory allocations layout

 

The following figure shows the layout of a memory page after successfully spraying the heap and punching memory holes. Our exploit triggers the bug that allows us to overflow the ‘SnxN’ tagged buffer and corrupt the adjacent object that we control.

Figure 6. Heap-Spraying #3

Figure 6. Heap-Spraying #3

Private namespaces, which are implemented as directory objects, are not only interesting because of the ability that they give to the attacker to manipulate the size of the allocated paged pool chunk. They also allow us to control the execution by overwriting the pointer stored in the LIST_ENTRY field of the NAMESPACE_DESCRIPTOR structure. This field links the aforementioned structure into a linked list of all the private namespaces available in the system.

Assuming that we have successfully managed to corrupt the LIST_ENTRY field of the NAMESPACE_DESCRIPTOR structure of a specific private namespace, then upon deletion of this we are able to trigger a write-what-where condition.

Figure 7. Not-So-Safe Unlinking (Win 7 SP1)

Figure 7. Not-So-Safe Unlinking (Win 7 SP1)

However, this method will not work from Windows 8 and above because the kernel implements safe unlinking of LIST_ENTRY structures which mitigates this method of exploitation.

Figure 8. Safe Unlinking (Win 8.1)

Figure 8. Safe Unlinking (Win 8.1)

I noticed during the process of creating the exploit that it seems to be possible to take advantage of this bug using the same objects but without relying on this specific method. That being said, I still decided to do it that way for the sake of writing a working proof-of-concept exploit for this vulnerability targeting Windows 7 SP1 x86.

In the following figure, we show a directory object of a private namespace before and after corruption. Notice that we have overwritten the LIST_ENTRY field of the NAMESPACE_DESCRIPTOR structure with a userland address (0x41414141) that we control.

Figure 9. Directory Object of PrivateNamespace – Before and after corruption.

Figure 9. Directory Object of PrivateNamespace – Before and after corruption.

Exploitation – Controlling the EIP

After corrupting the directory object (Figure 9), we need to take control of the execution flow and redirect it to our payload.

We used the write-what-where condition to overwrite a function pointer in HalDispatchTable, and more specifically the pointer to hal!HaliQuerySystemInformation function which is stored at HalDispatchTable+sizeof(ULONG_PTR). We can then redirect the execution on our payload by calling ntdll!NtQueryIntervalProfile from userland.

The calling function sequence is: ntdll!NtQueryIntervalProfile à nt!NtQueryIntervalProfile à nt!KeQueryIntervalProfileà call   [nt!HalDispatchTable+sizeof(ULONG_PTR)] (0x41414141).

What we know so far is that we can overwrite an arbitrary pointer in kernel address space, and control the EIP via this hijack. However, this is not enough to have a working exploit.

The write-what-where condition is triggered upon unlinking a private namespace from the list of private namespaces available in the system. We are expecting this to happen once we try to close the handle, or in other words free the directory object of a particular private namespace.

According to MSDN we can achieve so by calling ClosePrivateNamespace. In the aforementioned article ‘j00ru’ suggests to call CloseHandle in order to achieve this.

After examining how ClosePrivateNamespace behaves in userland, I noticed that it is basically a combination of calling the undocumented ZwDeletePrivateNameSpace and ZwClose (CloseHandle in userland) kernel functions in this order. So basically, the kernel first unlinks the private namespace, then destroys the directory object that is ‘hosting’ it.  This is actually a very interesting detail because it can help us to build a more stable exploit.

Remember, that at the moment of unlinking the private namespace in order to trigger the write-what-where condition the directory object and its pool chunk header have been corrupted due to the heap overflow. This means that by starting to free directory objects until we meet the corrupted one and proceed with the exploit the following situation might occur. If we free an object of which the pool chunk allocation header references the previous (corrupted) object, we are going to get a BSoD screen. This is because the kernel compares the actual size of the previous object (stored in the corrupted pool chunk header) with the size value stored in the object that we currently trying to free.

Furthermore, we don’t want to free our corrupted object before our payload has been executed and we have successfully fixed it, otherwise the host will go down again because of these kernel security related checks.

We can avoid this situation by separating these two stages. Indeed, we can first trigger the what-where condition just by calling ZwDeletePrivateNameSpace for all the potentially corrupted objects. This will trigger the unlinking of the private namespace that we are targeting, but doesn’t destroy the directory object itself. The kernel will also overwrite the LIST_ENTRY field of the NAMESPACE_DESCRIPTOR with a NULL pointer in order to indicate that this namespace has been unlinked already, but we can restore this later during the post-exploitation clean-up stage.

Finally, in our payload we can safely fix the corrupted directory object and the associated NAMESPACE_DESCRIPTOR structure so that the private namespace can be finally correctly unlinked upon terminating the exploit process.

Vendor’s Fix

Figure 10. Vulnerable Function

Figure 10. Vulnerable Function

 

Figure 11. Fixed Function

Figure 11. Fixed Function

 

Did you spot the difference? If not, don’t worry. Indeed it is not very clear, but if you look closely you will notice an added call sub_C161C instruction which basically calls a subroutine that is not visible here. Its purpose is to verify that the size of the supplied data fits the fixed-size ‘SnxN’ tagged allocation. If it doesn’t, then the driver calls ExAllocatePoolWithTag in order to allocate a new paged pool chunk where the data can be safely copied.

As a final note, it is very important to mention that the allocation of the ‘SnxN’ tagged buffers is not directly controlled by us. In a common scenario the memory allocation that we overflow in kernel address space occurs during the IOCTL request. However, this is not the case and we will leave it as an exercise to the reader to find a way to control this exploitation stage.

Demonstration Video

 

To contact Nettitude’s editor, please email media@nettitude.com.

McAfee File Lock Driver – Kernel Memory Leak

  • CVE: CVE-2015-8772
  •  Vendor: McAfee – Intel Security
  •  Reported by: Kyriakos Economou
  •  Date of Release: 26/01/2016
  •  Date of Fix: N/A
  •  Affected Products: Multiple
  •  Affected Version: McPvDrv.sys v4.6.111.0
  •  Fixed Version: N/A

Description:

McAfee File Lock Driver does not handle correctly IOCTL_DISK_VERIFY IOCTL requests, which leads to kernel memory leak through specifically crafted IOCTLs. Normally the IOCTL_DISK_VERIFY IOCTL is used to verify an extent on a fixed disk and doesn’t return any data.

We have verified this issue in the latest McAfee File Lock v5.x which ships with McAfee total protection suite. However, other products that include this package will also be affected.

Vulnerable module: McPvDrv.sys v4.6.111.0

Earlier versions of this kernel driver are probably affected by the same issue.

Impact:

A local attacker might be able to disclose sensitive information from kernel memory or crash the affected host.

Technical Details:

When we send an IOCTL_DISK_VERIFY IOCTL request the input buffer parameter of DeviceIoControl function must be a pointer to a VERIFY_INFORMATION data structure.

typedef struct _VERIFY_INFORMATION {
LARGE_INTEGER StartingOffset;
DWORD Length;
} VERIFY_INFORMATION, *PVERIFY_INFORMATION;

The kernel memory leak is generated by the fact that the McPvDrv.sys driver doesn’t validate the VERIFY_INFORMATION.Length which is controlled by our input buffer. Furthermore, the driver trusts that value as the size of the input buffer allocated in kernel space, causing the associated function to read data passed the size of the speficied input buffer and read arbitrary data from kernel address space back to userland into a specified output buffer.

Disclosure Log:

Vendor Contacted: 16/09/2015
Request for feedback: 21/09/2015 – No response
Request for feedback: 08/10/2015 – No response
Request for feedback: 13/01/2016 – No response
Public Disclosure: 26/01/2016

Copyright:

Copyright © Nettitude Limited 2016, All rights reserved worldwide.

Disclaimer:

The information herein contained may change without notice. Any use of this information is at the user’s risk and discretion and is provided with no warranties. Nettitude and the author cannot be held liable for any impact resulting from the use of this information.

To contact Nettitude’s editor, please email media@nettitude.com

McAfee File Lock Driver – Kernel Stack Based BOF

  • CVE: CVE-2015-8773
  •  Vendor: McAfee – Intel Security
  •  Reported by: Kyriakos Economou
  •  Date of Release: 26/01/2016
  •  Date of Fix: N/A
  •  Affected Products: Multiple
  •  Affected Version: McPvDrv.sys v4.6.111.0
  •  Fixed Version: N/A

Description:

McAfee File Lock Driver does not handle correctly GUIDs of the encrypted vaults, which allows to crash the host by crafting a specific IOCTL with a malformed Vault GUID which is used to identify an object of FILE_DEVICE_DISK DeviceType, causing a kernel stack based buffer overflow.

We have verified this issue in the lastest McAfee File Lock v5.x which ships with McAfee total protection suite. However, other products that include this package will also be affected.

Vulnerable module: McPvDrv.sys v4.6.111.0

Earlier versions of this kernel driver are probably affected by the same issue.

Impact:

The return address is protected by a security cookie, so exloiting this issue further than crashing the host doesn’t seem to be possible.

Technical Details:

GUID example:

867ba474 34 00 65 00 39 00 38 00 37 00 66 00 61 00 34 00 2d 00 39 00 66 00 38 00 4.e.9.8.7.f.a.4.-.9.f.8.
867ba48c 33 00 2d 00 34 00 30 00 61 00 64 00 2d 00 61 00 61 00 31 00 66 00 2d 00 3.-.4.0.a.d.-.a.a.1.f.-.
867ba4a4 62 00 35 00 33 00 65 00 61 00 35 00 64 00 63 00 00 00 00 00 00 00 00 00 b.5.3.e.a.5.d.c……..

Parsing GUID:

95e77094 8b4d08 mov ecx,dword ptr [ebp+8] <– Pointer to Vault’s GUID (unicode)
95e77097 0fb701 movzx eax,word ptr [ecx] <– start reading GUID
95e7709a 83c40c add esp,0Ch
95e7709d 6685c0 test ax,ax
95e770a0 7426 je McPvDrv+0x30c8 (95e770c8)
95e770a2 0fb7c0 movzx eax,ax
95e770a5 8d957cffffff lea edx,[ebp-84h] <— load to EDX the stack buffer address
95e770ab eb03 jmp McPvDrv+0x30b0 (95e770b0)
95e770ad 8d4900 lea ecx,[ecx]
95e770b0 663d2d00 cmp ax,2Dh <— check if it is ‘-‘ character
95e770b4 7406 je McPvDrv+0x30bc (95e770bc)
95e770b6 668902 mov word ptr [edx],ax <— if not write the character in the stack buffer
95e770b9 83c202 add edx,2 <– increase buffer index by 2
95e770bc 0fb74102 movzx eax,word ptr [ecx+2] <— get next character
95e770c0 83c102 add ecx,2
95e770c3 6685c0 test ax,ax
95e770c6 75e8 jne McPvDrv+0x30b0 (95e770b0) <— loop if not x00x00

Specifying a long GUID through a crafted IOCTL overflows the kernel stack based buffer.
The next algorithm is vulnerable as well to stack based buffer overflow, and it overflows stack based buffer again used by another function, assuming that the GUID was not long enough to overwrite the stack cookie, but long enough to allow the execution flow to go on and trigger this bug later.

Device Name example:

a4a2fa30 68 4f ac 85 29 2b e3 82 02 0d b4 82 01 00 00 00 00 00 00 00 5c 00 hO..)+……………
a4a2fa46 44 00 65 00 76 00 69 00 63 00 65 00 5c 00 4d 00 63 00 50 00 65 00 D.e.v.i.c.e..M.c.P.e.
a4a2fa5c 72 00 73 00 6f 00 6e 00 61 00 6c 00 56 00 61 00 75 00 6c 00 74 00 r.s.o.n.a.l.V.a.u.l.t.
a4a2fa72 73 00 5c 00 4d 00 63 00 50 00 76 00 44 00 72 00 76 00 7b 00 34 00 s..M.c.P.v.D.r.v.{.4.
a4a2fa88 65 00 39 00 38 00 37 00 66 00 61 00 34 00 39 00 66 00 38 00 33 00 e.9.8.7.f.a.4.9.f.8.3.
a4a2fa9e 34 00 30 00 61 00 64 00 61 00 61 00 31 00 66 00 62 00 35 00 33 00 4.0.a.d.a.a.1.f.b.5.3.
a4a2fab4 65 00 61 00 35 00 64 00 63 00 7d 00 00 00 00 00 b7 16 37 a4 d8 fa e.a.5.d.c.}…….7…

DeviceName/GUID concatenation:

95e770dc b90c000000 mov ecx,0Ch
95e770e1 be70eae795 mov esi,offset McPvDrv+0xaa70 (95e7ea70)
95e770e6 f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
95e770e8 8bc3 mov eax,ebx
95e770ea 66a5 movs word ptr es:[edi],word ptr [esi]
95e770ec 83c0fe add eax,0FFFFFFFEh
95e770ef 90 nop
95e770f0 668b4802 mov cx,word ptr [eax+2]
95e770f4 83c002 add eax,2
95e770f7 6685c9 test cx,cx
95e770fa 75f4 jne McPvDrv+0x30f0 (95e770f0)
95e770fc 8b0d70ece795 mov ecx,dword ptr [McPvDrv+0xac70 (95e7ec70)]
95e77102 8908 mov dword ptr [eax],ecx
95e77104 8b1574ece795 mov edx,dword ptr [McPvDrv+0xac74 (95e7ec74)]
95e7710a 895004 mov dword ptr [eax+4],edx
95e7710d 8b0d78ece795 mov ecx,dword ptr [McPvDrv+0xac78 (95e7ec78)]
95e77113 894808 mov dword ptr [eax+8],ecx
95e77116 8b157cece795 mov edx,dword ptr [McPvDrv+0xac7c (95e7ec7c)]
95e7711c 89500c mov dword ptr [eax+0Ch],edx
95e7711f 668b0d80ece795 mov cx,word ptr [McPvDrv+0xac80 (95e7ec80)]
95e77126 8bfb mov edi,ebx
95e77128 66894810 mov word ptr [eax+10h],cx
95e7712c 83c7fe add edi,0FFFFFFFEh
95e7712f 90 nop
95e77130 668b4702 mov ax,word ptr [edi+2]
95e77134 83c702 add edi,2
95e77137 6685c0 test ax,ax
95e7713a 75f4 jne McPvDrv+0x3130 (95e77130)
95e7713c 8b1560ece795 mov edx,dword ptr [McPvDrv+0xac60 (95e7ec60)]
95e77142 8d857cffffff lea eax,[ebp-84h]
95e77148 8917 mov dword ptr [edi],edx
95e7714a 8bd0 mov edx,eax
95e7714c 8d642400 lea esp,[esp]
95e77150 668b08 mov cx,word ptr [eax]
95e77153 83c002 add eax,2
95e77156 6685c9 test cx,cx
95e77159 75f5 jne McPvDrv+0x3150 (95e77150)
95e7715b 8bfb mov edi,ebx
95e7715d 2bc2 sub eax,edx
95e7715f 83c7fe add edi,0FFFFFFFEh
95e77162 668b4f02 mov cx,word ptr [edi+2]
95e77166 83c702 add edi,2
95e77169 6685c9 test cx,cx
95e7716c 75f4 jne McPvDrv+0x3162 (95e77162)
95e7716e 8bc8 mov ecx,eax
95e77170 c1e902 shr ecx,2
95e77173 8bf2 mov esi,edx
95e77175 f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
95e77177 8bc8 mov ecx,eax
95e77179 83e103 and ecx,3
95e7717c 83c3fe add ebx,0FFFFFFFEh
95e7717f f3a4 rep movs byte ptr es:[edi],byte ptr [esi]
95e77181 8bfb mov edi,ebx
95e77183 668b4702 mov ax,word ptr [edi+2]
95e77187 83c702 add edi,2
95e7718a 6685c0 test ax,ax
95e7718d 75f4 jne McPvDrv+0x3183 (95e77183)
95e7718f a150ece795 mov eax,dword ptr [McPvDrv+0xac50 (95e7ec50)]
95e77194 8b4dfc mov ecx,dword ptr [ebp-4]
95e77197 8907 mov dword ptr [edi],eax

The function above concatenates L”DeviceMcPersonalVaultsMcPvDrv” with the specified GUID that was read from the previous function.

It is used by a call to IoCreateDevice in order to create a FILE_DEVICE_DISK DeviceType.

DeviceName Example: L”DeviceMcPersonalVaultsMcPvDrv{4e987fa49f8340adaa1fb53ea5dc}”

Since the concatenation happens again inside a stack based buffer, it is possible to cause a buffer overflow.

Return addresses for both functions are protected by a stack cookie, so exploiting this issue further than a BSoD is probably not possible.

Disclosure Log:

Vendor Contacted: 16/09/2015
Request for feedback: 21/09/2015 – No response
Request for feedback: 08/10/2015 – No response
Request for feedback: 13/01/2016 – No response
Public Disclosure: 26/01/2016

Copyright:

Copyright © Nettitude Limited 2016, All rights reserved worldwide.

Disclaimer:

The information herein contained may change without notice. Any use of this information is at the user’s risk and discretion and is provided with no warranties. Nettitude and the author cannot be held liable for any impact resulting from the use of this information.

To contact Nettitude’s editor, please email media@nettitude.com