Windows内核驱动完整实现
Windows内核驱动完整实现:创建设备、注册派遣函数与完成IRP请求
零基础学逆向 | 2026-07-08 17:58 | 四川

整理了一下当初学习Windows内核驱动开发的笔记
理解CPU权限级别和完整的驱动程序实现
一、CPU权限级别:为什么驱动是"内核级"的?
在理解Windows驱动之前,必须先搞懂CPU的运行模式。
CPU的四种运行模式
- 实模式(Real Mode):CPU通电后进入的模式,兼容16位操作系统和应用。人人平等,没有权限之分。
- 保护模式(Protected Mode):32位操作系统运行的模式,引入了权限等级的概念。
- 虚拟8086模式(Virtual-8086 Mode):在保护模式下模拟实模式环境,以运行16位程序。
保护模式到底保护什么?
保护模式的核心就是保护硬件。它把权限划分为4个级别(ring0~ring3):
| 级别 | 用途 | 特点 |
|---|---|---|
| ring0 | 操作系统内核代码 | 最高权限,可直接访问硬件 |
| ring3 | 应用程序代码 | 最低权限,不能直接操作硬件 |

应用程序访问硬件的路径:
应用程序 → 系统调用(API) → 操作系统 → 硬件
驱动程序就在 ring0 运行,是操作系统与硬件之间的桥梁。
保护模式提供的关键能力
- 权限管理 — 防止应用程序胡作非为
- 内存隔离 — 一个进程崩溃不影响其他进程
- 任务管理(Task)
- 系统调用
- 中断管理
如何进入ring0?
- 系统级别漏洞(不推荐)
- 操作系统为驱动厂商提供的接口 — 即驱动开发框架
💡 操作系统提供驱动接口,厂家编写
.sys文件,操作系统加载它(映射到高2G虚拟地址空间),然后调用它。
驱动的分类
| 类型 | 说明 |
|---|---|
| 内核驱动 | 不操作硬件的驱动(如虚拟光驱) |
| 硬件驱动 | 直接操作硬件的驱动(键盘、鼠标、显卡) |

⚠️ 驱动代码与操作系统同等级,崩溃可能导致系统蓝屏。务必使用虚拟机测试!
二、驱动开发环境搭建
WDK 的选择
WDK(Windows Driver Kit)是驱动开发的必备工具。
- WDK新版:与 Visual Studio 绑定
- WDK老版:提供离线文档
- EWDK:独立开发包,不绑定VS
下载地址:https://docs.microsoft.com/zh-cn/windows-hardware/drivers/other-wdk-downloads
安装顺序(高版本):
安装 VS → 安装 SDK → 安装 WDK → 自动安装 VS 插件
⚠️ 安装过程不能中断,否则只能重装系统才能再安装插件!
驱动框架体系
| 框架 | 说明 |
|---|---|
| WDM | Windows Driver Model,即插即用,硬件驱动标准 |
| KMDF | Kernel-Mode Driver Framework,内核模式驱动(NT驱动) |
| WDF | Windows Driver Framework,C++ 驱动框架 |
| UMDF | User-Mode Driver Framework,3环驱动 |

第一个驱动程序
- 准备
sources文件(指定目标文件名、类型、源文件) - 切换路径到编译目录
build编译free→ release 版checked→ debug 版
驱动安装与启动
SC 方式:
CreateService → 安装服务
StartService → 启动驱动
关于签名:
- XP 系统无签名要求,可关掉签名验证
- Win11 已无32位版本,仅支持64位驱动
三、完整的驱动程序 —— 创建设备、注册派遣函数、完成请求
现在进入最核心的部分:手写一个完整的驱动程序。
驱动程序的入口:DriverEntry
NTSTATUS DriverEntry(
__in struct _DRIVER_OBJECT* DriverObject,
__in PUNICODE_STRING RegistryPath
)
四个核心任务
- 创建设备(提供设备名称、扩展数据)
- 创建符号链接(与设备名称绑定,供3环应用访问)
- 注册派遣函数(处理 IRP 请求)
- 注册卸载函数(驱动停止时清理资源)
驱动对象与设备对象
┌─────────────────┐ ┌─────────────────┐
│ DRIVER_OBJECT │ ──────→ │ DEVICE_OBJECT │
│ (驱动对象) │ 绑定 │ (设备对象) │
│ 含派遣函数数组 │ │ 含扩展指针 │
│ 含设备对象链表 │ │ │
└─────────────────┘ └─────────────────┘
- DRIVER_OBJECT:系统创建的描述驱动信息的对象,绑定了派遣函数指针数组
- DEVICE_OBJECT:给3环操作的对象(一个驱动可对应多个设备)
创建设备:IoCreateDevice
NTSTATUS IoCreateDevice(
PDRIVER_OBJECT, // 驱动对象
ULONG DeviceExtensionSize, // 扩展数据大小
PUNICODE_STRING DeviceName, // 设备名称
DEVICE_TYPE DeviceType, // 驱动类型
ULONG DeviceCharacteristics, // 设备特性
BOOLEAN Exclusive, // 是否独占
PDEVICE_OBJECT* DeviceObject // 返回的设备对象指针
);

