ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Windows 驱动开发:用 Minifilter 检测文件与流删除——Windows-driver-samples delete 示例深度解析

2026/9/27 10:40:57 拓冰建站 浏览量
Windows 驱动开发:用 Minifilter 检测文件与流删除——Windows-driver-samples delete 示例深度解析 示例工程【免费下载链接】Windows-driver-samplesThis repo contains driver samples prepared for use with Microsoft Visual Studio and the Windows Driver Kit (WDK). It contains both Universal Windows Driver and desktop-only driver samples.项目地址https://gitcode.com/gh_mirrors/wi/Windows-driver-samples点击查看免费下载Delete 是一个以文件删除/流删除检测为核心目标的文件系统微过滤驱动Minifilter示例位于本仓库 filesys/miniFilter/delete 目录。它不拦截删除而是在 IRP_MJ_CREATE、IRP_MJ_SET_INFORMATION 与 IRP_MJ_CLEANUP 的回调中标记删除候选并在操作完成后验证删除是否真正发生最终以内核调试输出DbgPrint的形式上报。阅读本文后你将掌握文件删除在 Windows 上不可提前预测的根本原因、两种删除路径FILE_DELETE_ON_CLOSE 与 FileDispositionInformation/FileDispositionInformationEx的检测原理、并发 SetDisposition 竞态的识别方法、整文件删除与单流删除的区分技巧以及事务TxF环境下删除上报的提交/回滚处理。概述一个只做事后确认的删除检测过滤器该示例在 README 中定义为 Demonstrates how to detect deletions of files or streams. Deletions are reported as debug output.README.md即检测对象文件file与流stream含命名数据流 ADS的删除输出方式删除事件通过DbgPrintExDPFLTR_FLTMGR_ID、DPFLTR_ERROR_LEVEL写入内核调试输出参见 delete.c 中的DF_PRINT宏运行形态构建出的delete.sys是一个完整的文件系统微过滤驱动全部代码集中在一个 delete.c约 3282 行中。整个驱动只有两个事件入口和一个验证出口阶段关注对象作用IRP_MJ_CREATEFILE_DELETE_ON_CLOSE标志标记关闭即删的删除候选IRP_MJ_SET_INFORMATIONFileDispositionInformation/FileDispositionInformationEx记录删除处置delete disposition状态变化IRP_MJ_CLEANUPpost删除候选的最终状态验证文件/流是否真的被删除并上报这三个入口在 Callbacks 操作注册表 中登记CONST FLT_OPERATION_REGISTRATION Callbacks[] { { IRP_MJ_CREATE, 0, DfPreCreateCallback, DfPostCreateCallback }, { IRP_MJ_SET_INFORMATION, FLTFL_OPERATION_REGISTRATION_SKIP_PAGING_IO, DfPreSetInfoCallback, DfPostSetInfoCallback }, { IRP_MJ_CLEANUP, 0, DfPreCleanupCallback, DfPostCleanupCallback }, { IRP_MJ_OPERATION_END } };注意IRP_MJ_SET_INFORMATION使用了FLTFL_OPERATION_REGISTRATION_SKIP_PAGING_IO跳过分页 I/O避免在内存不足的换页路径上增加额外负担。核心难点为什么删除无法提前预知README 特别强调了一个设计约束README.md 中的 NOTE由于 Windows 操作系统删除文件的机制minifilter 无法提前预知某个文件或流将被删除它只能检测可能导致删除的操作然后在该操作完成后判断删除是否真的发生了。原因在于 Windows 的删除模型是延迟删除删除并不是在发出删除请求时立即完成而是先把文件/流置为删除待定delete-pending待最后一个句柄关闭后才真正从卷上移除。因此一次FILE_DELETE_ON_CLOSE创建可能最终没有删除例如后续又通过FileDispositionInformationEx清除了删除标志一次SetFileDisposition也可能因为后续句柄仍打开而迟迟不生效同一流上的多个删除处置操作还可能互相覆盖race。所以本示例的全部逻辑都遵循同一思路先标记候选再在 post-cleanup 时做最终验证验证手段是查询FileStandardInformation返回STATUS_FILE_DELETED即证明已删除必要时再通过文件 ID 打开或查询对象 ID 来区分整文件删除与仅流删除。三种上下文实例、流与事务驱动通过 FltMgr 上下文Context机制保存跨操作的状态注册于 Contexts 注册表FLT_INSTANCE_CONTEXTDF_INSTANCE_CONTEXT缓存卷 GUID 名称VolumeGuidName避免反复查询FLT_STREAM_CONTEXTDF_STREAM_CONTEXT这是删除检测的核心载体字段包括NameInfo最后一次 pre-cleanup 时取得的打开名FLT_FILE_NAME_INFORMATION用于上报FileId文件 IDDF_FILE_REFERENCE兼容 NTFS 64 位与 ReFS 128 位NumOps正在进行的删除处置操作计数竞态检测用IsNotified是否已上报过删除防止重复上报SetDisp/DeleteOnClose删除处置与关闭即删状态。FLT_TRANSACTION_CONTEXTDF_TRANSACTION_CONTEXT维护事务内待上报的删除通知链表DeleteNotifyList与保护它的ERESOURCE。一个值得学习的细节DF_TRANSACTION_CONTEXT中的ERESOURCE被设计成指针 单独分配而不是直接内嵌在结构里delete.c 的注释说明了原因。因为ERESOURCE必须从NonPagedPool分配若直接内嵌整个事务上下文都要提升为非分页内存用指针方式则主体仍可放在PagedPoolDF_CONTEXT_POOL_TYPE PagedPool仅ERESOURCE本身走非分页池从而节省非分页内存。上下文的获取/分配/附加统一封装在DfGetOrSetContextdelete.c中它实现了无则取、有则建、建后设、竞态则复用他人已设置的那份的完整语义当上下文类型是FLT_TRANSACTION_CONTEXT时还会额外调用FltEnlistInTransaction加入事务注册DF_NOTIFICATION_MASK即TRANSACTION_NOTIFY_COMMIT_FINALIZE | TRANSACTION_NOTIFY_ROLLBACK见 delete.c。检测路径一FILE_DELETE_ON_CLOSE创建即删应用以FILE_DELETE_ON_CLOSE打开文件时文件会在最后一个句柄关闭时被删除。DfPreCreateCallbackdelete.c检查创建选项if (FlagOn( Data-Iopb-Parameters.Create.Options, FILE_DELETE_ON_CLOSE )) { status DfAllocateContext( FLT_STREAM_CONTEXT, streamContext ); if (NT_SUCCESS( status )) { *CompletionContext (PVOID)streamContext; return FLT_PREOP_SYNCHRONIZE; } ... } *CompletionContext NULL; return FLT_PREOP_SUCCESS_NO_CALLBACK;关键点只有命中FILE_DELETE_ON_CLOSE才预分配流上下文并通过CompletionContext传给 post 回调返回FLT_PREOP_SYNCHRONIZE强制同步 post 回调保证创建结果立即可见在DfPostCreateCallbackdelete.c中只有创建真正成功且不是重解析点STATUS_REPARSE时才把上下文附加到流并置streamContext-DeleteOnClose TRUE。代码中的FLTFL_POST_OPERATION_DRAINING断言则用于确保没有处于排空unload 中的残余操作状态。检测路径二FileDispositionInformation / FileDispositionInformationEx另一种删除方式是IRP_MJ_SET_INFORMATION设置删除处置。DfPreSetInfoCallbackdelete.c只对FileDispositionInformation与FileDispositionInformationEx两类信息类感兴趣并在此完成竞态检测race (InterlockedIncrement( streamContext-NumOps ) 1); if (!race) { // 唯一在途操作结果是确定的做 postop *CompletionContext (PVOID)streamContext; return FLT_PREOP_SYNCHRONIZE; } else { // 存在并发操作删除处置的最终状态不可知 FltReleaseContext( streamContext ); } // FALL_THROUGH return FLT_PREOP_SUCCESS_NO_CALLBACK;这里的逻辑非常精巧NumOps记录在途的删除处置修改数。当检测到并发race时post 回调不会被调用NumOps也就永远不会被递减于是该值保持 2永久地把这个流标记为必须检查删除的候选——因为既然无法确定处置状态的最终结果就宁可多做一次删除验证。而在非竞态情况下DfPostSetInfoCallbackdelete.c会在操作成功后把结果写入上下文FileDispositionInformationEx若设置了FILE_DISPOSITION_ON_CLOSE则按FILE_DISPOSITION_DELETE更新DeleteOnClose否则更新SetDispFileDispositionInformation直接用FILE_DISPOSITION_INFORMATION.DeleteFile更新SetDisp。最后InterlockedDecrement( streamContext-NumOps )并释放上下文引用保持计数与引用平衡。验证出口post-cleanup 才是真相时刻无论文件是通过哪种方式进入删除候选状态真正的判定都在DfPostCleanupCallbackdelete.c。前置的DfPreCleanupCallbackdelete.c只做一件事为带流上下文的文件抓取名称信息DfGetFileNameInformation内部使用FltGetFileNameInformation(FLT_FILE_NAME_OPENED | FLT_FILE_NAME_QUERY_DEFAULT)FltParseFileNameInformation以便上报时有名字可用。post-cleanup 中的候选判定条件源码注释原话归纳NumOps 0—— 存在过竞态处置状态未知保守起见必须检查SetDisp TRUE—— 已设置删除处置DeleteOnClose TRUE—— 曾以FILE_DELETE_ON_CLOSE打开注意FileDispositionInformationEx可清除该标志。三者满足其一且IsNotified 0尚未上报过时执行验证status FltQueryInformationFile( Data-Iopb-TargetInstance, Data-Iopb-TargetFileObject, fileInfo, sizeof(fileInfo), FileStandardInformation, NULL ); if (STATUS_FILE_DELETED status) { status DfProcessDelete( Data, FltObjects, streamContext ); ... }FileStandardInformation查询返回STATUS_FILE_DELETED即证明流已被删除随后进入DfProcessDelete做最终定性。整文件删除 vs 单流删除的区分这是本示例最有技术含量的一环。场景是当最后一个句柄恰好是某个删除待定的命名数据流ADS的句柄时关闭它会连带整个文件一起消失——此时应上报整文件删除而不是流删除。DfProcessDeletedelete.c先判断是否处于事务中FltObjects-Transaction ! NULL然后调用DfIsFileDeleteddelete.c来区分。该函数按文件系统类型与事务状态选择验证手段场景手段判定非事务 NTFSFSCTL_GET_OBJECT_IDFltFsControlFileSTATUS_OBJECTID_NOT_FOUND→ 文件仍存在只是没有对象 ID其余错误码透传STATUS_FILE_DELETED表示已删除事务中 或 ReFS按文件 ID 打开DfDetectDeleteByFileIdSTATUS_INVALID_PARAMETER→ 文件已删除映射为STATUS_FILE_DELETEDSTATUS_DELETE_PENDING→ 文件仍在但删除待定映射为成功之所以分两条路径代码注释给出了两个关键原因FSCTL_GET_OBJECT_ID在事务内删除时不会返回STATUS_FILE_DELETED所以事务场景必须改用打开文件 ID 的方式ReFS 不支持对象 ID因此 ReFS 卷上始终走按 ID 打开。按文件 ID 打开的核心实现在DfDetectDeleteByFileIddelete.cstatus DfBuildFileIdString( Data, FltObjects, StreamContext, fileIdString ); ... IoInitializeDriverCreateContext( driverCreateContext ); driverCreateContext.TxnParameters IoGetTransactionParameterBlock( Data-Iopb-TargetFileObject ); status FltCreateFileEx2( gFilterHandle, Data-Iopb-TargetInstance, handle, NULL, FILE_READ_ATTRIBUTES, objectAttributes, ioStatus, (PLARGE_INTEGER) NULL, 0L, FILE_SHARE_VALID_FLAGS, FILE_OPEN, FILE_OPEN_REPARSE_POINT | FILE_OPEN_BY_FILE_ID, (PVOID) NULL, 0L, IO_IGNORE_SHARE_ACCESS_CHECK, driverCreateContext ); if (NT_SUCCESS( status )) { status FltClose( handle ); ... }关键点打开路径由卷 GUID 名 反斜杠 文件 ID 组成DfBuildFileIdStringdelete.c必须用FILE_OPEN_BY_FILE_ID按 ID 打开必须从当前文件对象继承事务参数块IoGetTransactionParameterBlock以保证打开操作发生在同一事务内文件已删除时DfBuildFileIdString内部查询文件 ID 的FltQueryInformationFile会直接返回STATUS_FILE_DELETED短路整个打开过程见 delete.c 注释而按 ID 打开一个不存在的文件则返回STATUS_INVALID_PARAMETER。NTFS 与 ReFS 的文件 ID 兼容DF_FILE_REFERENCE联合体delete.c专门处理两种文件 IDNTFS 的 64 位 ID 与 ReFS 的 128 位 ID。DfGetFileIddelete.c先查询FileInternalInformation若返回FILE_INVALID_FILE_ID说明该 ID 必须用 128 位表示再改用FileIdInformation查询。代码还通过KeMemoryBarrier()保证写入 128 位 ID与置位FileIdSet的次序因为编译器不支持原生 128 位值并用DfSizeofFileId宏动态判断 ID 长度确保 ReFS 按 ID 打开时 64/128 位皆可用。事务TxF环境下的延迟上报文件在事务内被删除时提交前文件仍是活着的只有提交才真正生效回滚则相当于没删。因此驱动不能在 post-cleanup 立刻上报而要挂起等待事务终局。机制如下DfProcessDelete检测到处于事务中时通过DfGetOrSetContext取得事务上下文并经由FltEnlistInTransaction登记了TRANSACTION_NOTIFY_COMMIT_FINALIZE | TRANSACTION_NOTIFY_ROLLBACK通知DfNotifyDeletedelete.c此时不直接打印deleted而是打印deleted in a transaction!并调用DfAddTransDeleteNotifydelete.c把一条DF_DELETE_NOTIFY内含StreamContext引用与FileDelete标志挂入事务上下文的DeleteNotifyList该链表由独立分配的ERESOURCE保护DfTransactionNotificationCallbackdelete.c在事务提交或回滚时被 FltMgr 回调提交则打印 deleted due to a transaction commit!回滚则先把IsNotified递减让删除候选回到未上报状态再打印 saved due to a transaction rollback!见DfNotifyDeleteOnTransactionEnddelete.c事务上下文清理回调DfTransactionContextCleanupCallbackdelete.c负责清空链表、释放每个DF_DELETE_NOTIFY及其StreamContext引用、销毁ERESOURCE。这一整套挂起—提交/回滚时结算的设计是事务型文件删除上报的正确范式也解释了为什么事务上下文要同时维护列表与锁。只挂接可写 NTFS/ReFS 卷DfInstanceSetupdelete.c决定了过滤器挂接范围先调用FltIsVolumeWritable判断卷是否可写——挂接到只读卷没有意义因为只读卷上不可能删除文件再按VolumeFilesystemType限定为FLT_FSTYPE_NTFS与FLT_FSTYPE_REFS其他情况一律返回STATUS_FLT_DO_NOT_ATTACH。INF 安装配置解读delete.inf 展示了典型的 UWD minifilter 安装配置[Version] Signature $Windows NT$ Class ActivityMonitor ;This is determined by the work this filter driver does ClassGuid {b86dff51-a31e-4bac-b3cf-e8cfe75c9fc2} ;This value is determined by the Class Provider %ProviderString% DriverVer 06/16/2007,1.0.0.1 CatalogFile delete.cat PnpLockdown 1设备类ActivityMonitor活动监视类与观察但不拦截的定位一致服务配置MiniFilter.ServiceServiceType 2SERVICE_FILE_SYSTEM_DRIVER、StartType 3SERVICE_DEMAND_START 按需启动、ErrorControl 1、Dependencies FltMgr、LoadOrderGroup FSFilter Activity Monitor注册表配置MiniFilter.AddRegistrySupportedFeatures 0x3并在Parameters\Instances下注册默认实例实例名delete Instance、海拔Altitude 370150、Flags 0x0允许所有挂接同时提供面向新版 Windows 的DefaultInstall.NT$ARCH$.10.0...25952通用安装节以及面向旧系统的 downlevel 安装/卸载节含LegacyUninstall与DelService0x200 停止后删除。海拔 370150 落在 INF 中声明的FSFilter Activity Monitor加载顺序组区间内且与LoadOrderGroup声明保持一致确保过滤器在文件系统栈中的加载次序正确。工程配置与构建工程文件 delete.vcxproj 声明DriverTargetPlatform Universal、PlatformToolset WindowsKernelModeDriver10.0、ConfigurationType Driver支持 Debug/Release × x64/ARM64 四套配置链接fltMgr.lib启用POOL_NX_OPTIN1、WarningLevel Level4且TreatWarningAsError true驱动签名摘要算法为 sha256delete.sln 与 delete.rcVFT_DRV / VFT2_DRV_SYSTEM文件描述 Delete Notification Filter Driver配套齐全README 明确说明该示例是Universal Windows DriverUWD仅使用 OneCoreUAP 包含的 API/DDI这意味着它可被用于需要通用驱动形态的 Windows 版本与架构组合整个仓库的构建流程可参考根目录的 Building-Locally.md 与 Build-Samples.ps1配合 Visual Studio WDK 完成编译与签名。调试输出与观察方法驱动的所有删除事件通过DF_DBG_PRINT(DFDBG_TRACE_ERRORS, ...)宏输出delete.c。gTraceFlags默认值为DFDBG_TRACE_ERRORS三个可用位为标志值用途DFDBG_TRACE_ERRORS0x00000001错误与删除事件上报默认开启DFDBG_TRACE_ROUTINES0x00000002例程进入/离开轨迹DFDBG_TRACE_OPERATION_STATUS0x00000004操作状态跟踪输出示例源码实际格式包括delete!DfPostCleanupCallback: A file %wZ (%p) has been deleted!delete!DfPostCleanupCallback: An alternate data stream %wZ (%p) has been deleted!事务场景A file %wZ (%p) has been deleted in a transaction!随后在提交/回滚时打印deleted due to a transaction commit!或saved due to a transaction rollback!上报同时打印文件打开名与流上下文指针%p后者可用于在重命名等边界情况下辅助区分对象参见DfPreCleanupCallback注释。若在 WinDbg/内核调试会话中加载该驱动并执行删除操作即可在调试输出流中观察到上述信息。源码结构速查函数均在 delete.c职责DriverEntry/DfUnloadFltMgr 注册、开始过滤 / 反注册DfInstanceSetup系列实例挂接策略与拆解回调DfGetOrSetContext/DfAllocateContext/DfSetContext/DfGetContext三类上下文的生命周期管理DfGetFileNameInformation/DfGetFileId/DfBuildFileIdString/DfGetVolumeGuidName名称、文件 ID、卷 GUID 获取与缓存DfDetectDeleteByFileId/DfIsFileDeleted按 ID 打开探测 / 整文件 vs 流删除判定DfProcessDelete/DfNotifyDelete/DfAddTransDeleteNotify/DfNotifyDeleteOnTransactionEnd删除定性、上报与事务挂起结算DfPre/PostCreateCallbackFILE_DELETE_ON_CLOSE 候选标记DfPre/PostSetInfoCallback删除处置跟踪与竞态检测DfPre/PostCleanupCallback名称采集与最终删除验证DfTransactionNotificationCallback提交/回滚时的延迟上报小结Delete 示例用约三千行代码完整演示了文件系统微过滤驱动中删除检测这一经典难题的工业级解法候选标记CREATE 的FILE_DELETE_ON_CLOSE SET_INFORMATION 的处置信息→ 竞态兜底NumOps永不归零策略→ 事后验证FileStandardInformation/ 对象 ID / 按文件 ID 打开→ 事务结算commit/rollback 通知。它同时兼顾了 NTFS 与 ReFS 的文件 ID 差异、整文件与流删除的语义区分以及 UWD 兼容性要求是学习IRP_MJ_CLEANUP语义、FltMgr 上下文与 TxF 事务通知的最佳起点之一。对于要在自己的驱动中实现删除监控如审计、数据保护、同步备份触发的开发者可以直接以本示例为骨架把DfNotifyDelete中的DbgPrint替换为实际业务上报逻辑。赞分享示例工程【免费下载链接】Windows-driver-samplesThis repo contains driver samples prepared for use with Microsoft Visual Studio and the Windows Driver Kit (WDK). It contains both Universal Windows Driver and desktop-only driver samples.项目地址https://gitcode.com/gh_mirrors/wi/Windows-driver-samples点击查看免费下载相关推荐Windows-driver-samples I2C驱动SkeletonI2C示例深度解析Windows driver samples I2C驱动SkeletonI2C示例深度解析 概述 SkeletonI2C是Windows驱动示例项目中的一个关示例工程性能优化指南Encrypted Core Data缓存配置与数据库迁移技巧 性能优化指南Encrypted Core Data缓存配置与数据库迁移技巧 Encrypted Core Data 是一个强大的iOS加密数据库解决方案应用安全密码学Windows-driver-samples智慧博物馆文物展示设备驱动开发Windows driver samples智慧博物馆文物展示设备驱动开发 传统博物馆展览常面临文物保护与观众体验的矛盾Windows driver sam示例工程上一篇米哈游游戏字体完全指南11款架空文字字体快速上手教程下一篇11款米哈游游戏字体完全指南让游戏文字走进现实创作创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考