Writing a Windows WDM driver

There is not enough information on developing windows drivers, perhaps because it is not widely used, nor that necessary any more.
It is still a lot of fun to write kernel mode code, apart from the constant rebooting.The number one resource on this topic is "Writing Windows WDM Device Drivers 1st Edition" by Chris Cant.

The Setup part

This can be a little bit of a hassle especially if you, like me, run a Linux host system. This will require two virtual machines. So this is by far the heaviest part. Begin by getting two virtual machines set up with Windows (the trial version will do fine). Pick one as the development box and one as the testing box. You can ease back on the system resources for the testing box, you'll be breaking it anyway. I also recommend setting up a shared folder between them, since you'll be transferring the compiled driver every time you rebuild.

Step 1

Get the WDK (Windows Driver Kit), and install it into Visual Studio on the dev box, get it here https://docs.microsoft.com/en-us/windows-hardware/drivers/download-the-wdk

Step 2

Get the WinDbg toolset, i like the newer looking "preview" version, but if you like the oldschool crusty look the old one should work too.

Step 3

Start a new project and make sure its the WDM thing, we don't want WDF.

Step 4

Delete the included INF file, as you'll be loading the driver with OSR, no installation configuration is needed.

Step 5

Disable driver signing in the project settings -> Driver signing.

Now you've gotten the dev box and a WDM project running. Next is setting up the test box. A few things are needed

One final thing is making sure you are able to load unsigned drivers, as we won't be signing ours for simplicity. There are a few guides on how to do this, but none i found works after a reboot (which will happen).
A solution to this is starting cmd elevated, and issuing bcdedit /set advancedoptions true Then rebooting into the advanced menu, and hitting 7 on the keyboard.

Advanced boot options for windows

When the machine is fully booted, take a snapshot of it (this is in general a good idea for quickly recovering from a crash).
Once it's all set up, launch WinDbg on the dev box, and verify that you can debug the target.

WDM

Windows Driver Model is an ancient framework for writing drivers introduced in Windows 98. Despite this, it is still available on the newest versions of Windows and probably will be for a long time, as many drivers are still today written with WDM. One way to check this is by opening a driver (.sys file) and looking for the IoCreateDevice(Secure) import.

WDM drivers are a little complex, and i won't be going over the specifics of how they work, for that i recommend the literature as well as the Microsoft documentation.

What you need to know is that they are installed into the system as a service, the service host then calls the driver entry point, and the rest of the set-up is up to the driver. Think of drivers as plugins to the IO stack of the kernel.

extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)

This is the DriverEntry function signature, you will be manipulating the DRIVER_OBJECT to define your own functions in the future.

For now let's start with a "Hello world" driver, as per programming tradition.

Since our driver will not be attached to any stdout sink, such as a terminal window, we will be printing the message with DbgPrint to be captured by WinDbg (or DebugView).

#include <wdm.h>
extern "C" NTSTATUS DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath)
{
    // The compiler will complain about unreferenced variables
    // Use this macro to avoid
    UNREFERENCED_PARAMETER(RegistryPath);
    UNREFERENCED_PARAMETER(DriverObject);
    
    DbgPrint("Hello World!");
    
    return STATUS_SUCCESS;
}

Compile this code and move the resulting .sys file to the testing machine. Start DebugView as administrator, capture the kernel messages, and set it to verbose. Then load the driver with the OSR loader.

Now this code is not actually a driver, as it does not register any Devices. The function IoCreateDevice serves this purpose. The signature of which is the following:

NTSTATUS IoCreateDevice(
  PDRIVER_OBJECT  DriverObject,
  ULONG           DeviceExtensionSize,
  PUNICODE_STRING DeviceName,
  DEVICE_TYPE     DeviceType,
  ULONG           DeviceCharacteristics,
  BOOLEAN         Exclusive,
  PDEVICE_OBJECT  *DeviceObject
);

You can choose not to name your device, but we will to allow access to it by means of opening a handle to \\.\DeviceName. Let us modify our code like so:

#include <wdm.h>

#pragma PAGEDCODE

void DriverUnload(IN PDRIVER_OBJECT pDriverObject)
{
	PAGED_CODE();

	PDEVICE_OBJECT pdo = pDriverObject->DeviceObject;

	if (pdo != NULL)
	{
		IoDeleteDevice(pdo);
	}
}

extern "C"
NTSTATUS DriverEntry(
	IN PDRIVER_OBJECT pDriverObject,
	IN PUNICODE_STRING pRegistryPath
)
{
	UNREFERENCED_PARAMETER(pRegistryPath);

	PDEVICE_OBJECT pdo;
	UNICODE_STRING devname;
	
	DbgPrint("Loading device");

	RtlInitUnicodeString(&devname, L"\\Device\\MyDriver");

	NTSTATUS stat = IoCreateDevice(pDriverObject,
		sizeof(ULONG),
		&devname,
		FILE_DEVICE_UNKNOWN,
		NULL,
		FALSE,
		&pdo);

	if (!NT_SUCCESS(stat))
	{
		DbgPrint("IoCreateDevice failed...");
		return stat;
	}
    
    pDriverObject->DriverUnload = DriverUnload;
    
	return STATUS_SUCCESS;
}

We added the DriverUnload function here to make sure we remove the device when we request it. As well as registering it into the DriverObject.

When you load this new driver, you should be able to find it with WinObj.

MyDriver visible in WinObj

It really does not do much, but you have managed to register a device on Windows, congrats!

Show Comments