Sometimes doing detailed RE research on large complex device drivers might not be feasible: naming fields and fix all data structures might take a few days or a few weeks. and sometimes decompilers produce incorrect result due to compiler optimization. But we know most device drivers only consume public kernel routines, and Windows Driver Framework is now open sourced. Technically speaking, it is possible to host drivers in a special environment.
Hosting UMDF Drivers
It turns out not difficult to host User-mode drivers (UMDF) with specialized host process. The host process needs to load the driver using normal DLL load procedure, then fills in the WDF implementation using WDF Bind functions. The driver will run happily then once you have the way to handle IO request and send them to the actual target. Lumia 950/Lumia 950 XL’s Type-C controller driver is a notable example on this approach.
But how about kernel-mode drivers? In fact, a substantial portion of kernel-mode drivers can be ported to user-mode with few lines of code change, if:
- IO requests are completed using WDF primitives.
- No direct memory register access. Technically register regions can be mapped twice (PA -> km VA -> um VA), but I am crossing it out here for convenience.
- Code can live well in passive IRQL level. No processing logic should exist in other IRQLs. UMDF code all runs at passive IRQL.
Given these constraints, I found out that the ANX7816 HDMI/DPI transmitter driver is a fitting example to start with.
Write Your PE/COFF Loader
Since there’s no official way to load a kernel-mode driver into user-mode process, a customized PE/COFF Loader is required. Modern drivers have ASLR (dynamic base) and NX features enabled, so it is important to take care about relocation and page permissions. Thumb-2 code relocations need to be specially handled. Each MOV32(T) relocation comes with two instructions, the first MOVW sets the lower 16bit of the relocation address, and the second MOVT sets the higher 16bit.
In addition, the cookie canary needs to be initialized to some cryptography-safe random number. The canary is in .data + 0x4, and it is 4 bytes long. Otherwise you will get a quick qfastfail:
Once code is loaded into the memory, just invoke the standard DriverEntry function. The address is exactly the entry point address of the PE/COFF file.
Please also consider visiting the project for more information, including the reference source code.