DirectX to the Kernel

December 04, 2018 | Fritz Sands

The operating system kernel is the final goal for every great exploit chain. You can look at the entries in the Zero Day Initiative (ZDI) Pwn2Own contests over the years to see that process at work. The Windows kernel has been subject to many points of attack. One of my favorites is abuse of DeviceIoControl calls to various drivers since this allows access to so many drivers written by so many vendors, many of which are not all that well written or tested.

Over the years, most of the attacks to penetrate into the Windows kernel have gone through win32k.sys -- a kernel-mode device driver that controls the Windows graphic and window management system. When Microsoft moved this functionality out of CSRSS and into the kernel twenty years ago, it instantly doubled or tripled the attack surface into the Windows kernel -- and that has been a rich mine of vulnerabilities ever since.

Another large attack surface has opened up since WDDM (Windows Display Driver Model) supplanted the earlier XDDM over the last decade. Calls to initiate actions by the display system start by going through win32k.sys, but after that there are direct calls by user processes into dgxkrnl.sys and other drivers through entry points in GDIPlus. This expanded attack surface is an inviting target for researchers.

In the spring on 2018, the ZDI purchased five vulnerabilities from ChenNan and RanchoIce of Tencent ZhanluLab that target the DirectX kernel interfaces. These purchases resulted in four CVE's from Microsoft. This post covers the vulnerabilities and provides Proof of Concept sources published on our site. 

Additionally, one of the attacks (ZDI-18-946/CVE-2018-8405) was featured in a presentation by Rancho and ChenNan at the 44CON conference in September. I highly recommend that researchers review the slide deck from that presentation.

An overview of DirectX

Before diving into the vulnerabilities, let’s take a brief look at the DirectX interface and drivers.

The DirectX graphics kernel subsystem consists of three kernel-mode drivers: dxgkrnl.sys, dxgmms1.sys, and dxgmms2.sys. These drivers communicate to the user through win32k.sys and through their own set of interfaces. They also communicate with BasicRender.sys, BasicDisplay.sys, and the display miniport drivers.

DirectX defines a number of complex kernel objects, most of which have names that begin with DXG. The user interfaces with DirectX through a number of complex API entry points , many of which begin with D3DKMT and others of which begin with DXGK.

Here are a few of the more interesting entry points:

D3DKMTEscape -- this entry point takes a completely user-controlled blob of data as an input. This data blob can be huge, and so there is a strong temptation to leave it in user memory instead of capturing it in the kernel during the transition to kernel processing. This pattern makes the invoked kernel routine a ripe candidate for forgetting try blocks and for time of check to time of use (TOC/TOU) vulnerabilities. The data is not in a standardized structure, so every driver has different definitions.

D3DKMTRender -- this entry point is the heart of actually rendering graphic data. User-address commands and patch buffers are interpreted by the kernel drivers and are, in fact, passed to the miniport drivers. Again, this is ripe for race conditions. Additionally, rendering can spawn worker threads, which make race condition vulnerabilities more likely.

D3DKMTCreateAllocation -- this entry point allocates memory. Issues (see ZDI-18-946 below) can arise because of the complex interplay between different flags and handles passed into the API call. 

One excellent overview of WDDM from an attack perspective is a 2014 Black Hat presentation by Ilja van Sprundel of IOActive entitled "Windows Kernel Graphics Driver Attack Surface" [PDF]. I highly recommend reading it. The presentation goes into great detail on the complex attack surface of the kernel side of WDDM.

Walkthrough of the vulnerabilities

The Proof of Concept (PoC) sources are found here. If you are going to reproduce the crashes, you need to go back to a version of Windows before August 2018 (when Microsoft patched out the vulnerabilities). Remember to attach a kernel debugger to the machine under test and set special pool on the drivers under attack. I tested these vulnerability reports on Windows 10 x64.

ZDI-18-946/CVE-2018-8405 - D3DKMTCreateAllocation Type Confusion Vulnerability

