If you're only interested in the Driver template and Client template you can skip to
#driver-code-template-breakdown
Windows Architecture Refresher
Before you are charts that were taken from "Windows Kernel Development" by Pavel Yosifovich, presented here for a quick representation of the general architecture of Windows OS and the general flow of a System Service Request (Syscall).
Getting Started with Driver Development
When developing a Driver, you should have remote machine in which you can test the driver, or at the very least have a VM with a snapshot since there is a good chance to crash the system if you're not careful.
Modern Windows machines have security mechanisms that only allows trusted drivers to load and execute such as Driver Signature Enforcement (DSE).
To conveniently test drivers on modern system we can disable this mechanism using the native tool bcdedit.exe
Requirements
Frameworks
Common Frameworks for driver development are WDM and KMDF, I will use WDM in this project.
Type of Drivers
Drivers are divided into two types, User-Mode drivers and Kernel-Mode drivers. Within kernel-mode drivers are additional types of drivers in which you can read more about here
The driver template in this page is a simple software driver.
Enable Test Mode
To allow unsigned or self-signed driver to load and execute, use bcdedit.exe
bcdedit.exe /set testsigning on
After a successful prompt you will need to reboot the machine.
General Flow of Driver Execution
Driver Initialization
DriverEntry is the Entry point of a Driver, it's the equivalent of a main() of user-mode programs. Most software drivers will be required to do the following actions before the driver can receive requests from clients.
Set unload routine
Set dispatch routines the driver support (e.g. IRP_MJ_CREATE for example)
Create device object -> IoCreateDevice()
Create a symbolic link to the device object -> IoCreateSymbolicLink()
extern"C"NTSTATUSDriverEntry(_In_PDRIVER_OBJECT DriverObject,_In_PUNICODE_STRING RegistryPath) {UNREFERENCED_PARAMETER(DriverObject);UNREFERENCED_PARAMETER(RegistryPath); // 1. Setting the DriverUnload (which hasn't been defined yet)DriverObject->DriverUnload = UnloadTestDriver; // 2. Setting Dispatch routineDriverObject->MajorFunction[IRP_MJ_CREATE] = BlueDriverCreateClose; // 3. Create Driver's DeviceObject; This object is accessible from user-mode and allows communication to the driver UNICODE_STRING devName =RTL_CONSTANT_STRING(L"\\Device\\BlueStreetDriver"); PDEVICE_OBJECT DeviceObject; NTSTATUS status =IoCreateDevice(DriverObject,0,&devName, FILE_DEVICE_UNKNOWN,0, FALSE,&DeviceObject);if (!NT_SUCCESS(status)) {TRACE("Failed to create device object (0x%08X)\n", status);return status; } // 4. Create Symbolic link for the Device which is accessible from user-mode UNICODE_STRING symLink =RTL_CONSTANT_STRING(L"\\??\\BlueStreetDriver"); status =IoCreateSymbolicLink(&symLink,&devName);if (!NT_SUCCESS(status)) {TRACE("Failed to create symbolic link (0x%08X)\n", status);IoDeleteDevice(DeviceObject);return status; }return STATUS_SUCCESS;}
Communication between User-Mode & Kernel-Mode
On a basic level, for a user-mode client to communicate with a driver it needs to:
Open a handle to the device's object -> CreateFile()
Send IO Control Requests -> DeviceIoControl()
The handle we open to the Device object is a handle to it's symbolic link which the Driver created, only using the symbolic link can we get a handle and communicate with the driver.
Driver & Client Template Breakdown
Template
#include<ntifs.h>#include"BasicDriverCommon.h"#defineNT_SUCCESS(Status) (((NTSTATUS)(Status)) >=0)#defineTRACE(format, ...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, format, __VA_ARGS__)NTSTATUSBlueDriverCreateClose(_In_PDEVICE_OBJECT DeviceObject,_In_PIRP Irp);NTSTATUSBlueDriverDeviceControl(_In_PDEVICE_OBJECT DeviceObject,_In_PIRP Irp);// MJ Function Create & CloseNTSTATUSBlueDriverCreateClose(PDEVICE_OBJECT DeviceObject,PIRP Irp) {UNREFERENCED_PARAMETER(DeviceObject);Irp->IoStatus.Status = STATUS_SUCCESS;Irp->IoStatus.Information =0;IoCompleteRequest(Irp, IO_NO_INCREMENT);return STATUS_SUCCESS;}// MJ Function Control DeviceNTSTATUSBlueDriverDeviceControl(PDEVICE_OBJECT DeviceObject,PIRP Irp) {UNREFERENCED_PARAMETER(DeviceObject);TRACE("Entered Device Control MJ Function\n"); // get our IO_STACK_LOCATIONauto stack =IoGetCurrentIrpStackLocation(Irp); // IOC_STACK_LOCATION*auto status = STATUS_SUCCESS;switch (stack->Parameters.DeviceIoControl.IoControlCode) {case IOCTL_BLUESTREET_SEND_DATA: {TRACE("BLUESTREET_SEND_DATA Control Activated\n"); // Data from IRP can be accessed via stack->Parameters.DeviceIoControl.Type3InputBufferauto BufferContent = (UINT32*)stack->Parameters.DeviceIoControl.Type3InputBuffer;int bufferSize =sizeof(*BufferContent);TRACE("BufferSize: %i\n", bufferSize);TRACE("BufferContent: %u\n",*BufferContent);break; }default: status = STATUS_INVALID_DEVICE_REQUEST;break; }Irp->IoStatus.Status = status;Irp->IoStatus.Information =0;IoCompleteRequest(Irp, IO_NO_INCREMENT);return status;}voidUnloadTestDriver(_In_PDRIVER_OBJECT DriverObject) {TRACE("Sample Driver Unload called\n"); UNICODE_STRING symLink =RTL_CONSTANT_STRING(L"\\??\\BlueStreetDriver"); // Delete symbolic linkIoDeleteSymbolicLink(&symLink); // Delete Device objectIoDeleteDevice(DriverObject->DeviceObject);}extern"C"NTSTATUSDriverEntry(_In_PDRIVER_OBJECT DriverObject,_In_PUNICODE_STRING RegistryPath) {UNREFERENCED_PARAMETER(DriverObject);UNREFERENCED_PARAMETER(RegistryPath);TRACE("[+] Entered DriverEntry\n"); // Configuring our Driver's MAJOR Functions DriverObject->DriverUnload = UnloadTestDriver;DriverObject->MajorFunction[IRP_MJ_CREATE] = BlueDriverCreateClose;DriverObject->MajorFunction[IRP_MJ_CLOSE] = BlueDriverCreateClose;DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = BlueDriverDeviceControl; // Create Driver's DeviceObject; This object is accessible from user-mode and allows communication to the driver UNICODE_STRING devName =RTL_CONSTANT_STRING(L"\\Device\\BlueStreetDriver"); PDEVICE_OBJECT DeviceObject; NTSTATUS status =IoCreateDevice(DriverObject,0,&devName, FILE_DEVICE_UNKNOWN,0, FALSE,&DeviceObject);if (!NT_SUCCESS(status)) {TRACE("Failed to create device object (0x%08X)\n", status);return status; } // Create Symbolic link for the Device which is accessible from user-mode UNICODE_STRING symLink =RTL_CONSTANT_STRING(L"\\??\\BlueStreetDriver"); status =IoCreateSymbolicLink(&symLink,&devName);if (!NT_SUCCESS(status)) {TRACE("Failed to create symbolic link (0x%08X)\n", status);IoDeleteDevice(DeviceObject);return status; }return STATUS_SUCCESS;}
#include<Windows.h>#include<stdio.h>#include"..\BasicDriver\BasicDriverCommon.h"intError(constchar* message) {printf("%s (error=%d)\n", message,GetLastError());return1;}intmain(int argc,constchar* argv[]){unsignedint data =1234; // Get handle to device object n; HANDLE hDevice =CreateFile(L"\\\\.\\BlueStreetDriver", \ GENERIC_WRITE, FILE_SHARE_WRITE, \nullptr, OPEN_EXISTING,0,nullptr);if (hDevice == INVALID_HANDLE_VALUE) {returnError("[!] Failed to open device"); } DWORD returned; BOOL success =DeviceIoControl(hDevice, IOCTL_BLUESTREET_SEND_DATA,&data,sizeof(data),nullptr,0,&returned,nullptr);if (success)printf("[+] IOCTL was successfully sent\n");elseError("[!] Failed sending IOCTL\n");CloseHandle(hDevice);}
2. Starting the service and using Winobj.exe (sysinternals) to verify the symbolic link to the Device was created successfully.
3. Executing the client process that initiates a DeviceIoControl() to the driver and using DebugView.exe (sysinternals) to observe the TRACE messages and verify the data from user-mode passed to the driver.
Generic Notes about Drivers
PLACEHOLDER
References and Additional Read
For actual driver / kernel development I recommend these sources for start: