This is the second part of my series of blogposts about hooking miniport and minifilter drivers. In this session I will focus on how to hook display miniport callbacks.
Port and miniport drivers are a concept that Microsoft uses to simplify the development of kernel code by different vendors. The port driver (Supplied by Microsoft) is responsible of performing common tasks and by that it helps vendors to avoid writing a lot of boilerplate code. Miniport drivers, supplied by third party vendors, are responsible for the execution tasks for a specific device. The miniport registers its callback functions with the port driver, which triggers them when needed.
In Windows Vista, Microsoft has introduced the concept of WDDM (Windows Display Driver Model) which simplifies the development of display drivers. WDDM allows one to develop a display driver in user mode (Implemented as DLL) and a display miniport in kernel mode, leaving most of the logic outside the kernel.
The display port driver is a part of the DirectX Graphics Kernel Subsystem (Dxgkrnl.sys). It handles requests from win32k and user mode interfaces and notifies the callbacks of the display miniports registered to it. This diagram demonstrates the model:
Independent Hardware Vendors (IHV), such as NVIDIA or AMD, are the common implementers of display miniport code which is responsible of all of the tasks related to their cards. Other parties that may implement display miniports are virtualization software vendors (e.g.: VMware, VirtualBox and more) which install it on their guest machines.
Hooking parts of WDDM (both User Mode and Kernel Mode) is very useful for cheat engines to manipulate API calls made to DirectX by video game processes. I chose to focus on hooking the miniport driver because it is the last station before the requests are sent to the cards themselves, and it is an opportunity to explain how the dxgkrnl driver implements the registration of miniport drivers and the invocation of miniport callbacks.
Display Miniport Internals
Registration of a display miniport driver
The display miniport is linked with displib.lib and uses its functions to interact with the dxgkrnl module. displib.lib exports the two interesting functions: DxgkInitialize and DxgkInitializeDisplayOnlyDriver (same as the previous function but for kernel mode display only drivers — KMDOD) which are responsible for the registration of display miniports with dxgkrnl. The display miniport drivers are supposed call those APIs during initialization. These functions send a couple of internal Ioctls to dxgkrnl, which returns a pointer to an internal function is responsible for initialization:
After that, DxgkInitialize and DxgkInitializeDisplayOnlyDriver call to their corresponding functions in dxgkrnl: DpiInitialize and DpiKmdDodInitialize. These functions finally call DpiInitializeEx which perform the real tasks to register the miniport.
DpiInitializeEx takes the following parameters: the driver object of the display miniport, the registry path of the driver object, a struct of type DRIVER_INITIALIZATION_DATA (It will be NULL if this is a KMDOD), struct of type KMDDOD_INITIALIZATION_DATA (It will be NULL if it is not KMDOD) and a Boolean which indicates whether or not its KMDOD. DRIVER_INITIALIZATION_DATA or KMDDOD_INITIALIZATION_DATA handle the callbacks for registration and the version of the functional interface implemented by the display miniport driver.
This function adds a new driver extension to the driver object:
IoAllocateDriverObjectExtension has the following arguments: The driver object which the extension will be associated to and an identifier to the extension (can be multiple extensions for a single driver object). As you can see the identifier for the extension is the driver object.
The function DpiInitializeEx copies the KMDDOD_INITIALIZATION_DATA or DRIVER_INITIALIZATION_DATA (corresponding to whether or not this is a KMDOD) into the driver extension, it adds a default dispatch function from dxgkrnl to the display miniport driver object and inserts a pointer to the driver extension into a global linked list.
Let’s take one miniport as an example:
Check the driver extension that belong to this driver object:
In the ClientDriverExtension field there is a list of driver extensions that are associated to the driver object.
The identifier of the driver extension is located at the ClientIdentificationAddress field. As you can see the ClientIdentificationAddress is equal to the pointer to the driver object. Thus, this driver extension is the driver extension which DpiInitializeEx allocate. To get the driver extension we take the address from the field ClientDriverExtension in DRIVER_EXTENSION and add 0x10h to it:
The functions marked in orange are the callbacks of the display miniport. The pointers in green are the previous node and the next node in the list of driver extensions of display miniport drivers.
A common mistake I saw in different forums is the idea that all of the callbacks are called from the driver extension, in fact, only few of (Like AddDevice callback) are. Most of the callbacks are called from an object called “DXGADAPTER”.
“An adapter is an abstraction of the hardware and the software capability of your computer. There are generally many adapters on your machine. Some devices are implemented in hardware (like your video card) and some are implemented in software (like the Direct3D reference rasterizer). Adapters implement functionality used by a graphic application. The following diagram shows a system with a single computer, two adapters (video cards), and three output monitors.”
DXGADAPTER is a kernel object that represents an adapter. The user mode application uses a handle to this object to perform jobs that are related to DXGADAPTER, such as: creating a device which can communicate with the miniport, enumerate the outputs, and more. The handle to this object can be opened by the functions D3DKMTOpenAdapterFromHdc, D3DKMTOpenAdapterFromLuid, D3DKMTOpenAdapterFromDeviceName and D3DKMTOpenAdapterFromGdiDisplayName. The handle is managed by a private table in dxgkrnl. When a user mode application uses an API which gets a handle to the adapter as a parameter, dxgkrnl looks up the handle in the private table and returns the pointer to the object.
The object created in the function CreateAdapter:
In the function DpiGetAdaperInfo the callbacks from the driver extension (Belong to driver object that is related to the adapter) are copied into a member in the object DXGADAPTER, For example:
After that the function DpiFdoSetDxgAdapter copies the pointer to this member into the device extension (Which belongs to device object which is related to the aforementioned driver object).
To sum it up visually:
Calling Miniport Callbacks
Unlike callbacks in the file system filter manager, miniport callbacks may be called in many different ways. Let’s dig into the callback DxgkDdiSetPointerPosition as an example:
The function DxgkWin32kSetPointerPosition is called by win32k module, which gets the adapter’s LUID (locally unique ID) as a parameter. The function references the DXGADAPTER member by its LUID. After that it calls DxgkSetPointerPosition:
DxgkSetPointerPosition is a generic function of the port driver. DxgkSetPointerPosition calls the function ADAPTER_DISPLAY::DdiSetPointerPosition which is responsible to notify the miniport callback:
How to Hook Miniport Callbacks
Now that we understand how the callbacks are managed, Let’s move to the fun part!!
Step One: Reference the driver object of the target display miniport driver
I used the function ObReferenceObjectByName which takes the driver object name as parameter and returns the pointer to the driver object of the miniport driver.
Step Two: Finding The Driver Extension of a Display Miniport
As I mentioned before, numerous driver extensions can be found in the driver object. For each driver extension there is an identifier (In the field ClientIdentificationAddress). To find the relevant driver extension we can use the function IoGetDriverObjectExtension and pass as arguments the pointer to the display miniport driver object and ClientIdentificationAddress object, which is in fact just the same pointer.
Step three: Initialization of a dummy miniport and searching for target miniport’s callbacks
Now that we have a pointer to the relevant driver extension, we need to find the offset of each callback in the driver extension. For that I used the same technique that I used in the previous part (Create a dummy miniport and find the offset by locating my dummy callbacks in my driver extension).
To initialize the miniport I used the function DxgkInitiliaze that I mentioned before:
Now that we have the offsets to the callbacks in the driver extension and the target miniport’s driver extension, we can uninitiliaze the miniport and easily find the pointers to the target miniport’s callbacks.
Step Four: Finding the Callbacks in the Miniport’s Adapter
As I mentioned before, most of the callbacks are called from the DXGADAPTER member. Because we don’t have the pointer to the adapter we need to locate it in the device extension of the device object of the target driver. In order to achieve that, I took each pointer in the device extension and found if it contains the callbacks of the target miniport that I found before. Once I found the adapter I could easily find the rest of the target callbacks using the same technique.
Step five: Hook the callbacks
We have the locations of the callbacks so we can easily hook them.
The POC code can be found in my GitHub repo (The POC was tested at Windows 10 64 bit version 1903).
Hook of CreateAllocation callback:
Hook of SubmitCommandVirtual callback:
As we can see our hook function is in the middle of the caller function and target callback of the display miniport (BasicRender).
- MSDN — Windows Vista And Later Display Driver Model Operation Flow
- Fritz Sands — Directx To The Kernel
- Zeronights — Graphic Mode To God Mode Discovery Vulnerabilities of GPU Virtualization
- ProjectZero — Attacking Windows Nvidia Driver
- Fanxiushu — WDDM Filter/Hook Driver
- Ilja van Sprundel — Windows Kernel Graphics Driver Attack Surface