The first vulnerability we’ll review is in the DXGDEVICE::CreateAllocation method in dgxkrnl.sys, is exposed through the D3DKMTCreateAllocation method, and could allow a local attacker to escalate privileges to System. Our advisory for this can be found here, and the patch from Microsoft is located here. The bug results from the lack of proper validation of user-supplied data, which can result in a type confusion condition.

To see this in action, enable special pool on dxgkrnl.sys before running the PoC. The type confusion results from inappropriate use of the CrossAdapter flag in an allocation. The PoC code uses a CrossAdapter flag of 0 for an allocation, and then passes the resultant handle into a second allocation in which it sets a CrossAdapter flag of 1.

And here is the blue screen analysis:

The faulting code is in DXGDEVICE::CreateAllocation and is a classic type confusion walk off of the end of an allocation:

ZDI-18-947/CVE-2018-8406 - D3DKMTRender Type Confusion Vulnerability

The next vulnerability exists in the dxgmms2.sys driver, and is exposed through the D3DKMTRender method. This vulnerability also could allow a local attacker to escalate privileges to System. Our advisory for this can be found here, and the patch from Microsoft is located here. Like the first example, this bug results in a type confusion condition. While similar in nature, these bugs have different root causes.

Again, you need to enable special pool on dxgkrnl.sys and dxgmms2.sys to see these bugs, and, of course, attach a kernel debugger to your target machine. This type confusion results from allocation operation that gets confused between two different adapters.

Relevant PoC code:

PoC crash details:

Vulnerable code:

ZDI-18-950/CVE-2018-8400 - D3DKMTRender Untrusted Pointer Dereference Vulnerability

This next vulnerability is also exposed through the D3DKMTRender routine. The vulnerability is in the DGXCONTEXT::ResizeUserModeBuffers method in dxgkrnl.sys. Our advisory for this can be found here, and the patch from Microsoft is located here. In this case, the bug is caused by the lack of proper validation of a user-supplied value prior to dereferencing it as a pointer. The resulting pointer dereference is due to the driver trusting a flag set by the user. Here are the relevant PoC details:

Which results in the crash:

Called from:

Vulnerable code:

Obviously, this flag from the user should not lead to an arbitrary dereference in the kernel.

ZDI-18-951/CVE-2018-8401 – BasicRender Race Condition Vulnerability

This final vulnerability is a bit more complex, as the vulnerability resides in the processing of the D3DKMTMarkDeviceAsError API and the D3DKMTSubmitCommand API by the BasicRender driver. Our advisory for this can be found here, and the patch from Microsoft is located here. Shared resources are not properly secured, which can result in a memory corruption condition. An attacker can use this to escalate privileges to SYSTEM. This type of elevation is often used by malware to install itself once a user clicks something they shouldn’t. Note that Microsoft gave one CVE for this bug and for ZDI-18-949, indicating an identical root cause.

The PoC code for the two cases are related but differ.

Key part of first PoC:

Each call to SubmitCommand spawns a thread through VidSchiWorkerThread. The call to MakeDeviceError changes the state of the same objects and a race condition occurs.

Here is the resulting crash:

The race condition is between two modifications of the same location:

For ZDI-18-949, you can see the difference in the PoC code despite the same root cause. Here’s the key part of this PoC:

Executing this PoC causes a crash in the Run method:

Here’s the vulnerable code:

The vulnerable code crashes the second time through Run, but not the first time.

Conclusions

WDDM and the DirectX graphics kernel code allow for a very powerful and flexible graphics system for Windows. They do this through use of a number of very complex objects and by creating a number of new complicated interfaces to user code. The Proofs of Concept given here should give you some idea of the complexity of the objects implemented in DirectX and the scope available for future research in this area. I am quite sure that this pond is not fished out.

Direct static analysis could give you attack information. However, this is certainly a daunting task. Another possibility is to set up a fuzzing framework to set different values into different flags and call DirectX methods in different orders and look for a crash. Of course, you can also add multiple threads changing and freeing data to investigate the possibility of race conditions and TOC/TOU. Remember to set special pool on all of the relevant drivers.

As always, when you find new vulnerabilities, the Zero Day Initiative is interested in talking with you. Until then, you can find me on Twitter at @FritzSands, and follow the team for the latest in exploit techniques and security patches.