设备扩展(DeviceExtension)
- 存放设备的额外数据
- 操作时向该指针指向的区域读写数据
- 推荐使用扩展指针而不是全局变量(多个设备共用时才用全局变量)
创建设备名称与符号链接
// 创建设备名称
UNICODE_STRING devName;
RtlInitUnicodeString(&devName, L"\\Device\\MyDriverDevice");
// 创建符号链接
UNICODE_STRING symLink;
RtlInitUnicodeString(&symLink, L"\\??\\MyDriver");
NTSTATUS status = IoCreateSymbolicLink(&symLink, &devName);
if (!NT_SUCCESS(status)) {
IoDeleteDevice(DeviceObject);
return status;
}
0环设备名格式:
\Device\XXX
3环通过符号链接访问:\??\XXX或\DosDevices\XXX
注册派遣函数
DriverObject->MajorFunction[IRP_MJ_CREATE] = MyCreate;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = MyClose;
DriverObject->MajorFunction[IRP_MJ_READ] = MyRead;
DriverObject->MajorFunction[IRP_MJ_WRITE] = MyWrite;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyDeviceControl;
NTSTATUS MyFunction(PDEVICE_OBJECT DeviceObject, PIRP Irp)
完成请求:IoCompleteRequest
NTSTATUS MyCreate(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
DbgPrint("[MyDriver] %s", __FUNCTION__);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
读写实现要点
NTSTATUS MyWrite(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
ULONG length = stack->Parameters.Write.Length;
PVOID buffer = Irp->AssociatedIrp.SystemBuffer;
// ⚠️ 安全检查:缓冲区大小由用户指定,需要验证
if (length > MAX_BUFFER_SIZE) {
Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_INVALID_PARAMETER;
}
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = length;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
卸载函数
VOID Unload(PDRIVER_OBJECT DriverObject)
{
// 1. 删除符号链接
UNICODE_STRING symLink;
RtlInitUnicodeString(&symLink, L"\\??\\MyDriver");
IoDeleteSymbolicLink(&symLink);
// 2. 删除设备
if (DriverObject->DeviceObject) {
IoDeleteDevice(DriverObject->DeviceObject);
}
DbgPrint("[MyDriver] Driver unloaded successfully");
}
四、控制函数与缓冲区通讯方式
控制码(IOCTL)
#define IOCTL_MY_READ CTL_CODE(
FILE_DEVICE_UNKNOWN, 0x800,
METHOD_BUFFERED, FILE_ANY_ACCESS
)
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
ULONG ctlCode = stack->Parameters.DeviceIoControl.IoControlCode;
ULONG inLen = stack->Parameters.DeviceIoControl.InputBufferLength;
ULONG outLen = stack->Parameters.DeviceIoControl.OutputBufferLength;
三种缓冲区通讯方式
| 通讯方式 | 机制 | 安全 | 性能 | 推荐场景 |
|---|---|---|---|---|
| 缓冲区方式 | 系统申请内核缓冲区,自动拷贝 | ✅ 最安全 | ❌ 较慢 | 小数据量、安全优先 |
| 直接方式 | 锁定用户内存,内核重新映射 | ✅ 安全 | ✅ 较快 | 大数据量传输 |
| 其它方式 | 直接访问用户地址 | ❌ 不安全 | ✅ 最快 | ⚠️ 不推荐 |

1️⃣ 缓冲区方式(推荐)
PVOID buffer = Irp->AssociatedIrp.SystemBuffer;
2️⃣ 直接方式
PMDL mdl = Irp->MdlAddress;
PVOID buffer = MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority);
if (buffer == NULL) {
// 处理错误...
}
3️⃣ 其它方式(NEITHER)
⚠️ 三环地址不可信任!用户程序可能在驱动处理期间改变地址内容,导致安全漏洞。
五、内核内存管理——分页与非分页内存
驱动使用的内存类型
| 内存类型 | 特点 | 用途 |
|---|---|---|
| 非分页内存池 | 常驻内存,不会被换出 | 重要的代码、DPC例程、中断处理 |
| 分页内存池 | 可用时在内存,需要时换出 | 一般的驱动代码 |
| 堆空间(ExAllocatePool) | 动态分配 | 运行时需要的内存 |
申请与释放
// 申请非分页内存
PVOID ptr = ExAllocatePool(NonPagedPool, size);
// 申请分页内存
PVOID ptr = ExAllocatePool(PagedPool, size);
// 释放
ExFreePool(ptr);
注意事项
⚠️ 重要的代码使用非分页内存,不重要的使用分页内存
- 3环程序没有资格申请非分页内存,只有分页内存
- 0环函数默认在非分页内存,可以用
#pragma alloc_text()指定在不同节 - DriverEntry 函数执行完后内存会被释放(属于 INIT 节),所以 Unload 函数不能放在 INIT 节
// 将 DriverEntry 放到 INIT 节,执行完释放
#pragma alloc_text(INIT, DriverEntry)
💡 非分页内存越用越少,申请完后系统无法回收——非分页内存耗尽可能导致系统崩溃!
结语:写给驱动开发新手的几点建议
-
虚拟机是第一生产力
驱动的bug几乎都意味着蓝屏,反复实机测试 = 反复重启 -
安全是第一优先级
内核代码的缓冲区溢出不只是崩溃,还可能被恶意利用 -
从 WDM/KMDF 开始
不要一上来就搞 WDF,先把基础框架搞清楚 -
用好 DbgPrint 和 WinDbg
内核调试和用户态调试完全不一样,早点掌握双机调试 -
阅读操作系统行为
用 WinObj 查看设备对象,理解系统的内部视图
驱动开发是一扇门,推开它,你就真正进入了操作系统的底层世界。
希望这篇文章能帮你迈出第一步。
📌 如果你觉得这篇文章有帮助,欢迎点赞、在看、转发!
📌 关注本公众号,持续输出内核驱动、Windows底层技术干货。
#Windows驱动 #内核开发 #驱动开发 #C语言 #系统编程
2. 安全是第一优先级
内核代码的缓冲区溢出不只是崩溃,还可能被恶意利用
-
从 WDM/KMDF 开始
不要一上来就搞 WDF,先把基础框架搞清楚 -
用好 DbgPrint 和 WinDbg
内核调试和用户态调试完全不一样,早点掌握双机调试 -
阅读操作系统行为
用 WinObj 查看设备对象,理解系统的内部视图
驱动开发是一扇门,推开它,你就真正进入了操作系统的底层世界。
希望这篇文章能帮你迈出第一步。
📌 如果你觉得这篇文章有帮助,欢迎点赞、在看、转发!
📌 关注本公众号,持续输出内核驱动、Windows底层技术干货。
#Windows驱动 #内核开发 #驱动开发 #C语言 #系统编程
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)