整理了一下当初学习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 运行,是操作系统与硬件之间的桥梁。
保护模式提供的关键能力
1. 权限管理 — 防止应用程序胡作非为
2. 内存隔离 — 一个进程崩溃不影响其他进程
3. 任务管理(Task)
4. 系统调用
5. 中断管理
如何进入ring0?
1. 系统级别漏洞(不推荐)
2. 操作系统为驱动厂商提供的接口 — 即驱动开发框架
💡 操作系统提供驱动接口,厂家编写 .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环驱动 |
第一个驱动程序
1. 准备 sources 文件(指定目标文件名、类型、源文件)
2. 切换路径到编译目录
3. build 编译
• free → release 版
• checked → debug 版
驱动安装与启动
SC 方式:
CreateService→安装服务
StartService→启动驱动
关于签名:
• XP 系统无签名要求,可关掉签名验证
• Win11 已无32位版本,仅支持64位驱动
三、完整的驱动程序 —— 创建设备、注册派遣函数、完成请求
现在进入最核心的部分:手写一个完整的驱动程序。
驱动程序的入口:DriverEntry
NTSTATUS DriverEntry(
__instruct _DRIVER_OBJECT* DriverObject,
__inPUNICODE_STRING RegistryPath
)
四个核心任务:
1. 创建设备(提供设备名称、扩展数据)
2. 创建符号链接(与设备名称绑定,供3环应用访问)
3. 注册派遣函数(处理 IRP 请求)
4. 注册卸载函数(驱动停止时清理资源)
驱动对象与设备对象
┌─────────────────┐┌─────────────────┐
│DRIVER_OBJECT│ ──────→ │DEVICE_OBJECT│
│(驱动对象)│绑定│(设备对象)│
│含派遣函数数组││含扩展指针│
│含设备对象链表│││
└─────────────────┘└─────────────────┘
• DRIVER_OBJECT:系统创建的描述驱动信息的对象,绑定了派遣函数指针数组
• DEVICE_OBJECT:给3环操作的对象(一个驱动可对应多个设备)
创建设备:IoCreateDevice
NTSTATUS IoCreateDevice(
PDRIVER_OBJECT,//驱动对象
ULONGDeviceExtensionSize, // 扩展数据大小
PUNICODE_STRINGDeviceName,// 设备名称
DEVICE_TYPEDeviceType,// 驱动类型
ULONGDeviceCharacteristics, // 设备特性
BOOLEANExclusive,// 是否独占
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);
returnstatus;
}
0环设备名格式:DeviceXXX
3环通过符号链接访问:??XXX 或 DosDevicesXXX
注册派遣函数
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);
returnSTATUS_SUCCESS;
}
读写实现要点
NTSTATUS MyWrite(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
PIO_STACK_LOCATIONstack = IoGetCurrentIrpStackLocation(Irp);
ULONGlength = stack->Parameters.Write.Length;
PVOIDbuffer = Irp->AssociatedIrp.SystemBuffer;
//⚠️ 安全检查:缓冲区大小由用户指定,需要验证
if(length > MAX_BUFFER_SIZE) {
Irp->IoStatus.Status= STATUS_INVALID_PARAMETER;
Irp->IoStatus.Information= 0;
IoCompleteRequest(Irp,IO_NO_INCREMENT);
returnSTATUS_INVALID_PARAMETER;
}
Irp->IoStatus.Status= STATUS_SUCCESS;
Irp->IoStatus.Information= length;
IoCompleteRequest(Irp,IO_NO_INCREMENT);
returnSTATUS_SUCCESS;
}
卸载函数
VOID Unload(PDRIVER_OBJECT DriverObject)
{
//1. 删除符号链接
UNICODE_STRINGsymLink;
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)
💡 非分页内存越用越少,申请完后系统无法回收——非分页内存耗尽可能导致系统崩溃!
结语:写给驱动开发新手的几点建议
1. 虚拟机是第一生产力
驱动的bug几乎都意味着蓝屏,反复实机测试=反复重启
2. 安全是第一优先级
内核代码的缓冲区溢出不只是崩溃,还可能被恶意利用
3. 从 WDM/KMDF 开始
不要一上来就搞 WDF,先把基础框架搞清楚
4. 用好 DbgPrint 和 WinDbg
内核调试和用户态调试完全不一样,早点掌握双机调试
5. 阅读操作系统行为
用 WinObj 查看设备对象,理解系统的内部视图
驱动开发是一扇门,推开它,你就真正进入了操作系统的底层世界。希望这篇文章能帮你迈出第一步。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📌 如果你觉得这篇文章有帮助,欢迎点赞、在看、转发!
📌 关注本公众号,持续输出内核驱动、Windows底层技术干货。
#内核开发 #驱动开发 #C语言 #系统编程
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……




还没有评论,来说两句吧...