驱动开发基础回顾:WDF 与 NT 驱动模型、IRP 与派遣函数、驱动入口与卸载例程
在深入驱动安全测试之前,必须牢固掌握 Windows 驱动开发的两大核心模型——NT 式驱动与 WDF(Windows Driver Frameworks)。本章将系统梳理驱动的基本骨架:入口/卸载例程、IRP 请求分发机制以及派遣函数的注册与实现。这些概念是后续静态分析与动态测试的基石。
2.1 NT 驱动模型 vs. WDF 驱动模型
Windows 驱动开发经历了从 NT 式驱动(传统 WDM)到 WDF(KMDF/UMDF)的演进。NT 模型直接暴露 I/O 管理器接口,开发者需手动处理大量底层细节;WDF 则封装了通用逻辑,提供更安全、更简洁的编程框架。
| 对比维度 | NT 式驱动(传统) | WDF(KMDF/UMDF) |
|---|---|---|
| 框架层次 | 直接与 I/O 管理器交互 | 通过框架对象封装 I/O 管理器 |
| 即插即用/电源管理 | 手动处理 IRP_MJ_PNP / IRP_MJ_POWER | 框架自动处理,仅需注册回调 |
| 同步与并发 | 开发者自旋锁/互斥体 | 框架提供自动锁定与队列管理 |
| 错误处理 | 手动检查每个 IRP 状态 | 框架提供默认完成例程与错误路径 |
| 代码量 | 较大,模板代码多 | 减少 30%~50% 样板代码 |
| 安全审计重点 | IRP 派遣函数、缓冲区处理 | EvtIo* 回调、请求对象生命周期 |
EvtIoRead/EvtIoWrite 回调中的请求验证。
2.2 IRP 与派遣函数机制
IRP(I/O Request Packet) 是 Windows I/O 子系统的核心数据结构,用于在驱动层之间传递 I/O 请求。每个 IRP 包含主功能码(MajorFunction)和次功能码(MinorFunction),驱动通过注册 派遣函数(Dispatch Function) 来处理不同类型的请求。
IRP_MJ_CREATE—— 打开设备(CreateFile)IRP_MJ_CLOSE—— 关闭设备句柄IRP_MJ_READ/IRP_MJ_WRITE—— 读写操作IRP_MJ_DEVICE_CONTROL—— 设备控制(DeviceIoControl)IRP_MJ_PNP—— 即插即用事件IRP_MJ_POWER—— 电源管理IRP_MJ_INTERNAL_DEVICE_CONTROL—— 内部设备控制
NT 式驱动通过 DriverObject->MajorFunction[] 数组注册派遣函数;WDF 则通过 WdfDeviceIoQueueCreate 和 EvtIo* 回调注册。
// NT 式驱动:注册派遣函数示例
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreate;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchClose;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchIoControl;
DriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead;
DriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchWrite;
DriverObject->DriverUnload = DriverUnload;
// ... 创建设备对象、符号链接等
return STATUS_SUCCESS;
}
// 派遣函数通用签名
NTSTATUS DispatchCreate(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
// 处理打开请求
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
// WDF (KMDF) 驱动:注册 EvtIo* 回调示例
EVT_WDF_DRIVER_DEVICE_ADD KmdfEvtDeviceAdd;
EVT_WDF_IO_QUEUE_IO_READ KmdfEvtIoRead;
EVT_WDF_IO_QUEUE_IO_WRITE KmdfEvtIoWrite;
EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL KmdfEvtIoDeviceControl;
NTSTATUS KmdfEvtDeviceAdd(WDFDRIVER Driver, PWDFDEVICE_INIT DeviceInit)
{
WDFDEVICE device;
WDF_IO_QUEUE_CONFIG queueConfig;
// ... 初始化设备
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig, WdfIoQueueDispatchSequential);
queueConfig.EvtIoRead = KmdfEvtIoRead;
queueConfig.EvtIoWrite = KmdfEvtIoWrite;
queueConfig.EvtIoDeviceControl = KmdfEvtIoDeviceControl;
WdfIoQueueCreate(device, &queueConfig, WDF_NO_OBJECT_ATTRIBUTES, WDF_NO_HANDLE);
return STATUS_SUCCESS;
}
IRP_MJ_DEVICE_CONTROL 派遣函数中,如果未正确验证 IoStackLocation->Parameters.DeviceIoControl.InputBufferLength 和 OutputBufferLength,极易导致内核内存泄漏或越界写入。WDF 中同样需要检查 WdfRequestRetrieveInputBuffer 返回的缓冲区大小。
2.3 驱动入口与卸载例程
每个 Windows 驱动必须包含 DriverEntry 入口函数和 DriverUnload 卸载例程(NT 式)或 EvtDriverUnload(WDF)。入口函数负责初始化驱动对象、注册派遣函数、创建设备对象;卸载例程负责清理资源、删除设备对象和符号链接。
// NT 式驱动:DriverEntry 与 DriverUnload 完整骨架
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
NTSTATUS status;
PDEVICE_OBJECT deviceObject = NULL;
UNICODE_STRING deviceName, symLinkName;
// 1. 注册派遣函数
DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreate;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchClose;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchIoControl;
DriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead;
DriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchWrite;
DriverObject->DriverUnload = DriverUnload;
// 2. 创建设备对象
RtlInitUnicodeString(&deviceName, L"\\Device\\MyDriverDevice");
status = IoCreateDevice(DriverObject, 0, &deviceName,
FILE_DEVICE_UNKNOWN, 0, FALSE, &deviceObject);
if (!NT_SUCCESS(status)) return status;
// 3. 创建符号链接
RtlInitUnicodeString(&symLinkName, L"\\DosDevices\\MyDriver");
status = IoCreateSymbolicLink(&symLinkName, &deviceName);
if (!NT_SUCCESS(status)) {
IoDeleteDevice(deviceObject);
return status;
}
return STATUS_SUCCESS;
}
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
UNICODE_STRING symLinkName;
RtlInitUnicodeString(&symLinkName, L"\\DosDevices\\MyDriver");
IoDeleteSymbolicLink(&symLinkName);
IoDeleteDevice(DriverObject->DeviceObject);
}
// WDF (KMDF) 驱动:DriverEntry 与 EvtDriverUnload 骨架
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
WDF_DRIVER_CONFIG config;
WDF_DRIVER_CONFIG_INIT(&config, KmdfEvtDeviceAdd);
config.EvtDriverUnload = KmdfEvtDriverUnload; // 注册卸载回调
return WdfDriverCreate(DriverObject, RegistryPath,
WDF_NO_OBJECT_ATTRIBUTES, &config, WDF_NO_HANDLE);
}
VOID KmdfEvtDriverUnload(WDFDRIVER Driver)
{
// 框架自动清理设备对象和队列,通常只需记录日志或释放全局资源
DbgPrint("MyKMDFDriver: Unloading...\n");
}
IoCancelIrp 或设置取消安全标志。
2.4 驱动对象与设备对象关系图
下图展示了 NT 式驱动中 驱动对象(DRIVER_OBJECT)、设备对象(DEVICE_OBJECT) 与 IRP 派遣表 之间的核心关系。理解此结构是进行驱动逆向与安全分析的前提。
2.5 派遣函数注册与 IRP 处理流程
当用户态调用 CreateFile、ReadFile、DeviceIoControl 等 API 时,I/O 管理器构造 IRP 并调用驱动注册的派遣函数。下图展示了从用户态请求到派遣函数执行的完整路径。
2.6 驱动开发安全基线清单
在进入静态分析之前,请确保掌握以下安全基线。这些条目将作为后续测试用例设计的依据。
| 检查项 | NT 式驱动 | WDF 驱动 | 安全风险等级 |
|---|---|---|---|
| 入口函数返回值 | 必须返回 STATUS_SUCCESS 或错误码 | WdfDriverCreate 返回 NTSTATUS | 高 |
| 派遣函数覆盖 | 至少注册 CREATE/CLOSE/DEVICE_CONTROL | EvtIoDeviceControl 必须实现 | 高 |
| 缓冲区验证 | 检查 InputBufferLength / OutputBufferLength | 检查 WdfRequestRetrieveInputBuffer 大小 | 高 |
| IRP 完成 | 每个 IRP 必须且仅完成一次 | 框架自动完成,但 EvtIo* 中不可重复完成 | 高 |
| 卸载清理 | 删除设备对象、符号链接、取消挂起 IRP | 框架自动清理,但需释放全局资源 | 中 |
| 同步保护 | 自旋锁/互斥体保护共享数据 | 框架队列自动序列化 | 中 |
| 符号链接权限 | 检查设备对象 ACL | WdfDeviceInitAssignSDDLString | 中 |
MajorFunction 数组的初始化位置、IoCreateDevice 调用点以及 DriverUnload 中的资源释放逻辑。WDF 驱动则需关注 WdfDriverCreate 参数和 EvtDriverUnload 回调地址。