第11章 其他主题
收尾工具箱:驱动签名、Driver Verifier、Native API、传统过滤驱动与设备通知,以及三条技术路线的支持边界。
学习目标
- 能解释驱动签名的测试/发布两条路径,以及 Driver Verifier 的使用边界(测试机 + 目标驱动)
- 能改出“设备到达通知”的注册与回调,并完成用后注销
- 能回答:为什么 SSDT 挂钩在现代 Windows 上不可行?新代码应该选哪条路线?
为什么大楼交付前要体检、发证、装监控
想象大楼里新开一家公司(你的驱动要进驻系统)。搬进来之前有三道手续:体检(查身体有没有暗病——内存乱用、锁没锁好)、发通行证(没有有效证件,64 位大门直接拒入)、装监控(新设备接进来要有人知道)。大楼里还有几条老路和一条禁区:老路(旧式过滤驱动)还在但没人维护,禁区(改大楼结构图)一碰就触发警报。
没有这些手续会怎样?带病上工的程序一次越界就把整栋楼搞崩;没证的程序根本进不了门;U 盘插进来没人响应,外设全成摆设。
这一章是收尾工具箱:体检(Driver Verifier)、发证(驱动签名)、近路(Native API)、旧地图(传统过滤驱动)、监控(设备通知),最后标出禁区(SSDT 挂钩)——知道什么不能做,和知道怎么做一样重要。
驱动签名:进 64 位大门的通行证
↡对驱动文件进行数字签名,64 位 Windows 强制要求,未签名驱动拒绝加载 是驱动的“身份证”。开发调试阶段用测试签名:bcdedit /set testsigning on 打开测试模式 + 自签证书签名——但这会永久降低这台机器的安全边界,只许用在可丢弃的测试机。发布阶段用正式签名:EV 证书签名后提交微软门户,拿回 attestation 签名(微软背书),这是兼容 HVCI(内存完整性)的唯一途径。
一个常见混淆:测试签名的驱动换到生产机直接拒绝加载,报“无法验证此驱动软件的发布者”——因为生产机不认你的测试证书。
Driver Verifier:内核自带的体检仪
↡Windows 内置的驱动验证工具,对目标驱动开启内存池、IRQL、锁、引用计数等规则检查,违规当场 bug check 把驱动放进“放大镜”下跑:内存越界、错误 IRQL、锁顺序问题、引用泄漏,任何一条规则被违反就当场蓝屏,并借崩溃转储定位到责任代码——与其在用户现场随机蓝屏,不如在测试机上有预谋地蓝屏。
铁律两条:只在测试机开(全局开启性能下降 3-5 倍甚至无法启动);只对目标驱动开(verifier /driver MyDriver.sys)。违规转储用调试器的 !analyze -v 就能找到出错栈。
Native API:绕开 Win32 的近路
↡内核导出的底层系统服务接口(Nt*/Zw* 函数),Win32 API 内部就是调用它们实现的 是 Win32 之下的“原始通道”:CreateFile 最终调用 NtCreateFile,ReadFile 最终调用 NtReadFile。用户模式程序可以直接调用 ntdll.dll 导出的这些函数,拿到 Win32 层不暴露的参数(如完整对象属性、标志位)。
风险在于:Native API 是未承诺稳定的内部接口,参数语义随版本变动,且签名行为与 Win32 不同(如不自动初始化 OBJECT_ATTRIBUTES)。能用 Win32 就用 Win32,Native API 只在确实需要底层控制时用。
传统过滤驱动:还在但没人维护的老路
↡Minifilter 出现之前的老式文件系统过滤驱动,直接接管 IRP 分发例程,兼容性差、开发成本高 是第 10 章 Minifilter 的前身:不依赖过滤器管理器,自己写整套 MajorFunction 分发例程,自己管理设备栈附加、IRP 传递、名字缓冲……所有基础设施都要手搓,与新版文件系统兼容性差。
现在写新代码没有理由再走这条路:Minifilter 把基础设施全包了(第 10 章),传统过滤驱动只作为“读老代码”的迁移知识保留。判断标准很简单——新工程选受支持框架,老代码才谈迁移。
设备通知:大楼的入住监控
↡驱动通过 IoRegisterPlugPlayNotification 注册回调,在设备到达、移除或接口变化时得到内核通知 让驱动对热插拔有反应:U 盘插入 → 设备到达事件 → 你的回调被调用 → 打开设备接口、完成初始化。没有它,即插即用设备对驱动来说“来了也不知道”。
注册时指定事件类别(设备接口变化)、设备接口 GUID(只关心某类设备)和回调函数;返回的通知句柄必须在卸载时 IoUnregisterPlugPlayNotification 注销——和前面所有回调一样,注销是纪律的一部分。
动手:走一遍交付流水线
下面的可视化把整章工具画成一张图:上部是交付流水线五个阶段(开发 → Verifier → 测试签名 → 发布签名 → 部署通知),中部是 Verifier 工作台三步骤,下部是三条技术路线。点阶段框看说明;打开“全局 Verifier”开关模拟误操作;点路线三列看支持边界。
猜一猜:把“全局 Verifier”开关打开——结果条会变成什么颜色?提示文字说明了什么恢复手段?为什么“只查目标驱动”才是正确姿势?
Driver Verifier 是内核自带的"体检仪":对目标驱动开启内存池、IRQL、锁、引用计数等规则检查,任何违规当场 bug check(蓝屏)并定位到出错驱动。只能在测试机上、只对目标驱动开——全局开启会让系统慢 3-5 倍甚至无法启动。违规时的崩溃转储配合调试器 !analyze 就能找到责任代码。
代码对照:五件工具的用法
这一节按原书结构拆解五段:设备通知注册、Verifier 验证流程、测试/发布签名、Native API 调用、新旧两代过滤框架对比。五段覆盖本章全部工具的关键用法,完整工程见章末来源链接。
段一:设备通知 —— 注册与回调
设备通知三步:填订阅参数(回调 + 上下文)→ IoRegisterPlugPlayNotification 注册(声明关心“设备接口到达”事件、按 GUID 过滤设备类别)→ 卸载时用返回的句柄注销。回调里区分事件类型(到达/移除),到达时打开接口初始化。
// 注册设备通知:只关心 USB 设备接口到达
NTSTATUS RegisterDeviceNotification(PDRIVER_OBJECT Drv)
{
DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS params = { 0 };
params.Callback = OnDeviceNotify; // 通知回调
params.Context = nullptr;
return IoRegisterPlugPlayNotification(
EventCategoryDeviceInterfaceChange,
PNPNOTIFY_DEVICE_INTERFACE_ARRIVAL,
(PUNICODE_STRING)&GUID_DEVINTERFACE_USB_DEVICE,
Drv->DriverObject,
¶ms, &g_notifyHandle); // 注销凭证
}
VOID OnDeviceNotify(PVOID Context,
PDEVICE_NOTIFY_CALLBACK_CONTEXT Ctx,
PDEVICE_NOTIFY_PARAMETERS Params)
{
UNREFERENCED_PARAMETER(Context);
UNREFERENCED_PARAMETER(Ctx);
if (Params->Event == DeviceInterfaceArrival) {
// 新设备到了:打开接口、完成初始化
DbgPrint("Device arrived: %wZ\n",
&Params->DeviceInterface);
}
}RegisterDeviceNotification
│
├─→ 填订阅参数:回调 + 上下文
├─→ IoRegisterPlugPlayNotification(
│ 事件类别 = 设备接口变化
│ 子事件 = 接口到达
│ GUID = 只关心 USB 设备
│ → &g_notifyHandle(注销凭证)
│
运行:U 盘插入
│ → OnDeviceNotify:Event == 到达?→ 打开接口初始化
│
卸载:IoUnregisterPlugPlayNotification(g_notifyHandle)段二:Verifier —— 只查目标,违规即蓝
Verifier 是命令行工具,没有内核 API。标准流程:测试机管理员对目标驱动开启(/flags 指定规则组合,0x1 是内存池检查)→ 跑测试场景 → 违规当场蓝屏 → 用转储定位责任代码 → verifier /reset 结束(重启生效)。
:: 测试机上,只验证目标驱动(管理员)
verifier /flags 0x1 /driver MyDriver.sys
:: 跑压力场景;违规时系统立即蓝屏
:: 重启后收集崩溃转储:!analyze -v
:: 转储中责任模块就是违规驱动
:: 结束验证(重启后生效)
verifier /reset
:: 误开全局后无法启动:安全模式进入后
verifier /bootmode 1 (或 /reset 恢复)Driver Verifier 工作流程
│
├─→ 选择目标:/driver MyDriver.sys(绝不 /all)
├─→ 选择规则:池检查 / IRQL / 锁 / 引用计数
├─→ 跑测试:违规 → 立即 bug check
│ 转储分析:!analyze -v 定位责任驱动
└─→ 结束:/reset(重启生效)
误开全局 → 无法启动 → 安全模式 /reset段三:签名 —— 测试自签,发布走门户
签名流程分两轨:测试轨(测试机开 testsigning + signtool 自签证书)与发布轨(EV 证书 SHA-256 签名 + 时间戳 + 提交微软门户拿 attestation 签名)。发布轨的 attestation 签名是 HVCI 兼容的前提,缺它驱动在开启内存完整性的机器上被拒。
:: 测试签名(仅测试机)
bcdedit /set testsigning on
signtool sign /f MyTestCert.pfx /p pass MyDriver.sys
:: 发布签名(生产):EV 证书 + SHA-256 + 时间戳
signtool sign /fd sha256 /a /ph
/tr http://timestamp.digicert.com MyDriver.sys
:: 提交微软硬件开发者门户 → attestation 签名
:: (HVCI/内存完整性兼容的唯一途径)测试轨: 发布轨:
bcdedit testsigning EV 证书
+ 自签证书 + signtool SHA-256
↓ + 时间戳
本机可加载 ↓
(生产机拒绝) 提交微软门户
→ attestation 签名
→ 生产机 + HVCI 均可加载段四:Native API —— 用户模式直接走底层
Win32 的 CreateFile 内部就是调 NtCreateFile。直接调用原生 API 能拿到 Win32 不暴露的参数(对象属性、标志位),但必须自己初始化 OBJECT_ATTRIBUTES、自己处理 IO_STATUS_BLOCK——接口未承诺稳定,能不用就不用。
// 用户模式直接调用原生 API(ntdll.dll)
UNICODE_STRING name;
RtlInitUnicodeString(&name, L"\\??\\C:\\tmp\\data.bin");
OBJECT_ATTRIBUTES oa;
InitializeObjectAttributes(&oa, &name,
OBJ_CASE_INSENSITIVE, nullptr, nullptr);
IO_STATUS_BLOCK io;
HANDLE hFile = nullptr;
NTSTATUS st = NtCreateFile(&hFile,
FILE_GENERIC_READ, &oa, &io, nullptr,
FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ,
FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT,
nullptr, 0);
if (!NT_SUCCESS(st)) return st;Win32 API(CreateFile)
│
▼
NtCreateFile(原生 API,ntdll.dll)
│
├─ 参数更底层:OBJECT_ATTRIBUTES / 标志位
├─ 自己初始化对象属性(Win32 会替你填)
└─ 返回 NTSTATUS(Win32 转成错误码)
能用 Win32 就用 Win32;Native API 只用于
需要底层参数控制的场景(未承诺稳定)段五:新旧两代过滤框架 —— 一个逻辑,两种写法
传统过滤驱动与 Minifilter 实现的是同一件事(在文件 I/O 路径上插一手),但写法天差地别:老框架手写整套 MajorFunction 分发和 IRP 传递,新框架只声明回调表,基础设施全由过滤器管理器承包。
// 老框架:接管 IRP 分发例程,全部手写
NTSTATUS DriverEntry(PDRIVER_OBJECT Drv, PUNICODE_STRING)
{
Drv->MajorFunction[IRP_MJ_CREATE] = FsFilterCreate;
Drv->MajorFunction[IRP_MJ_READ] = FsFilterRead;
Drv->MajorFunction[IRP_MJ_WRITE] = FsFilterWrite;
Drv->DriverUnload = FsFilterUnload;
return STATUS_SUCCESS;
}
// 每个分发例程都要:查栈位置 → 处理 →
// IoCallDriver 下传 → IoCompletion 回调收尾// 新框架:只声明关心的操作,其余框架承包
FLT_OPERATION_REGISTRATION g_ops[] = {
{ IRP_MJ_CREATE, 0, OnCreatePre, nullptr, nullptr },
{ IRP_MJ_READ, 0, OnReadPre, nullptr, nullptr },
{ IRP_MJ_WRITE, 0, OnWritePre, nullptr, nullptr },
{ IRP_MJ_OPERATION_END } // 表结束标记
};
// 注册两行:FltRegisterFilter + FltStartFiltering
// 传参/完成/名字缓冲/上下文全由 FltMgr 处理容易踩的坑
小结
- 驱动签名两轨:测试轨(testsigning + 自签)限测试机,发布轨(EV + attestation)才进生产
- Driver Verifier 只对目标驱动开在测试机,违规当场蓝屏、转储定位
- Native API 是 Win32 之下的底层通道,能用 Win32 就别直接用
- 传统过滤驱动是遗留路线,新代码一律 Minifilter;SSDT 挂钩是 PatchGuard 禁区
- 设备通知经 IoRegisterPlugPlayNotification 注册,卸载用句柄注销
练习
问题 1: 在 Demo 中打开“全局 Verifier”开关,观察结果条与恢复提示。为什么“违规当场蓝屏”恰恰是 Verifier 的价值,而不是它的缺点?
问题 2: 你的驱动要交付给开启 HVCI(内存完整性)的客户机器。给出完整的签名与发布检查清单,并说明为什么测试签名路径在这里不够用。
问题 3(独立实现题): 实现 USB 设备到达通知的三段:1)写出 RegisterDeviceNotification——订阅设备接口变化事件、按 USB 设备 GUID 过滤、保存通知句柄;2)写出 OnDeviceNotify 回调——到达时打印接口名、移除时打印移除;3)写出卸载注销片段——注销通知句柄并判空。要求回调区分到达/移除事件。
知识点对照
11.1 驱动程序签名
驱动程序签名用微软认可的证书签署驱动,系统启动时校验签名的合法性、完整性与发布来源,未签名驱动无法加载。
11.2 驱动程序验证器
驱动程序验证器强制检查内存分配、锁的使用与 IRQL 规范,非常适合在测试阶段开启并使用。
11.3 使用原生API
使用原生API直接调用 Nt 系列系统服务,可以绕过 Win32 层的部分限制与封装,直达内核。
11.4 过滤驱动程序
过滤驱动程序附加在设备栈上,拦截并处理来自上下层的 I/O 请求,从而实现对设备的监控与扩展功能。
11.4.1 过滤驱动程序的实现
过滤驱动程序的实现要点是复制目标设备对象并挂接到设备栈上,再把请求转发给下一层处理,同时保存原设备指针。
11.4.2 附加过滤器
附加过滤器在驱动加载时创建过滤设备对象,并将其附加到目标设备栈的顶部开始拦截工作,卸载时再摘除。
11.5 设备监视器
设备监视器用过滤驱动观察设备栈上的所有 IRP 请求,并记录每个请求的处理结果与最终状态信息。
11.5.1 增加过滤设备
增加过滤设备时创建过滤设备对象,并将其附加到目标设备栈之上,完成挂接并开始接收 I/O 请求。
11.5.2 移除过滤设备
移除过滤设备时先从设备栈上脱离,再删除过滤设备对象,并释放其占用的全部资源,恢复设备栈的原状。
11.5.3 初始化和卸载
初始化和卸载阶段负责注册与注销设备接口,管理过滤设备从创建到删除的整个生命周期过程,保证栈完整性。
11.5.4 处理请求
处理请求在过滤器的分发例程中执行,根据既定规则决定放行、修改或完成该请求,然后返回处理状态。
11.6 驱动程序挂钩
驱动程序挂钩通过替换函数指针或系统服务表来拦截系统调用,从而实现行为监控与安全防护功能,但易被检测。
11.7 内核库
内核库提供通用的内核函数集合,驱动程序可以静态链接并复用其中的代码,从而避免重复开发工作。
11.4.3 在任意时刻附加过滤器
在任意时刻附加过滤器需要枚举设备栈并用 IoAttachDeviceToDeviceStack 安全挂接。
11.4.4 过滤器的清理
过滤器的清理在卸载或设备移除时执行,先脱离设备栈再删除过滤设备对象,并释放占用的相关资源。
11.4.5 基于硬件的过滤驱动程序的更多内容
基于硬件的过滤驱动程序的更多内容涉及总线、端口与功能驱动之间的分层职责划分和协作关系,这也是本章的核心要点。
11.5.5 测试驱动程序
测试驱动程序用工具向目标设备发送请求,观察过滤设备记录的数据流是否完整正确,验证通过,这也是本章的核心要点。
11.5.6 请求的结果
请求的结果在完成例程中读取状态与返回数据,并记录到监视日志中供用户后续查看分析,这也是本章的核心要点。
11.8 总结
本章总结了签名、验证器、原生 API 与传统过滤驱动等其他主题的核心要点和落地实践经验分享。
术语表
名词解释
本章出现的专业名词,用大白话再讲一遍。
- 驱动签名
对驱动文件做数字签名,64 位 Windows 强制要求。测试签名(自签证书)只限测试机;发布签名(EV + 微软 attestation)才是生产通道。相当于大楼发的通行证:临时访客证只让进测试楼,正式员工证才能进生产区。
- Driver Verifier
Windows 内置的驱动验证工具:对目标驱动开启内存池、IRQL、锁、引用计数等规则检查,违规当场蓝屏并借转储定位。相当于大楼给员工做体检:有暗病当场查出来,总比入职后随时倒下强。
- Native API
内核导出的底层系统服务接口(Nt*/Zw* 函数),Win32 API 内部就是调用它们实现的。相当于大楼的内部员工通道:Win32 是前台,Native API 是直达后台的楼梯——能用前台就走前台,内部通道不承诺稳定。
- 传统过滤驱动
Minifilter 出现之前的老式文件系统过滤驱动:自己接管 IRP 分发例程、自己管理设备栈。相当于旧式仓库管理员:一个人管全部流程,费力且易错;现在有了分工明确的值班岗体系(Minifilter),新仓库不再用旧办法。
- 设备通知
驱动通过 IoRegisterPlugPlayNotification 注册的回调机制,在设备到达、移除或接口变化时收到内核通知。相当于大楼的入住监控:新设备(U 盘)插进来,系统第一时间通知相关岗位,撤岗时注销通知。