I have just completed Pavel Yosifovich's excellent Windows Kernel Programming training (which I would highly recommend to anyone interested in learning more about the Windows kernel), and I came out of it with quite a lot of kernel driver code. Some of the things we did were writing our own version of the Sysinternals Process Monitor tool, and a proof of concept anti-ransomware tool; in both cases we were able to monitor everything happening on the filesystem, processes, and threads. You can do a lot in a kernel driver.
There is quite a bit involved in writing a Windows kernel driver, but every driver starts out the same way. So I've written some boilerplate starter code that I use every time I start writing a new kernel driver, which makes it a lot easy to get up and running quickly. In order to use this, you'll need to install the latest versions of the following:
- Windows 10 x64
- Visual Studio 2019 (any edition) with the C++ workload installed
- Windows 10 SDK
- Windows 10 Driver kit (WDK)
After you have the above install, you can start a new kernel driver project by opening Visual Studio, select "Create a new project", and choose the "Empty WDM Driver" project template. Unless you're developing a hardware driver or a filesystem minifilter driver, you'll need to delete the .inf file that Visual Studio puts into the project by default (most other drivers won't build with it present). After that, just add a new .cpp file to the project, and drop the following boilerplate code into it, and you're ready to go!
#include <ntddk.h> void MyDriverUnload(PDRIVER_OBJECT DriverObject); NTSTATUS MyDriverCreate(PDEVICE_OBJECT, PIRP Irp); NTSTATUS MyDriverClose(PDEVICE_OBJECT, PIRP Irp); NTSTATUS MyDriverRead(PDEVICE_OBJECT, PIRP Irp); NTSTATUS MyDriverWrite(PDEVICE_OBJECT, PIRP Irp); NTSTATUS MyDriverControl(PDEVICE_OBJECT, PIRP Irp); NTSTATUS CompleteRequest(PIRP Irp, NTSTATUS status = STATUS_SUCCESS, ULONG_PTR info = 0); extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING) { // Set driver unload function DriverObject->DriverUnload = MyDriverUnload; // Register dispatch routines DriverObject->MajorFunction[IRP_MJ_CREATE] = MyDriverCreate; DriverObject->MajorFunction[IRP_MJ_CLOSE] = MyDriverClose; DriverObject->MajorFunction[IRP_MJ_READ] = MyDriverRead; DriverObject->MajorFunction[IRP_MJ_WRITE] = MyDriverWrite; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyDriverControl; // Attempt to register device and symbolic link; break on failure NTSTATUS status; UNICODE_STRING devName = RTL_CONSTANT_STRING(L"\\Device\\MyDriver"); PDEVICE_OBJECT DeviceObject = nullptr; UNICODE_STRING symLink = RTL_CONSTANT_STRING(L"\\??\\MyDriver"); auto symLinkCreated = false; do { // Register device (no device extension, unknown device type, non-exclusive access) status = IoCreateDevice(DriverObject, 0, &devName, FILE_DEVICE_UNKNOWN, 0, FALSE, &DeviceObject); if (!NT_SUCCESS(status)) break; // Choose the method for storing and accessing data with the device. // Only applies to IRP_MJ_READ and IRP_MJ_WRITE. // IRP_MJ_DEVICE_CONTROL uses different methods (set in "CTRL_CODE" macro). DeviceObject->Flags |= DO_DIRECT_IO; // Alternatively: DO_BUFFERED_IO // Register symbolic link to device status = IoCreateSymbolicLink(&symLink, &devName); if (!NT_SUCCESS(status)) break; symLinkCreated = true; } while (false); // Check for errors if (!NT_SUCCESS(status)) { if (DeviceObject) IoDeleteDevice(DeviceObject); if (symLinkCreated) IoDeleteSymbolicLink(&symLink); return status; } return status; } // Called when unloading the driver. void MyDriverUnload(PDRIVER_OBJECT DriverObject) { UNREFERENCED_PARAMETER(DriverObject); // Clean up memory allocations, etc. here } // Dispatch routine that handles OpenFile calls to this driver's device object. NTSTATUS MyDriverCreate(PDEVICE_OBJECT, PIRP Irp) { return CompleteRequest(Irp); } // Dispatch routine that handles CloseFile calls to this driver's device object. NTSTATUS MyDriverClose(PDEVICE_OBJECT, PIRP Irp) { return CompleteRequest(Irp); } // Dispatch routine that handles ReadFile calls to this driver's device object. NTSTATUS MyDriverRead(PDEVICE_OBJECT, PIRP Irp) { return CompleteRequest(Irp); } // Dispatch routine that handles WriteFile calls to this driver's device object. NTSTATUS MyDriverWrite(PDEVICE_OBJECT, PIRP Irp) { return CompleteRequest(Irp); } // Dispatch routine that handles DeviceIoControl calls to this driver's device object. NTSTATUS MyDriverControl(PDEVICE_OBJECT, PIRP Irp) { return CompleteRequest(Irp); } // Helper function used internally for completing an IRP, at the end of all dispatch routines. NTSTATUS CompleteRequest(PIRP Irp, NTSTATUS status, ULONG_PTR info) { Irp->IoStatus.Status = status; Irp->IoStatus.Information = info; // 0 on failure, otherwise the number of bytes transferred (usually; refer to documentation) IoCompleteRequest(Irp, IO_NO_INCREMENT); // Boost thread priority here, if desired. return status; }
If you'd like to see some interesting things you can do with Windows kernel drivers, check out the Windows driver samples page in Microsoft's documentation or head over to Pavel's GitHub page for some other great examples of the power of Windows kernel functions.