返回

如何编译加载微过滤器驱动? WDK、INF与sc.exe详解

windows

搞定文件系统微过滤器驱动编译与加载:从 WDK 到 sc.exe

开发 Windows 驱动,特别是文件系统微过滤器(Filesystem Minifilter),有时会遇到点小麻烦。比如,你可能发现找不到以前那个专门的微过滤器项目模板了,或者想知道能不能不用 .inf 文件,直接用 sc.exe 来安装和加载驱动。咱们就来捋一捋怎么编译和部署这种驱动,特别是怎么绕过一些常见的坑。

问题来了:怎么编译和安装微过滤器?

你可能正捣鼓 WDM 驱动,注意到 Visual Studio 里的 WDK 模板似乎少了“FileSystem Mini-Filter”这个选项(或者感觉它变了)。随之而来的问题是:

  1. 还能用那个经典的 WDM 驱动模板,然后手动链接 fltmgr.lib 来搞定吗?
  2. 有没有办法不写 .inf 文件,直接安装微过滤器驱动?
  3. 我知道 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

操作步骤:

  1. 安装 Visual Studio 和 WDK: 确保你的开发环境装好了这两个。
  2. 创建项目:
    • 打开 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),你需要自己添加微过滤器的代码结构。
  3. 配置项目属性:
    • 右键点击项目 -> "属性"。
    • 确保 "配置属性" -> "常规" -> "平台工具集" 指向你安装的 WDK。
    • 检查 "驱动程序设置" (Driver Settings) -> "目标 OS 版本" 和 "目标平台"。
  4. 编写代码:
    • 你的主源文件需要包含 <fltKernel.h>
    • 实现必要的 DriverEntry 函数。这是驱动的入口点,你需要在这里调用 FltRegisterFilter 来向筛选器管理器注册你的驱动。
    • 实现微过滤器所需的回调函数(比如 PreOperationCallbackPostOperationCallback)。

代码示例 (简化版 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。如果你熟悉编译链接过程,这完全可行,但比较繁琐,容易出错。

操作步骤:

  1. 创建 WDM 项目: 使用 Visual Studio 创建一个 "Empty WDM Driver" 项目。
  2. 添加源文件: 创建你的 .c.cpp 文件,并加入到项目中。
  3. 包含头文件: 在代码中 #include <fltKernel.h>。确保项目的包含目录 (Include Directories) 设置正确,能找到 WDK 的头文件。
  4. 链接 fltmgr.lib:
    • 右键点击项目 -> "属性"。
    • 导航到 "链接器" (Linker) -> "输入" (Input)。
    • 在 "附加依赖项" (Additional Dependencies) 里,明确添加 fltmgr.lib;
    • 确保 "库目录" (Library Directories) 设置正确,能找到 WDK 的 lib 目录 (例如 $(KMDF_LIB_PATH)$(KMDF_VER_PATH)\ 或 WDK 安装目录下的 lib\winXXX\km)。
  5. 编写代码: 同方案一,实现 DriverEntry 和必要的回调函数。
  6. 编译: 构建项目。

安全建议:
这种方式下,你更容易忘记某些编译选项或链接设置,可能导致驱动行为异常或不稳定。强烈建议反复核对 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")。这个高度值必须是字符串形式。

安装方法:

  1. MyMinifilter.sysMyMinifilter.inf 放在一起。
  2. 右键点击 .inf 文件,选择“安装”。(需要管理员权限)
  3. 或者使用命令行工具 pnputil /add-driver MyMinifilter.inf /install

再聊 sc.exe:手动挡的挑战

现在来看你问题的核心:能不能绕过 INF,只用 sc.exe

可以,但有坑,需要手动补救。

原理与作用:
sc.exe 是一个用来管理 Windows 服务的命令行工具。它可以创建、查询、启动、停止和删除服务,包括驱动服务。但是,它是一个通用的服务管理工具,并不“理解”文件系统微过滤器所需的特殊注册表结构,特别是 InstancesAltitude

操作步骤:

  1. 复制驱动文件: 手动将编译好的 MyMinifilter.sys 复制到 C:\Windows\System32\drivers 目录下。(需要管理员权限)

  2. 创建服务 (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 还不知道怎么加载它,因为它缺少高度信息。

  3. 手动添加注册表项 (reg addregedit): 这是最关键的一步,用来弥补 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 图形界面手动创建这些键和值,但命令行更方便脚本化。

  4. 启动服务 (sc startnet start):
    现在注册表配置好了,可以尝试加载驱动了。

    sc start MyMfService
    

    或者

    net start MyMfService
    

    如果一切顺利,驱动应该会加载,并在内核调试器(如 WinDbg)中看到你的 DriverEntry 打印的调试信息。

  5. 停止和删除服务 (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 请求(可能已经被上层处理过),比如杀毒软件的文件扫描可能需要在较低的高度。

微软维护着一个已分配的高度范围列表,以避免不同厂商的驱动产生冲突。开发时,你应该:

  1. 查阅微软官方文档,了解不同功能类型的过滤器推荐的高度范围。
  2. 如果计划公开发布驱动,需要向微软申请一个唯一的高度值。
  3. 测试时,可以使用微软文档中为测试/开发保留的高度范围,或者选择一个不太可能冲突的范围(比如你引用的例子里的 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 文件才是正道。它不仅更规范、更可靠,也是确保驱动能被系统正确管理和卸载的关键。