如何编译加载微过滤器驱动? WDK、INF与sc.exe详解
2025-03-29 17:56:04
搞定文件系统微过滤器驱动编译与加载:从 WDK 到 sc.exe
开发 Windows 驱动,特别是文件系统微过滤器(Filesystem Minifilter),有时会遇到点小麻烦。比如,你可能发现找不到以前那个专门的微过滤器项目模板了,或者想知道能不能不用 .inf
文件,直接用 sc.exe
来安装和加载驱动。咱们就来捋一捋怎么编译和部署这种驱动,特别是怎么绕过一些常见的坑。
问题来了:怎么编译和安装微过滤器?
你可能正捣鼓 WDM 驱动,注意到 Visual Studio 里的 WDK 模板似乎少了“FileSystem Mini-Filter”这个选项(或者感觉它变了)。随之而来的问题是:
- 还能用那个经典的 WDM 驱动模板,然后手动链接
fltmgr.lib
来搞定吗? - 有没有办法不写
.inf
文件,直接安装微过滤器驱动? - 我知道
sc.exe
命令没法直接设置微过滤器的“高度”(Altitude),那如果我手动改注册表把高度加上去,能不能用sc create
创建服务,再用sc start
把它跑起来?
这些问题挺实在的,关系到开发流程和部署方式。咱们一步步来看。
为啥编译和安装微过滤器驱动会有点绕?
主要是因为微过滤器驱动跟传统的 WDM 设备驱动不太一样。它依赖于一个叫“筛选器管理器”(Filter Manager, FltMgr.sys
)的系统组件。FltMgr.sys
负责加载微过滤器、管理它们的栈位置(就是“高度”),并且把 I/O 请求分发给它们。
编译时,你需要包含 fltKernel.h
头文件,并链接 fltmgr.lib
库。这个库提供了微过滤器跟 FltMgr.sys
交互所需的各种函数。
安装时,情况更复杂点。标准的驱动安装方式是使用 .inf
文件。.inf
文件不仅仅是告诉系统把驱动文件复制到哪里,更重要的是,它包含了配置驱动服务、设置依赖关系、以及——对于微过滤器来说至关重要的——在注册表中登记“实例”(Instances)和“高度”(Altitude)的信息。
高度决定了你的微过滤器在文件系统栈中的位置,直接影响它能看到和处理哪些 I/O 操作。没有正确的高度信息,FltMgr.sys
就不知道该怎么加载和管理你的驱动。这就是为什么 sc.exe
直接安装会遇到麻烦,因为它本身不处理这些微过滤器特有的注册表项。
编译方案:路不止一条
搞定编译其实不算太难,主要看你用什么工具和方法。
方案一:利用 WDK 的驱动项目模板 (推荐)
这是最省心、最规范的做法。虽然你可能感觉模板名字或者位置变了,但在最新版的 Visual Studio 和 WDK (Windows Driver Kit) 中,通常都包含了用于开发各种驱动(包括文件系统驱动)的模板。
原理与作用:
WDK 提供的项目模板已经预先配置好了大部分编译和链接设置。它知道要包含哪些头文件,链接哪些库,使用什么样的编译器选项,能帮你避免很多手动配置的麻烦。对于微过滤器,它会自动包含 fltKernel.h
并链接 fltmgr.lib
。
操作步骤:
- 安装 Visual Studio 和 WDK: 确保你的开发环境装好了这两个。
- 创建项目:
- 打开 Visual Studio。
- 选择 "创建新项目"。
- 在搜索框里输入 "driver" 或 "kernel"。你可能会看到类似 "Kernel Mode Driver (KMDF)" 或 "Empty WDM Driver" 等模板。
- 关键点:虽然没有独立的 "Minifilter" 模板,但你可以基于 KMDF 或 WDM 模板开始,或者查找社区/微软提供的更具体的示例模板。一个常见做法是使用 WDK 自带的 Minifilter 示例(比如
C:\Program Files (x86)\Windows Kits\10\Samples\xxx\FileSystem\MiniFilter
目录下的示例)作为起点。 - 如果你选择了一个基础模板(如 KMDF/WDM),你需要自己添加微过滤器的代码结构。
- 配置项目属性:
- 右键点击项目 -> "属性"。
- 确保 "配置属性" -> "常规" -> "平台工具集" 指向你安装的 WDK。
- 检查 "驱动程序设置" (Driver Settings) -> "目标 OS 版本" 和 "目标平台"。
- 编写代码:
- 你的主源文件需要包含
<fltKernel.h>
。 - 实现必要的
DriverEntry
函数。这是驱动的入口点,你需要在这里调用FltRegisterFilter
来向筛选器管理器注册你的驱动。 - 实现微过滤器所需的回调函数(比如
PreOperationCallback
和PostOperationCallback
)。
- 你的主源文件需要包含
代码示例 (简化版 DriverEntry):
#include <fltKernel.h>
// 全局变量,保存筛选器句柄
PFLT_FILTER gFilterHandle = NULL;
// 卸载回调函数
NTSTATUS FilterUnload(FLT_FILTER_UNLOAD_FLAGS Flags);
// 操作回调函数 (示例)
FLT_PREOP_CALLBACK_STATUS PreOperationCallback(
PFLT_CALLBACK_DATA Data,
PCFLT_RELATED_OBJECTS FltObjects,
PVOID *CompletionContext
);
// 回调函数注册表
const FLT_OPERATION_REGISTRATION Callbacks[] = {
{ IRP_MJ_CREATE,
0, // Flags - 0表示不关心同步IO等
PreOperationCallback,
NULL }, // PostOperationCallback 设为 NULL
// ... 在这里添加你需要过滤的其他 I/O 操作
{ IRP_MJ_OPERATION_END } // 标记结束
};
// 过滤器注册信息
const FLT_REGISTRATION FilterRegistration = {
sizeof(FLT_REGISTRATION), // Size
FLT_REGISTRATION_VERSION, // Version
0, // Flags
NULL, // ContextRegistration
Callbacks, // OperationRegistration
FilterUnload, // FilterUnloadCallback
NULL, // InstanceSetupCallback
NULL, // InstanceQueryTeardownCallback
NULL, // InstanceTeardownStartCallback
NULL, // InstanceTeardownCompleteCallback
NULL, // GenerateFileNameCallback
NULL, // NormalizeNameComponentCallback
NULL // NormalizeContextCleanupCallback
};
// 驱动入口点
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
NTSTATUS status;
DbgPrint("MyMinifilter: DriverEntry entered.\n");
// 注册过滤器
status = FltRegisterFilter(
DriverObject,
&FilterRegistration,
&gFilterHandle); // 保存返回的句柄
if (NT_SUCCESS(status)) {
// 启动过滤 (如果注册成功)
status = FltStartFiltering(gFilterHandle);
if (!NT_SUCCESS(status)) {
// 启动失败,注销过滤器
FltUnregisterFilter(gFilterHandle);
} else {
DbgPrint("MyMinifilter: Filtering started successfully.\n");
}
} else {
DbgPrint("MyMinifilter: Failed to register filter (Status=0x%X).\n", status);
}
return status;
}
// 驱动卸载函数
NTSTATUS FilterUnload(FLT_FILTER_UNLOAD_FLAGS Flags) {
UNREFERENCED_PARAMETER(Flags);
PAGED_CODE(); // 确保在分页内存中执行
DbgPrint("MyMinifilter: FilterUnload called.\n");
// 先注销过滤器,这样系统就不会再发新的请求过来了
if (gFilterHandle != NULL) {
FltUnregisterFilter(gFilterHandle);
gFilterHandle = NULL; // 清理句柄
}
return STATUS_SUCCESS;
}
// Pre-operation 回调示例 (简单打印)
FLT_PREOP_CALLBACK_STATUS PreOperationCallback(
PFLT_CALLBACK_DATA Data,
PCFLT_RELATED_OBJECTS FltObjects,
PVOID *CompletionContext)
{
UNREFERENCED_PARAMETER(CompletionContext);
// 仅作演示,打印目标文件名
if (FltObjects->FileObject != NULL) {
PFLT_FILE_NAME_INFORMATION nameInfo = NULL;
NTSTATUS status = FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &nameInfo);
if (NT_SUCCESS(status)) {
DbgPrint("MyMinifilter: PreOperation for %wZ\n", &nameInfo->Name);
FltReleaseFileNameInformation(nameInfo);
}
}
// 让请求继续下去
return FLT_PREOP_SUCCESS_NO_CALLBACK;
}
进阶技巧:
- 研究 WDK 提供的 minifilter 示例代码,它们结构更完整,处理了更多细节。
- 利用 WDK 的构建环境(比如命令行
msbuild
)可以实现自动化构建。
方案二: 手动链接 fltmgr.lib
(基于 WDM 模板?- 可能有点绕)
理论上,你可以从一个空的 WDM 驱动模板开始,然后手动进行配置。
原理与作用:
这种方法的核心是自己动手,丰衣足食。你需要告诉编译器头文件在哪,告诉链接器要用 fltmgr.lib
。如果你熟悉编译链接过程,这完全可行,但比较繁琐,容易出错。
操作步骤:
- 创建 WDM 项目: 使用 Visual Studio 创建一个 "Empty WDM Driver" 项目。
- 添加源文件: 创建你的
.c
或.cpp
文件,并加入到项目中。 - 包含头文件: 在代码中
#include <fltKernel.h>
。确保项目的包含目录 (Include Directories) 设置正确,能找到 WDK 的头文件。 - 链接
fltmgr.lib
:- 右键点击项目 -> "属性"。
- 导航到 "链接器" (Linker) -> "输入" (Input)。
- 在 "附加依赖项" (Additional Dependencies) 里,明确添加
fltmgr.lib;
。 - 确保 "库目录" (Library Directories) 设置正确,能找到 WDK 的
lib
目录 (例如$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\
或 WDK 安装目录下的lib\winXXX\km
)。
- 编写代码: 同方案一,实现
DriverEntry
和必要的回调函数。 - 编译: 构建项目。
安全建议:
这种方式下,你更容易忘记某些编译选项或链接设置,可能导致驱动行为异常或不稳定。强烈建议反复核对 WDK 示例项目的设置。
结论: 虽然技术上可行,但一般不推荐这么做。使用 WDK 模板或基于官方示例能省去很多麻烦,并且能利用模板提供的最佳实践配置。
安装与加载:INF 还是 sc.exe
?
这部分才是问题的关键,特别是 sc.exe
和高度(Altitude)的纠葛。
先说重点:推荐用 INF 文件
对于微过滤器驱动,使用 .inf
文件是微软推荐的标准安装方式。
为什么推荐 INF?
- 自动化配置: INF 文件能自动完成服务的创建、驱动文件的复制、以及最重要的——注册表项的设置。
- 高度(Altitude)配置: 这是 INF 文件无法替代的核心功能。它通过特定的节(Section)来定义驱动加载的高度。
- 卸载方便: 通常 INF 文件也会包含卸载逻辑,能比较干净地移除驱动和相关配置。
- 标准化: 符合 Windows 驱动安装的标准流程。
INF 文件示例(关键部分):
假设你的驱动名叫 MyMinifilter.sys
,服务名叫 MyMfService
。
[Version]
Signature="$WINDOWS NT[Version]
Signature="$WINDOWS NT$"
Class=ActivityMonitor ; 或其他合适的文件系统过滤器类
ClassGuid={b86dff51-a31e-4bac-b3cf-e8cfe75c9fc2} ; Activity Monitor 的 GUID
Provider=%MyVendorName%
DriverVer= ; TODO: 设置日期和版本,例如 10/27/2023,1.0.0.0
CatalogFile=MyMinifilter.cat ; 用于驱动签名
[Manufacturer]
%MyVendorName%=MyDevices,NT$ARCH$
[MyDevices.NT$ARCH$]
%MyDeviceDescription%=MyInstallSection, ROOT\MyMinifilter ; 可以虚拟一个硬件ID
[DefaultInstall.NT$ARCH$] ; 对于非即插即用驱动,使用这个
CopyFiles=MyDriverFiles
AddReg=MyRegistrySettings
[DefaultInstall.NT$ARCH$.Services]
AddService=MyMfService,,MyServiceInstallSection
[MyServiceInstallSection]
DisplayName = %MyServiceDescription%
Description = "My awesome minifilter driver"
ServiceType = 2 ; SERVICE_FILE_SYSTEM_DRIVER
StartType = 1 ; SERVICE_SYSTEM_START (也可以是 3 = demand start)
ErrorControl= 1 ; SERVICE_ERROR_NORMAL
ServiceBinary = %12%\MyMinifilter.sys ; %12% 代表 Drivers 目录 (System32\drivers)
LoadOrderGroup = "FSFilter Activity Monitor" ; 指定加载组,需要与高度匹配
AddReg = MyServiceRegistrySettings ; 可选的服务特定注册表设置
[MyDriverFiles]
MyMinifilter.sys
[MyRegistrySettings]
; 这里是关键:设置实例和高度
HKLM,"SYSTEM\CurrentControlSet\Services\MyMfService\Instances","DefaultInstance",0x00000000,"MyMinifilterDefaultInstance"
HKLM,"SYSTEM\CurrentControlSet\Services\MyMfService\Instances\MyMinifilterDefaultInstance","Altitude",0x00000000,"370030" ; 这里设置你的高度值!
[MyServiceRegistrySettings]
; 例如,可以在这里为服务添加一些自定义参数
; HKLM,"SYSTEM\CurrentControlSet\Services\MyMfService\Parameters","MyParameter",0x00010001,1
[Strings]
MyVendorName="My Company Name"
MyDeviceDescription="My Minifilter Device"
MyServiceDescription="My Minifilter Service"
quot;
Class=ActivityMonitor ; 或其他合适的文件系统过滤器类
ClassGuid={b86dff51-a31e-4bac-b3cf-e8cfe75c9fc2} ; Activity Monitor 的 GUID
Provider=%MyVendorName%
DriverVer= ; TODO: 设置日期和版本,例如 10/27/2023,1.0.0.0
CatalogFile=MyMinifilter.cat ; 用于驱动签名
[Manufacturer]
%MyVendorName%=MyDevices,NT$ARCH$
[MyDevices.NT$ARCH$]
%MyDeviceDescription%=MyInstallSection, ROOT\MyMinifilter ; 可以虚拟一个硬件ID
[DefaultInstall.NT$ARCH$] ; 对于非即插即用驱动,使用这个
CopyFiles=MyDriverFiles
AddReg=MyRegistrySettings
[DefaultInstall.NT$ARCH$.Services]
AddService=MyMfService,,MyServiceInstallSection
[MyServiceInstallSection]
DisplayName = %MyServiceDescription%
Description = "My awesome minifilter driver"
ServiceType = 2 ; SERVICE_FILE_SYSTEM_DRIVER
StartType = 1 ; SERVICE_SYSTEM_START (也可以是 3 = demand start)
ErrorControl= 1 ; SERVICE_ERROR_NORMAL
ServiceBinary = %12%\MyMinifilter.sys ; %12% 代表 Drivers 目录 (System32\drivers)
LoadOrderGroup = "FSFilter Activity Monitor" ; 指定加载组,需要与高度匹配
AddReg = MyServiceRegistrySettings ; 可选的服务特定注册表设置
[MyDriverFiles]
MyMinifilter.sys
[MyRegistrySettings]
; 这里是关键:设置实例和高度
HKLM,"SYSTEM\CurrentControlSet\Services\MyMfService\Instances","DefaultInstance",0x00000000,"MyMinifilterDefaultInstance"
HKLM,"SYSTEM\CurrentControlSet\Services\MyMfService\Instances\MyMinifilterDefaultInstance","Altitude",0x00000000,"370030" ; 这里设置你的高度值!
[MyServiceRegistrySettings]
; 例如,可以在这里为服务添加一些自定义参数
; HKLM,"SYSTEM\CurrentControlSet\Services\MyMfService\Parameters","MyParameter",0x00010001,1
[Strings]
MyVendorName="My Company Name"
MyDeviceDescription="My Minifilter Device"
MyServiceDescription="My Minifilter Service"
说明:
Class=ActivityMonitor
和对应的ClassGuid
指定了驱动的类型,这会影响它在设备管理器中的显示以及与其他驱动的交互。你需要根据你的驱动功能选择合适的类。ServiceType = 2
明确告诉系统这是一个文件系统驱动。LoadOrderGroup
很重要,它需要和你申请到的或者测试用的高度范围所对应的组匹配。比如FSFilter Activity Monitor
对应的高度范围通常是 360000 - 389999。- 最关键的部分是
[MyRegistrySettings]
:- 第一行在服务的
Instances
项下创建了一个名为DefaultInstance
的字符串值,其数据指向了下一行要创建的键名。 - 第二行创建了实际的实例键
MyMinifilterDefaultInstance
,并在其中创建了Altitude
字符串值,赋予它你选择的高度(例如"370030"
)。这个高度值必须是字符串形式。
- 第一行在服务的
安装方法:
- 把
MyMinifilter.sys
和MyMinifilter.inf
放在一起。 - 右键点击
.inf
文件,选择“安装”。(需要管理员权限) - 或者使用命令行工具
pnputil /add-driver MyMinifilter.inf /install
。
再聊 sc.exe
:手动挡的挑战
现在来看你问题的核心:能不能绕过 INF,只用 sc.exe
?
可以,但有坑,需要手动补救。
原理与作用:
sc.exe
是一个用来管理 Windows 服务的命令行工具。它可以创建、查询、启动、停止和删除服务,包括驱动服务。但是,它是一个通用的服务管理工具,并不“理解”文件系统微过滤器所需的特殊注册表结构,特别是 Instances
和 Altitude
。
操作步骤:
-
复制驱动文件: 手动将编译好的
MyMinifilter.sys
复制到C:\Windows\System32\drivers
目录下。(需要管理员权限) -
创建服务 (
sc create
):
打开管理员权限的命令提示符或 PowerShell。执行以下命令:sc create MyMfService type= filesys start= demand error= normal binPath= C:\Windows\System32\drivers\MyMinifilter.sys DisplayName= "My Minifilter Service" group= "FSFilter Activity Monitor"
type= filesys
: 指定服务类型为文件系统驱动。start= demand
: 设置为手动启动(也可以是system
系统启动或auto
自动启动,但system
对启动阶段驱动更常用)。error= normal
: 启动失败时的错误报告级别。binPath=
: 指定驱动文件的完整路径。DisplayName=
: 服务的显示名称。group=
: 指定加载顺序组,务必与你打算设置的高度范围所属的组一致。这一步虽然sc.exe
支持,但单靠它还不够。
这时,服务是创建了,但 FltMgr 还不知道怎么加载它,因为它缺少高度信息。
-
手动添加注册表项 (
reg add
或regedit
): 这是最关键的一步,用来弥补sc.exe
的不足。reg add HKLM\SYSTEM\CurrentControlSet\Services\MyMfService\Instances /v DefaultInstance /t REG_SZ /d "MyMinifilterDefaultInstance" /f reg add HKLM\SYSTEM\CurrentControlSet\Services\MyMfService\Instances\MyMinifilterDefaultInstance /v Altitude /t REG_SZ /d "370030" /f
- 第一条命令在
SYSTEM\CurrentControlSet\Services\MyMfService
下创建Instances
子键,并在其中添加名为DefaultInstance
的字符串值,数据为你的实例名(比如MyMinifilterDefaultInstance
)。/f
参数强制覆盖现有值(如果存在)。 - 第二条命令创建实例键
MyMinifilterDefaultInstance
,并在其下添加名为Altitude
的字符串值,数据为你想要设置的高度值(比如"370030"
)。注意:高度值必须是字符串 (REG_SZ) 。
你也可以用
regedit
图形界面手动创建这些键和值,但命令行更方便脚本化。 - 第一条命令在
-
启动服务 (
sc start
或net start
):
现在注册表配置好了,可以尝试加载驱动了。sc start MyMfService
或者
net start MyMfService
如果一切顺利,驱动应该会加载,并在内核调试器(如 WinDbg)中看到你的
DriverEntry
打印的调试信息。 -
停止和删除服务 (
sc stop
,sc delete
):
需要时,可以用以下命令停止和删除服务:sc stop MyMfService sc delete MyMfService
重要提醒: 用
sc delete
删除服务后,之前用reg add
手动添加的Instances
注册表项不会 被自动删除!你需要手动清理:reg delete HKLM\SYSTEM\CurrentControlSet\Services\MyMfService\Instances /f
(注意:这会删除整个 Instances 键及其所有子项。执行
sc delete MyMfService
后,MyMfService
键本身可能也会被删除,但最好检查并手动清理残留的Instances
)
sc.exe
方式的缺点:
- 繁琐且易错: 手动操作注册表容易出错,输错键名、值名或高度值都可能导致驱动加载失败或行为异常。
- 清理不彻底: 卸载时需要手动清理注册表项,容易遗漏。
- 不适合发布: 这种方式对于开发和测试可能还行,但绝不适合用于向最终用户发布驱动。生产环境部署必须使用 INF 文件。
- 驱动签名: 无论用哪种方式安装,现代 Windows 系统(特别是 64 位)都需要驱动具有有效的数字签名才能加载。测试时可以使用测试签名(需要开启测试模式
bcdedit /set testsigning on
),发布时需要购买商业代码签名证书并提交微软签名。sc.exe
本身不处理签名验证,但操作系统加载驱动时会检查。
深入一点:高度(Altitude)这回事
简单来说,高度是一个数字(表示为字符串),决定了你的微过滤器在特定卷的文件系统驱动栈中的“楼层”。
- 数字越大,楼层越高 ,越先看到 I/O 请求,比如加密驱动可能需要高高度。
- 数字越小,楼层越低 ,越晚看到 I/O 请求(可能已经被上层处理过),比如杀毒软件的文件扫描可能需要在较低的高度。
微软维护着一个已分配的高度范围列表,以避免不同厂商的驱动产生冲突。开发时,你应该:
- 查阅微软官方文档,了解不同功能类型的过滤器推荐的高度范围。
- 如果计划公开发布驱动,需要向微软申请一个唯一的高度值。
- 测试时,可以使用微软文档中为测试/开发保留的高度范围,或者选择一个不太可能冲突的范围(比如你引用的例子里的 37xxxx 范围)。
FltMgr.sys
加载驱动时,会读取注册表里的 Altitude
值,并据此把你的驱动实例插入到正确的栈位置。这就是为什么没有这个注册表项,驱动就无法正确加载和运行的原因。
进阶技巧与安全建议
- 驱动签名是必须的: 不要忽视驱动签名。没有签名,你的驱动在多数用户的机器上根本无法加载。学习如何进行测试签名和获取发布签名。
- 内核调试是好朋友: 使用 WinDbg 和 VMWare/Hyper-V 虚拟机进行内核调试。这是排查驱动问题的必备技能。在代码中多加
DbgPrint
输出调试信息。 - 严谨的错误处理: 内核代码中的任何错误都可能导致系统蓝屏(BSOD)。务必检查每个内核 API 调用的返回值,并妥善处理错误情况。
- 资源管理: 小心处理内存分配 (
ExAllocatePoolXxx
/FltAllocateContext
) 和释放 (ExFreePool
/FltReleaseContext
),以及锁的使用,防止内存泄漏和死锁。 - 充分测试: 在不同版本的 Windows、不同的文件系统(NTFS, FAT32, ReFS)、不同的场景(高 I/O 负载、网络共享、特殊文件类型)下进行充分测试。
虽然用 sc.exe
配合手动修改注册表可以在没有 INF 的情况下安装和加载文件系统微过滤器驱动,但这种方法风险较高、维护困难,仅建议在受控的开发或测试环境中使用。对于任何正式用途,老老实实编写 INF 文件才是正道。它不仅更规范、更可靠,也是确保驱动能被系统正确管理和卸载的关键。