Windows 驱动开发基础
什么是Windows 驱动
概念
Windows驱动程序(Device Driver)是一种特殊的系统软件,它运行在操作系统的内核模式下,充当硬件设备与操作系统之间的翻译官和协调者。从技术角度来看,驱动程序是一组遵循特定接口规范的函数集合,这些函数能够直接与硬件设备进行通信,同时向操作系统提供标准化的访问接口。
与普通应用程序不同,驱动程序拥有系统的最高权限。它可以访问硬件寄存器、操作物理内存、响应硬件中断,甚至可以修改操作系统内核的数据结构。这种特权级别使得驱动程序既强大又危险——一个设计良好的驱动可以大幅提升系统性能,而一个存在漏洞的驱动则可能导致系统崩溃或成为安全攻击的入口。
作用
驱动程序在现代计算机系统中扮演着不可替代的角色。从功能角度看,它实现了硬件抽象,使得应用程序开发者无需关心底层硬件的具体实现细节。从扩展性角度看,驱动程序机制使得操作系统能够支持无限多样的硬件设备,而无需修改操作系统本身。从安全角度看,驱动程序也被广泛应用于安全软件中,用于实现系统监控、进程保护、网络过滤等功能。
驱动开发环境
- 下载Visual Studio(SDK)
- WDK 引用网址:
- 下载 Windows 驱动程序工具包 (WDK)
注意: WDK和SDK版本需要配套, 先装Visual Studio(SDK),再装WDK。
核心概念
内核态与用户态
Windows 将系统划分为两个特权级别:内核态(Ring 0)和用户态(Ring 3)。
内核态是操作系统核心与驱动程序的运行环境,代码在此拥有最高权限,可以执行特权指令、访问任意内存并直接操作硬件。用户态则是普通应用程序的运行空间,权限受到严格限制,无法直接访问硬件或内核内存,越权操作会触发保护性异常。
这种隔离机制是系统稳定性的基础:即使应用程序崩溃,也不会影响内核。当用户程序需要执行特权操作时,必须通过系统调用切换到内核态,由内核代为完成。由于该过程涉及上下文保存和权限验证,频繁的切换会带来性能开销。
驱动程序运行在内核态,这既赋予它强大的能力,也意味着必须谨慎编写,否则可能直接影响系统安全与稳定。
设备对象(Device Object)
在 Windows 驱动编程中,设备对象(Device Object)是驱动程序向操作系统注册的内核数据结构,代表驱动能够提供服务的一个逻辑单元。
设备对象不一定对应真实的物理硬件。一个物理设备可能需要多个设备对象来表示不同功能,例如声卡可同时创建音频输出、音频输入和 MIDI 设备对象。设备对象也可以是完全虚拟的,如用于进程间通信的虚拟设备或用于系统监控的过滤设备。
驱动程序通过调用 IoCreateDevice
函数创建设备对象。此后,设备对象成为 I/O 管理器的管理目标,所有针对该设备的 I/O 请求都会以 IRP 的形式路由到驱动程序的处理函数。设备对象中包含设备类型、特性以及关联的驱动对象等关键信息,是驱动与操作系统交互的核心结构。
符号链接(Symbolic Link)
在 Windows 驱动开发中,符号链接是连接内核空间设备与用户空间程序的桥梁。
驱动程序创建的设备对象存在于内核命名空间中,路径通常类似 \Device\MyDriver
,用户态程序无法直接访问。为此,需要创建一个符号链接,将设备对象映射到用户可见的路径,如 \\.\MyDriver
或 \DosDevices\MyDriver
。
这样,用户态程序便可通过 CreateFile
等标准 API 打开设备,就像操作普通文件一样。符号链接本质上是一个指针,指向真实的设备对象,从而实现内核与用户之间的通信。
IRP(I/O Request Packet)
在 Windows 内核中,IRP(I/O Request Packet)是表示 I/O 操作的标准数据结构。
当用户程序发起 I/O 请求时,I/O 管理器会创建一个 IRP,其中包含操作类型(读、写、控制等)、缓冲区地址和相关参数。IRP 会沿驱动程序栈逐层传递,每个驱动都可以处理或转发,直到最底层驱动完成硬件操作。
IRP 采用异步处理模式,驱动可立即返回,稍后再完成操作并通知 I/O 管理器。这种机制使 Windows 能够高效处理并发 I/O 请求,是内核 I/O 架构的核心。
Hello World 驱动程序
用户层
#include <iostream>
#include <Windows.h>#define wszDeviceSymbolLinkName L"\\\\.\\Yan"#define IOCTL_DEFINE(FUNC_NUM, METHOD) CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800 + FUNC_NUM, METHOD, FILE_ANY_ACCESS)
#define IOCTL_FUNCTION_1 IOCTL_DEFINE(1, METHOD_IN_DIRECT)const int length = 0;
int main()
{HANDLE file = CreateFileW(wszDeviceSymbolLinkName, GENERIC_ALL, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if (file == INVALID_HANDLE_VALUE){printf("CreateFileW failed %08X\n", GetLastError());return 1;}BOOL result = FALSE;wchar_t msg[] = L"Hello World";DWORD retutnLength = 0;printf("msg ptr: %p\n", msg);printf("length ptr: %p\n", &length);getchar();result = DeviceIoControl(file, IOCTL_FUNCTION_1, msg, sizeof(msg), (PVOID)&length, sizeof(length), &retutnLength, NULL);if (result && retutnLength == sizeof(length)){printf("msg length: %d\n", length);}else{printf("DeviceIoControl IOCTL_FUNCTION_1 failed %08X retutnLength: %d\n", GetLastError(), retutnLength);}CloseHandle(file);system("pause");return 0;
}
驱动层
#include <ntifs.h>
#include <ntstrsafe.h>
#pragma warning(disable: 4133)#define TRACE(fmt, ...) DbgPrintEx(DPFLTR_SYSTEM_ID, DPFLTR_ERROR_LEVEL,fmt, __VA_ARGS__ )
#define wszDeviceName L"\\Device\\Yan"
#define wszDeviceSymbolLinkName L"\\DosDevices\\Yan"#define IOCTL_DEFINE(FUNC_NUM, METHOD) CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800 + FUNC_NUM, METHOD, FILE_ANY_ACCESS)
#define IOCTL_FUNCTION_1 IOCTL_DEFINE(1, METHOD_IN_DIRECT)NTSTATUS DeviceCreate(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{TRACE("DeviceCreate\n");Irp->IoStatus.Information = 0;Irp->IoStatus.Status = STATUS_SUCCESS;IoCompleteRequest(Irp, IO_NO_INCREMENT);return STATUS_SUCCESS;
}NTSTATUS DevicecClose(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{TRACE("DevicecClose\n");Irp->IoStatus.Information = 0;Irp->IoStatus.Status = STATUS_SUCCESS;IoCompleteRequest(Irp, IO_NO_INCREMENT);return STATUS_SUCCESS;
}NTSTATUS DevicecUnsupport(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{Irp->IoStatus.Information = 0;Irp->IoStatus.Status = STATUS_SUCCESS;IoCompleteRequest(Irp, IO_NO_INCREMENT);return STATUS_SUCCESS;
}NTSTATUS DevicecControl(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{TRACE("DevicecControl\n");ULONG ctl_code;ULONG ctl_method;ULONG inbuf_size = 0;ULONG outbuf_size = 0;PVOID inbuffer = NULL;PVOID outbuffer = NULL;NTSTATUS status = STATUS_SUCCESS;ULONG_PTR return_length = 0;PIO_STACK_LOCATION stack_location = IoGetCurrentIrpStackLocation(Irp);if (stack_location->MajorFunction != IRP_MJ_DEVICE_CONTROL){status = STATUS_INVALID_DEVICE_REQUEST;Irp->IoStatus.Status = status;IoCompleteRequest(Irp, IO_NO_INCREMENT);return status;}ctl_code = stack_location->Parameters.DeviceIoControl.IoControlCode;ctl_method = METHOD_FROM_CTL_CODE(ctl_code);if (ctl_method == METHOD_IN_DIRECT || ctl_method == METHOD_OUT_DIRECT){inbuffer = Irp->AssociatedIrp.SystemBuffer;inbuf_size = stack_location->Parameters.DeviceIoControl.InputBufferLength;outbuffer = Irp->MdlAddress ? MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority) : NULL;outbuf_size = stack_location->Parameters.DeviceIoControl.OutputBufferLength;}else{status = STATUS_INVALID_PARAMETER;Irp->IoStatus.Status = status;IoCompleteRequest(Irp, IO_NO_INCREMENT);return status;}//if (inbuffer == NULL || outbuffer == NULL)//{// status = STATUS_INSUFFICIENT_RESOURCES;// Irp->IoStatus.Status = status;// IoCompleteRequest(Irp, IO_NO_INCREMENT);// return status;//}switch (ctl_code){case IOCTL_FUNCTION_1:{DbgBreakPoint();UNICODE_STRING str;RtlInitUnicodeString(&str, (PWCH)inbuffer);TRACE("IOCTL_FUNCTION_1: %wZ\n", &str);*(int*)outbuffer = str.Length;return_length = sizeof(int);break;}default:{status = STATUS_NOT_IMPLEMENTED;return_length = 0;break;}}Irp->IoStatus.Status = status;Irp->IoStatus.Information = return_length;IoCompleteRequest(Irp, IO_NO_INCREMENT);return STATUS_SUCCESS;
}VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{UNICODE_STRING usDeivceSymbolLinkName = RTL_CONSTANT_STRING(wszDeviceSymbolLinkName);IoDeleteSymbolicLink(&usDeivceSymbolLinkName);PDEVICE_OBJECT device = DriverObject->DeviceObject;while (device){PDEVICE_OBJECT next = device->NextDevice;IoDeleteDevice(device);device = next;}TRACE("DriverUnload\n");
}NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{NTSTATUS status = STATUS_SUCCESS;UNICODE_STRING usDeivceName = RTL_CONSTANT_STRING(wszDeviceName);UNICODE_STRING usDeivceSymbolLinkName = RTL_CONSTANT_STRING(wszDeviceSymbolLinkName);PDEVICE_OBJECT DeivceObject = NULL;pDriverObject->DriverUnload = DriverUnload;for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; ++i){pDriverObject->MajorFunction[i] = DevicecUnsupport;}pDriverObject->MajorFunction[IRP_MJ_CREATE] = DeviceCreate;pDriverObject->MajorFunction[IRP_MJ_CLOSE] = DevicecClose;pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DevicecControl;// 创建设备status = IoCreateDevice(pDriverObject,0,&usDeivceName,FILE_DEVICE_UNKNOWN,FILE_DEVICE_SECURE_OPEN,FALSE,&DeivceObject);if (!NT_SUCCESS(status)){TRACE("IoCreateDevice Failed status: %08X\n", status);goto LAB_END;}// 可以不清除该标志//DeivceObject->Flags |= DO_DEVICE_INITIALIZING;DeivceObject->Flags |= DO_DIRECT_IO;status = IoCreateSymbolicLink(&usDeivceSymbolLinkName, &usDeivceName);if (!NT_SUCCESS(status)){TRACE("IoCreateSymbolicLink Failed status: %08X\n", status);goto LAB_END;}LAB_END:if (!NT_SUCCESS(status)){DriverUnload(pDriverObject);}return status;
}
验证
-
使用第三方驱动加载工具加载驱动
-
执行用户程序, 输出如下:
msg ptr: 000000C17358FAA8 length ptr: 00007FF62951BF20msg length: 0 请按任意键继续. . .
-
驱动程序输出,如下:
Mon Sep 29 10:19:38.800 2025 (UTC + 8:00): DeviceCreate Mon Sep 29 10:19:39.618 2025 (UTC + 8:00): DevicecControl Mon Sep 29 10:19:51.615 2025 (UTC + 8:00): IOCTL_FUNCTION_1: Hello World Mon Sep 29 10:19:51.623 2025 (UTC + 8:00): DevicecClose
安全防护
VirboxProtector 在保护驱动程序时采用多层次防护策略。驱动程序运行在内核态,一旦被逆向分析成功,攻击者就能了解其安全机制的实现细节,进而绕过杀毒软件监控、突破反外挂检测、或破解授权验证。因此,必须对驱动程序的核心逻辑实施深度保护。
代码虚拟化将驱动中的关键函数转换为自定义虚拟机字节码,原始的x86/x64指令被替换成只有特定虚拟机才能解释的指令序列。逆向分析时看到的不是可读的汇编代码,而是陌生的指令体系。攻击者无法直接定位IRP处理函数、回调例程等关键代码,必须先完整分析虚拟机架构,工作量远超传统驱动逆向。
代码混淆通过控制流平坦化、虚假分支、指令替换等技术打乱驱动结构。简单的权限检查被展开成复杂跳转网络,清晰的函数调用变成间接跳转,原本几行的逻辑膨胀成上百行等效代码。即使能够阅读反汇编,也难以理解驱动的真实工作流程。