Windows驱动开发入门-文件系统微过滤驱动 本节讲文件系统微/小过滤驱动Minifilter,是sfilter(遗留过滤驱动)的新型替代品,使用比sfilter方便,因为不需要手动构造IRP、维护设备对象等,但也有些局限性,更底层的功能完全实现不了。
系统内部有个叫做过滤管理器的遗留过滤驱动,用来管理小过滤驱动。每个小过滤驱动都有自身高度值,该值确定它在设备栈中相对位置,从大到小顺序调用。当某些小过滤驱动高度会比遗留过滤驱动要高而某些要低,此时系统装入多个驱动管理器实例(称为帧),每个实例管理自己的小过滤驱动。
本节例子是限制notepad.exe文件的操作,使其无法被双击执行、无法被复制、无法被改名、无法被删除。
微软把Minifilter框架从VS2022的WDK中删除了,从这里找:https://learn.microsoft.com/en-us/windows-hardware/drivers/samples/file-system-driver-samples。
这里有个很好的示例:https://github.com/hkx3upper/FOKS-TROT。
编程框架 装入 用来装入的用户模式API是FilterLoad
,需要传递驱动名,即HKLM\System\CurrentControlSet\Services\drivername下键值。系统内部用内核APIFltLoadFilter
,语义一样。若从用户模式调用,则调用者令牌中必须包含SeLoadDriverPrivilege权限,该权限默认在管理员令牌中存在,标准用户令牌中不存在。
小过滤驱动的卸载回调可让卸载请求失败而将自身保留在系统中。
微文件系统过滤注册 用这个内核API注册:
1 2 3 4 5 NTSTATUS FltRegisterFilter ( IN PDRIVER_OBJECT Driver, IN CONST PFLT_REGISTRATION Registration, OUT PFLT_FILTER* RetFilter )
开启微过滤器:
1 2 3 NTSTATUS FltStartFiltering ( IN PFLT_FILTER Filter )
驱动没必要自己设置IRP分发例程,因为小过滤驱动不直接在I/O路径上,过滤管理器才是。
微过滤器注册结构 定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 typedef struct _FLT_REGISTRATION { USHORT Size; USHORT Version; FLT_REGISTRATION_FLAGS Flags; CONST FLT_CONTEXT_REGISTRATION *ContextRegistration; CONST FLT_OPERATION_REGISTRATION *OperationRegistration; PFLT_FILTER_UNLOAD_CALLBACK FilterUnloadCallback; PFLT_INSTANCE_SETUP_CALLBACK InstanceSetupCallback; PFLT_INSTANCE_QUERY_TEARDOWN_CALLBACK InstanceQueryTeardownCallback; PFLT_INSTANCE_TEARDOWN_CALLBACK InstanceTeardownStartCallback; PFLT_INSTANCE_TEARDOWN_CALLBACK InstanceTeardownCompleteCallback; PFLT_GENERATE_FILE_NAME GenerateFileNameCallback; PFLT_NORMALIZE_NAME_COMPONENT NormalizeNameComponentCallback; PFLT_NORMALIZE_CONTEXT_CLEANUP NormalizeContextCleanupCallback; #if FLT_MGR_LONGHORN PFLT_TRANSACTION_NOTIFICATION_CALLBACK TransactionNotificationCallback; PFLT_NORMALIZE_NAME_COMPONENT_EX NormalizeNameComponentExCallback; #endif #if FLT_MGR_WIN8 PFLT_SECTION_CONFLICT_NOTIFICATION_CALLBACK SectionNotificationCallback; #endif } FLT_REGISTRATION, *PFLT_REGISTRATION;
一个可行的设置例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const FLT_REGISTRATION FilterRegistration = { sizeof ( FLT_REGISTRATION ), FLT_REGISTRATION_VERSION, 0 , NULL , Callbacks, NPUnload, NPInstanceSetup, NPInstanceQueryTeardown, NPInstanceTeardownStart, NPInstanceTeardownComplete, NULL , NULL , NULL };
上面FLT_OPERATION_REGISTRATION结构:
1 2 3 4 5 6 7 8 9 10 typedef struct _FLT_OPERATION_REGISTRATION { UCHAR MajorFunction; FLT_OPERATION_REGISTRATION_FLAGS Flags; PFLT_PRE_OPERATION_CALLBACK PreOperation; PFLT_POST_OPERATION_CALLBACK PostOperation; PVOID Reserved1; } FLT_OPERATION_REGISTRATION, *PFLT_OPERATION_REGISTRATION;
OperationRegistration域的设置例子:
1 2 3 4 5 6 7 8 9 10 const FLT_OPERATION_REGISTRATION Callbacks[] = { { IRP_MJ_CREATE, 0 , NPPreCreate, NPPostCreate }, { IRP_MJ_OPERATION_END } };
DRIVER_OBJECT的FastIoDispatch成员指向一个FAST_IO_DISPATCH结构,包含一堆快速I/O回调。快速I/O用于对有缓存的文件进行同步I/O操作,在用户缓冲区和系统缓存之间传递数据,跳过文件系统和存储驱动栈。例如NTFS文件系统:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 3: kd> .reload /f ntfs.sys 3: kd> !drvobj \filesystem\ntfs f Driver object (ffff968918893da0) is for: \FileSystem\Ntfs Driver Extension List: (id , addr) Device Object list: ffff96891680d030 ffff968918887950 DriverEntry: fffff8027bc14010 Ntfs!GsDriverEntry DriverStartIo: 00000000 DriverUnload: 00000000 AddDevice: 00000000 Dispatch routines: //MajorFunction成员 [00] IRP_MJ_CREATE fffff8027babbf30 Ntfs!NtfsFsdCreate //创建或打开文件/目录 [01] IRP_MJ_CREATE_NAMED_PIPE fffff80279291250 nt!IopInvalidDeviceRequest [02] IRP_MJ_CLOSE fffff8027baba380 Ntfs!NtfsFsdClose [03] IRP_MJ_READ fffff8027b983ed0 Ntfs!NtfsFsdRead //从文件中读 [04] IRP_MJ_WRITE fffff8027b992c10 Ntfs!NtfsFsdWrite //写到文件中 [05] IRP_MJ_QUERY_INFORMATION fffff8027babc550 Ntfs!NtfsFsdDispatchWait [06] IRP_MJ_SET_INFORMATION fffff8027ba78da0 Ntfs!NtfsFsdSetInformation //设置文件信息 如删除/重命名等 [07] IRP_MJ_QUERY_EA fffff8027babc550 Ntfs!NtfsFsdDispatchWait //读取文件/目录扩展属性 [08] IRP_MJ_SET_EA fffff8027babc550 Ntfs!NtfsFsdDispatchWait [09] IRP_MJ_FLUSH_BUFFERS fffff8027bad0d70 Ntfs!NtfsFsdFlushBuffers [0a] IRP_MJ_QUERY_VOLUME_INFORMATION fffff8027bace480 Ntfs!NtfsFsdDispatch [0b] IRP_MJ_SET_VOLUME_INFORMATION fffff8027bace480 Ntfs!NtfsFsdDispatch [0c] IRP_MJ_DIRECTORY_CONTROL fffff8027bab73c0 Ntfs!NtfsFsdDirectoryControl //发给目录的请求 [0d] IRP_MJ_FILE_SYSTEM_CONTROL fffff8027ba5e2e0 Ntfs!NtfsFsdFileSystemControl //文件系统的设备I/O控制请求 [0e] IRP_MJ_DEVICE_CONTROL fffff8027ba5e160 Ntfs!NtfsFsdDeviceControl [0f] IRP_MJ_INTERNAL_DEVICE_CONTROL fffff80279291250 nt!IopInvalidDeviceRequest [10] IRP_MJ_SHUTDOWN fffff8027bbd23c0 Ntfs!NtfsFsdShutdown [11] IRP_MJ_LOCK_CONTROL fffff8027b9cb010 Ntfs!NtfsFsdLockControl [12] IRP_MJ_CLEANUP fffff8027babad20 Ntfs!NtfsFsdCleanup [13] IRP_MJ_CREATE_MAILSLOT fffff80279291250 nt!IopInvalidDeviceRequest [14] IRP_MJ_QUERY_SECURITY fffff8027bace480 Ntfs!NtfsFsdDispatch [15] IRP_MJ_SET_SECURITY fffff8027bace480 Ntfs!NtfsFsdDispatch [16] IRP_MJ_POWER fffff80279291250 nt!IopInvalidDeviceRequest [17] IRP_MJ_SYSTEM_CONTROL fffff80279291250 nt!IopInvalidDeviceRequest [18] IRP_MJ_DEVICE_CHANGE fffff80279291250 nt!IopInvalidDeviceRequest [19] IRP_MJ_QUERY_QUOTA fffff8027babc550 Ntfs!NtfsFsdDispatchWait [1a] IRP_MJ_SET_QUOTA fffff8027babc550 Ntfs!NtfsFsdDispatchWait [1b] IRP_MJ_PNP fffff8027bae05c0 Ntfs!NtfsFsdPnp //此外还有: //IRP_MJ_ACQUIRE_FOR_SECTION_SYNCHRONIZATION 节将被打开 //IRP_MJ_OPERATION_END 已到达操作回调数组末尾 Fast I/O routines: FastIoCheckIfPossible fffff8027bbb45f0 Ntfs!NtfsFastIoCheckIfPossible FastIoRead fffff8027bab9cf0 Ntfs!NtfsCopyReadA FastIoWrite fffff8027bab94a0 Ntfs!NtfsCopyWriteA FastIoQueryBasicInfo fffff8027bacb260 Ntfs!NtfsFastQueryBasicInfo FastIoQueryStandardInfo fffff8027bab81e0 Ntfs!NtfsFastQueryStdInfo FastIoLock fffff8027bacd460 Ntfs!NtfsFastLock FastIoUnlockSingle fffff8027bacdb70 Ntfs!NtfsFastUnlockSingle FastIoUnlockAll fffff8027bbb1990 Ntfs!NtfsFastUnlockAll FastIoUnlockAllByKey fffff8027bbb1c40 Ntfs!NtfsFastUnlockAllByKey ReleaseFileForNtCreateSection fffff8027b9a21c0 Ntfs!NtfsReleaseForCreateSection FastIoQueryNetworkOpenInfo fffff8027baa99a0 Ntfs!NtfsFastQueryNetworkOpenInfo AcquireForModWrite fffff8027b9a42b0 Ntfs!NtfsAcquireFileForModWrite MdlRead fffff8027ba755f0 Ntfs!NtfsMdlReadA MdlReadComplete fffff8027926ca30 nt!FsRtlMdlReadCompleteDev PrepareMdlWrite fffff8027badc1b0 Ntfs!NtfsPrepareMdlWriteA MdlWriteComplete fffff80279653980 nt!FsRtlMdlWriteCompleteDev FastIoQueryOpen fffff8027bab8470 Ntfs!NtfsNetworkOpenCreate ReleaseForModWrite fffff8027b9a7410 Ntfs!NtfsReleaseFileForModWrite AcquireForCcFlush fffff8027b992320 Ntfs!NtfsAcquireFileForCcFlush ReleaseForCcFlush fffff8027b9a58f0 Ntfs!NtfsReleaseFileForCcFlush Device Object stacks: !devstack ffff96891680d030 : !DevObj !DrvObj !DevExt ObjectName ffff9689167b5790 \FileSystem\FltMgr ffff9689167b58e0 > ffff96891680d030 \FileSystem\Ntfs ffff96891680d180 !devstack ffff968918887950 : !DevObj !DrvObj !DevExt ObjectName ffff9689166bfd20 \FileSystem\FltMgr ffff9689166bfe70 > ffff968918887950 \FileSystem\Ntfs 00000000 Ntfs Processed 2 device objects.
接下来注册:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 CONST FLT_REGISTRATION FilterRegistration = { sizeof (FLT_REGISTRATION), FLT_REGISTRATION_VERSION, 0 , nullptr , Callbacks, DelProtectUnload, DelProtectInstanceSetup, DelProtectInstanceQueryTeardown, DelProtectInstanceTeardownStart, DelProtectInstanceTeardownComplete, }; PFLT_FILTER FilterHandle; NTSTATUS DriverEntry (_In_ PDRIVER_OBJECT DriverObject,_In_ PUNICODE_STRING RegistryPath) { NTSTATUS status; status=FltRegisterFilter (DriverObject,&FIlterRegistration,&FilterHandle); if (NT_SUCCESS (status)){ status=FltStartingFiltering (FilterHandle); if (!NT_SUCCESS (status)) FltUnregisterFilter (FilterHandle); }; return status; };
命名管道是一个单向或双向的通信机制,从一个服务器到一个或多个客户端,实现为一个文件系统npfs.sys。用CreateNamedPipe
创建一个命名管道服务器,可用CreateFile
加上“\\服务名\pipe\管道名”方式连接到服务器。邮件槽是个单向通信机制,实现为一个文件系统msfs.sys。用CreateMailslot
创建邮件槽,用“\\服务名\mailslot\邮件槽名”连接。
高度值不需要在注册时提供,在驱动安装时,高度值被写入注册表适当位置,如FileInfo小过滤驱动位置“HKEY_LOCAL_MACHIINE\SYSTEM\CurrentControlSet\Services\FileInfo\Instance\FileInfo\Altitude”。为得到一个适当高度,驱动发行者需要给fsfcomm@microsoft.com发送一封Email,请求基于这个驱动面向的目标分配一个高度值。完整高度范围列表为https://learn.microsoft.com/zh-cn/windows-hardware/drivers/ifs/allocated-altitudes ,申请高度的邮件需要的详细信息有https://learn.microsoft.com/zh-cn/windows-hardware/drivers/ifs/minifilter-altitude-request 。
卸载回调函数 1 2 3 4 5 6 7 8 NTSTATUS NPUnload (__in FLT_FILTER_UNLOAD_FLAGS Flags) { UNREFERENCED_PARAMETER (Flags); PAGED_CODE (); PT_DBG_PRINT (PTDBG_TRACE_ROUTINES, ("NPminifilter!NPUnload: Entered\n" )); FltCloseCommunicationPort (gServerPort); FltUnregisterFilter (gFilterHandle); return STATUS_SUCCESS; };
预操作回调函数 第一个参数FLT_CALLBACK_DATA为回调数据包,包含请求相关全部信息,所以不用直接读取IRP信息了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 FLT_PREOP_CALLBACK_STATUS NPPreCreate (__inout PFLT_CALLBACK_DATA Data, __in PCFLT_RELATED_OBJECTS FltObjects, __deref_out_opt PVOID* CompletionContext) { char FileName[260 ] = "X:" ; NTSTATUS status; PFLT_FILE_NAME_INFORMATION nameInfo; UNREFERENCED_PARAMETER (FltObjects); UNREFERENCED_PARAMETER (CompletionContext); PAGED_CODE (); __try { status = FltGetFileNameInformation (Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &nameInfo); if (NT_SUCCESS (status)) { if (gCommand == ENUM_BLOCK) { FltParseFileNameInformation (nameInfo); if (NPUnicodeStringToChar (&nameInfo->Name, FileName)) if (strstr (FileName, "NOTEPAD.EXE" ) > 0 ) { Data->IoStatus.Status = STATUS_ACCESS_DENIED; Data->IoStatus.Information = 0 ; FltReleaseFileNameInformation (nameInfo); return FLT_PREOP_COMPLETE; }; }; FltReleaseFileNameInformation (nameInfo); }; } __except (EXCEPTION_EXECUTE_HANDLER) { DbgPrint ("NPPreCreate EXCEPTION_EXECUTE_HANDLER\n" ); }; return FLT_PREOP_SUCCESS_WITH_CALLBACK; };
其中回调数据包Data参数的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 typedef struct _FLT_CALLBACK_DATA { FLT_CALLBACK_DATA_FLAGS Flags; PETHREAD CONST Thread; PFLT_IO_PARAMETER_BLOCK CONST Iopb; IO_STATUS_BLOCK IoStatus; struct _FLT_TAG_DATA_BUFFER *TagData; union { struct { LIST_ENTRY QueueLinks; PVOID QueueContext[2 ]; }; PVOID FilterContext[4 ]; }; KPROCESSOR_MODE RequestorMode; } FLT_CALLBACK_DATA, *PFLT_CALLBACK_DATA;
Flags标志可以为:
标志
含义
FLTFL_CALLBACK_DATA_DIRTY
驱动修改了该结构并调用了FltSetCallbackDataDirty
,该结构除Thread和RequestMode都能改。
FLTFL_CALLBACK_DATA_FAST_IO_OPERATION
这是个快速I/O操作
FLTFL_CALLBACK_DATA_IRP_OPERATION
这是个基于IRP的操作
FLTFL_CALLBACK_DATA_GENERATED_IO
这是由另一个小过滤驱动生成的操作
FLTFL_CALLBACK_DATA_POST_OPERATION
这是一个操作后回调
注意Iopb域,这里有我们需要的信息:
1 2 3 4 5 6 7 8 9 10 typedef struct _FLT_IO_PARAMETER_BLOCK { ULONG IrpFlags; UCHAR MajorFunction; UCHAR MinorFunction; UCHAR OperationFlags; UCHAR Reserved; PFILE_OBJECT TargetFileObject; PFLT_INSTANCE TargetInstance; FLT_PARAMETERS Parameters; } FLT_IO_PARAMETER_BLOCK, *PFLT_IO_PARAMETER_BLOCK;
这里包括主功能号、次功能号、文件对象指针等,还有个FLT_PARAMETERS参数域,这个域根据不同主功能号而变化,比如写请求的写入位置、长度、缓冲区等参数,这个太长了,在fltkernel.h中自己去看吧。
操作预回调常用返回值有:
返回值
含义
FLT_PREOP_COMPLETE
完成操作,不调用操作后回调,不将请求转发给下层小过滤驱动
FLT_PREOP_SUCCESS_NO_CALLBACK
完成当前请求,允许继续传递到下一个过滤器,不调用操作后回调
FLT_PREOP_SUCCESS_WITH_CALLBACK
同上,但调用操作后回调
FLT_PREOP_PENDING
驱动挂起该请求并不继续处理,直到驱动调用FltCompletePendedPreOperation
为止
FLT_PREOP_SYNCHRONIZE
类似FLT_PREOP_SUCCESS_WITH_CALLBACK,但驱动请求过滤管理器在同一线程中调用操作后回调,IRQL≤APC_LEVEL。
操作前回调的第二个参数类型:
1 2 3 4 5 6 7 8 9 typedef struct _FLT_RELATED_OBJECTS { USHORT CONST Size; USHORT CONST TransactionContext; PFLT_FILTER CONST Filter; PFLT_VOLUME CONST Volume; PFLT_INSTANCE CONST Instance; PFILE_OBJECT CONST FileObject; PKTRANSACTION CONST Transaction; } FLT_RELATED_OBJECTS, *PFLT_RELATED_OBJECTS;
取得一个文件或目录的文件名信息结构用FltGetFileNameInformation
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 NTSTATUS FltGetFileNameInformation ( IN PFLT_CALLBACK_DATA CallbackData, IN FLT_FILE_NAME_OPTIONS NameOptions, OUT PFLT_FILE_NAME_INFORMATION *FileNameInformation ) ;typedef struct _FLT_FILE_NAME_INFORMATION { USHORT Size; FLT_FILE_NAME_PARSED_FLAGS NamesParsed; FLT_FILE_NAME_OPTIONS Format; UNICODE_STRING Name; UNICODE_STRING Volume; UNICODE_STRING Share; UNICODE_STRING Extension; UNICODE_STRING Stream; UNICODE_STRING FinalComponent; UNICODE_STRING ParentDir;
上述FileNameInformation需要用FltReleaseFileNameInformation
释放。
NTFS中,文件数据是$DATA流(默认流),也是保存在同一文件里。资源管理器等工具不会查看别的流,GetFileSize
等标准API也不返回。但可用DeleteFile
删除、用FindFirstStream
和FindNextStream
枚举。
FltGetFileNameInformation
只填充上面结构的Name字段,FltParseFileNameInformation
用来将该结构Name拆成多个成员,获取含有路径名称与文件名的结构:
1 2 3 NTSTATUS FltParseFileNameInformation ( IN OUT PFLT_FILE_NAME_INFORMATION FileNameInformation ) ;
此时可以对FLT_FILE_NAME_INFORMATION做一个RAII包装器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 enum class FileNameOptions { Normalized = FLT_FILE_NAME_NORMALIZED, Opened = FLT_FILE_NAME_OPENED, Short = FLT_FILE_NAME_SHORT, QueryDefault = FLT_FILE_NAME_QUERY_DEFAULT, QueryCacheOnly = FLT_FILE_NAME_QUERY_CACHE_ONLY, QueryFileSystemOnly = FLT_FILE_NAME_QUERY_FILESYSTEM_ONLY, RequestFromCurrentProvider = FLT_FILE_NAME_REQUEST_FROM_CURRENT_PROVIDER, DoNotCache = FLT_FILE_NAME_DO_NOT_CACHE, AllowQueryOnReparse = FLT_FILE_NAME_ALLOW_QUERY_ON_REPARSE }; DEFINE_ENUM_FLAG_OPERATORS (FileNameOptions);struct FilterFileNameInformation { FilterFileNameInformation (PFLT_CALLBACK_DATA data, FileNameOptions options = FileNameOptions::QueryDefault | FileNameOptions::Normalized); ~FilterFileNameInformation (); operator bool () const { return _info != nullptr ; } PFLT_FILE_NAME_INFORMATION Get () const { return _info; } operator PFLT_FILE_NAME_INFORMATION () const { return Get (); } PFLT_FILE_NAME_INFORMATION operator ->() { return _info; } NTSTATUS Parse () ; private : PFLT_FILE_NAME_INFORMATION _info; }; FilterFileNameInformation::FilterFileNameInformation (PFLT_CALLBACK_DATA data, FileNameOptions options) { auto status = FltGetFileNameInformation (data, (FLT_FILE_NAME_OPTIONS)options, &_info); if (!NT_SUCCESS (status)) _info = nullptr ; } FilterFileNameInformation::~FilterFileNameInformation () { if (_info) FltReleaseFileNameInformation (_info); } NTSTATUS FilterFileNameInformation::Parse () { return FltParseFileNameInformation (_info); } FilterFileNameInformation nameInfo (Data) ;if (nameinfo){ if (NT_SUCCESS (nameInfo.Parse ())){ KdPrint (("%wZ\n" ,&nameInfo->FinalComponent)); } }
后操作回调函数 IRQL过高时,驱动不能访问分页内存、不能使用某些内核API、不能获取同步原语(如互斥量、快速互斥量、执行体资源、信号量、事件等,但可以获取自旋锁)、不能设置、取得或删除上下文(可释放上下文)。
当需要推迟到一个在IRQL低于DISPATCH_LEVEL的例程时,有两种途径:
驱动用FltDoCompletionProcessingWhenSafe
设置一个回调,降到DISPATCH_LEVEL后调用。但这方法不能用于IRP_MJ_READ、IRP_MJ_WRITE或IRP_MJ_FLUSH_BUFFERS,若这些操作在下层被同步完成则可能引起死锁。这方法只能在基于IRP的操作中使用,可用FLT_IS_IRP_OPERATION
检查。
驱动用FltQueueDeferredIoWorkItem
发送一个工作项并排队,最终在PASSIVE_LEVEL执行。工作项回调中最终调用FltCompletePendedPostOperation
以通知过滤管理器操作后回调已完成。
操作后回调返回FLT_POSTOP_FINISHED_PROCESSING表示驱动已完成该操作。若驱动需要在工作项的高IRQL上执行某些工作,可返回FLT_POSTOP_MORE_PROCESSING_REQUIRED通知过滤管理器操作处于等待完成阶段,直到工作项中用FltCompletePendedPostOperation
通知过滤管理器可继续处理请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 FLT_POSTOP_CALLBACK_STATUS NPPostCreate (__inout PFLT_CALLBACK_DATA Data, __in PCFLT_RELATED_OBJECTS FltObjects, __in_opt PVOID CompletionContext, __in FLT_POST_OPERATION_FLAGS Flags) { FLT_POSTOP_CALLBACK_STATUS returnStatus = FLT_POSTOP_FINISHED_PROCESSING; PFLT_FILE_NAME_INFORMATION nameInfo; NTSTATUS status; UNREFERENCED_PARAMETER (CompletionContext); UNREFERENCED_PARAMETER (Flags); if (!NT_SUCCESS (Data->IoStatus.Status) || (STATUS_REPARSE == Data->IoStatus.Status)) return FLT_POSTOP_FINISHED_PROCESSING; status = FltGetFileNameInformation (Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &nameInfo); if (!NT_SUCCESS (status)) return FLT_POSTOP_FINISHED_PROCESSING; return returnStatus; };
其他回调函数 一般这些回调函数都不需要实现什么功能,可直接在微过滤器注册结构中设置为NULL即可。
InstanceSetupCallback 让开发者决定哪个卷需要绑定,哪个卷不需要绑定。这个回调函数在下列情况下被调用:
当一个微过滤器加载时,每个存在的卷都会导致这个调用。
当一个新的卷被挂载时。
当FltAttachVolume
被内核模式调用时。
当FltAttachVolumeAtAltitude
被内核模式调用时。
当FilterAttach
被用户模式调用时。
当FilterAttachAtAltitude
被用户模式调用时。
例子:
1 2 3 4 5 6 7 8 9 NTSTATUS NPInstanceSetup (__in PCFLT_RELATED_OBJECTS FltObjects, __in FLT_INSTANCE_SETUP_FLAGS Flags, __in DEVICE_TYPE VolumeDeviceType, __in FLT_FILESYSTEM_TYPE VolumeFilesystemType) { UNREFERENCED_PARAMETER (FltObjects); UNREFERENCED_PARAMETER (Flags); UNREFERENCED_PARAMETER (VolumeDeviceType); UNREFERENCED_PARAMETER (VolumeFilesystemType); PAGED_CODE (); PT_DBG_PRINT (PTDBG_TRACE_ROUTINES, ("NPminifilter!NPInstanceSetup: Entered\n" )); return STATUS_SUCCESS; };
其中FltObjects结构含有指向微过滤器、卷和实例的指针,这个实例是将要在该函数内生成的实例。
Flags标记是什么操作出发了这个函数:
FLTFL_INSTANCE_SETUP_AUTOMATIC_ATTACHMENT:一个微过滤器注册时自动的绑定通知。
FLTFL_INSTANCE_SETUP_MANUAL_ATTACHMENT:通过调用用户态FilterAttach
或用户态FilterAttachVolumeAtAltitude
或内核态FltAttachVolume
发起的手工请求。
FLTFL_INSTANCE_SETUP_NEWLY_MOUNTED_VOLUME:文件系统刚刚挂载了一个卷。
还通过得到VolumeDeviceType卷设备类型和VolumeFilesystemType卷文件系统类型来判断这个卷是否是过滤器所感兴趣的。微过滤器还可以用FltGetVolumeProperties
获取卷属性、用FltSetInstanceContext
在实例上设置上下文、在卷上打开关闭文件等。如果这个回调函数返回STATUS_SUCCESS或被设置为NULL时将这个实例绑定到卷上,如果返回警告或错误则不绑定。
InstanceQueryTeardownCallback 控制实例销毁函数,只有在一个手工解除绑定时被调用。手工解除绑定有两种可能:
内核模式调试FltDetachVolume
。
用户模式调试FilterDetach
。
如果过滤器不提供这个,不允许手工解除绑定,但卷和过滤器的卸载仍然可以。如果调用成功,那么InstanceTeardownStartCallback
和InstanceTeardownCompleteCallback
将会被调用。返回错误时手工解除绑定失败,推荐错误代码STATUS_FLT_DO_NOT_DETACH。
例子:
1 2 3 4 5 6 7 NTSTATUS NPInstanceQueryTeardown (__in PCFLT_RELATED_OBJECTS FltObjects, __in FLT_INSTANCE_QUERY_TEARDOWN_FLAGS Flags) { UNREFERENCED_PARAMETER (FltObjects); UNREFERENCED_PARAMETER (Flags); PAGED_CODE (); PT_DBG_PRINT (PTDBG_TRACE_ROUTINES, ("NPminifilter!NPInstanceQueryTeardown: Entered\n" )); return STATUS_SUCCESS; };
InstanceTeardownStartCallback 解除绑定回调函数,在决定解除绑定时完成这些东西:
重设所有未决I/O操作,包括预操作和后操作。
保证不会有新的I/O操作进入未决。
对刚刚到达的操作开始最少的工作。
关闭所有打开的文件。
取消所有本过滤器发起的I/O请求。
停止将新的工作任务排队。
该函数完成后微过滤器把控制权交还给过滤管理器来继续它的销毁过程。
例子:
1 2 3 4 5 6 7 VOID NPInstanceTeardownStart (__in PCFLT_RELATED_OBJECTS FltObjects, __in FLT_INSTANCE_TEARDOWN_FLAGS Flags) { UNREFERENCED_PARAMETER (FltObjects); UNREFERENCED_PARAMETER (Flags); PAGED_CODE (); PT_DBG_PRINT (PTDBG_TRACE_ROUTINES, ("NPminifilter!NPInstanceTeardownStart: Entered\n" )); return ; };
FltObjects同上,有微过滤器、卷和实例。
Reason指明这次销毁原因,可能是以下标记的组合:
FLTFL_INSTANCE_TEARDOWN_MANUAL:这次销毁操作是一个手工的请求,即FilterDetach
或FltDetachVolume
。
FLTFL_INSTANCE_TEARDOWN_FILTER_UNLOAD:微过滤器执行卸载或选择把卸载请求失败掉导致的。
FLTFL_INSTANCE_TEARDOWN_MANDATORY_FILTER_UNLOAD:强制卸载导致的,不能把卸载请求失败掉。
FLTFL_INSTANCE_TEARDOWN_VOLUME_DISMOUNT:因为一个卷被解除挂载。
FLTFL_INSTANCE_TEARDOWN_INTERNAL_ERROR:安装实例时内部错误导致的,如内存不足。
该回调函数不能失败,过滤管理器保证运行在Passive中断级。
InstanceTeardownCompleteCallback 实例解除绑定完成函数。当所有与这个实例相关的操作都排除干净或完成时,该函数被调用。过滤管理器保证此时此实例存在的所有操作回调都完成了,这时微过滤器必须关闭这个实例打开的所有文件。
函数原型完全同上InstanceTeardownStart
,例子:
1 2 3 4 5 6 7 VOID NPInstanceTeardownComplete (__in PCFLT_RELATED_OBJECTS FltObjects, __in FLT_INSTANCE_TEARDOWN_FLAGS Flags) { UNREFERENCED_PARAMETER (FltObjects); UNREFERENCED_PARAMETER (Flags); PAGED_CODE (); PT_DBG_PRINT (PTDBG_TRACE_ROUTINES, ("NPminifilter!NPInstanceTeardownComplete: Entered\n" )); return ; };
该回调函数不能失败,过滤管理器保证运行在Passive中断级。
上下文 微过滤驱动提供上下文结构,将一些数据附加到文件系统实体上,如卷和文件,能被任何文件系统对象设置和取得。
用FLT_CONTEXT_REGISTRATION声明驱动需要什么样的上下文,以及用于哪种类型对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 typedef struct _FLT_CONTEXT_REGISTRATION { FLT_CONTEXT_TYPE ContextType; FLT_CONTEXT_REGISTRATION_FLAGS Flags; PFLT_CONTEXT_CLEANUP_CALLBACK ContextCleanupCallback; SIZE_T Size; ULONG PoolTag; PFLT_CONTEXT_ALLOCATE_CALLBACK ContextAllocateCallback; PFLT_CONTEXT_FREE_CALLBACK ContextFreeCallback; PVOID Reserved1; } FLT_CONTEXT_REGISTRATION, *PFLT_CONTEXT_REGISTRATION; typedef USHORT FLT_CONTEXT_TYPE; #define FLT_VOLUME_CONTEXT 0x0001 #define FLT_INSTANCE_CONTEXT 0x0002 #define FLT_FILE_CONTEXT 0x0004 #define FLT_STREAM_CONTEXT 0x0008 #define FLT_STREAMHANDLE_CONTEXT 0x0010 #define FLT_TRANSACTION_CONTEXT 0x0020 #if FLT_MGR_WIN8 #define FLT_SECTION_CONTEXT 0x0040 #endif #define FLT_CONTEXT_END 0xffff
上下文的大小可在Size字段中指定,也可设为FLT_VARIABLE_SIZED_CONTEXTS使用可变大小。
一个构建上下文注册结构数组的例子:
1 2 3 4 5 6 7 8 9 struct FileContext { Mutex Lock; UNICODE_STRING FileName; BOOLEAN Written; }; const FLT_CONTEXT_REGISTRATION Contexts[] = { { FLT_FILE_CONTEXT, 0 , nullptr , sizeof (FileContext), DRIVER_CONTEXT_TAG }, { FLT_CONTEXT_END } };
用FltAllocateContext
分配:
1 2 3 4 5 6 7 NTSTATUS FltAllocateContet ( _In_ PFLT_FILTER Filter, _In_ FLT_CONTEXT_TYPE ContextType, _In_ SIZE_T ContextSize, _In_ POOL_TYPE PoolType, _Outptr_ PFLT_CONTEXT* ReturnedContext ) ;
上下文分配完成后,驱动可在其中保存任何想要的数据,然后驱动必须将上下文附加到一个对象上,此时需要调用的为FltSetXxxContext
。其中“Xxx”可以是File、Instance、Volume、Stream、StreamHandle或Transaction中的一个。节上下文需要用FltCreateSectionForDataScan
设置。这些函数构造形式相同,以File的举例:
1 2 3 4 5 6 7 NTSTATUS FltSetFileContext ( _In_ PFLT_INSTANCE Instance, _In_ PFILE_OBJECT FileObject, _In_ FLT_SET_CONTEXT_OPERATION Operation, _In_ PFLT_CONTEXT NewContext, _Outptr_ PFLT_CONTEXT* OldContext ) ;
上下文是引用计数的,分配和设置上下文都增加引用计数。FltReleaseContext
较少引用计数。一般不用FltDeleteContext
手动删除上下文,一般等对应文件系统对象被销毁时自动被过滤管理器删除。
别的回调想获取该上下文时,用FltGetXxxContext
,这里“Xxx”可以是File、Instance、Volume、Stream、StreamHandle、Transaction或Section之一,并将上下文引用计数加一,用完后要调用FltReleaseContext
。
I/O请求 一般内核代码用ZwCreateFile
等打开文件句柄,用ZwReadFile
、ZwWriteFile
和ZwDeviceIoControlFile
等进行I/O操作。但对于微过滤驱动的回调中进行I/O操作,用上面这方法则I/O操作从最顶端过滤驱动往下朝着文件系统本身方向进行,半路会遇到当前小过滤驱动本身,造成重入。此时微过滤驱动直接用过滤管理器例程进行I/O操作,发送到下一层过滤驱动。如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 NTSTATUS FltCreateFile ( _In_ FLT_FILTER Filter, _In_opt_ PFLT_INSTANCE Instance, _Out_ PHANDLE FileHandle, _In_ ACCESS_MASK DesiredAccess, _In_ POBJECT_ATTRIBUTES ObjectAttributes, _Out_ PIO_STATUS_BLOCK IoStatusBlock, _In_opt_ PLARGE_INTEGER AllocationSize, _In_ ULONG FileAttributes, _In_ ULONG ShareAccess, _In_ ULONG CreateDisposition, _In_ ULONG CreateOptions, _In_reads_bytes_opt_(EaLength) PVOID EaBuffer, _In_ ULONG EaLength, _In_ ULONG Flags ) ;
然后驱动那这返回的句柄去用标准I/O的API,如ZwReadFile
和ZwWriteFile
等,这些仍只针对下面的驱动层次。另一种方法是用FltCreateFileEx
或FltCreateFileEx2
返回的FILE_OBJECT调用FltReadFile
或FltWriteFile
。
操作完成后要将返回的句柄用FltClose
,若文件对象也返回了,也要用ObDeferenceObject
减少引用计数。
Minifilter与应用通信 建立通信端口 之前用DeviceIoControl
方式,但这样必须由用户模式客户程序初始化通信,若驱动想主动发送给客户程序则无法做到。
微过滤驱动用FltCreateCommunicationPort
创建一个过滤器通信端口,为来自客户程序的连接和消息注册回调。用户模式客户程序用FilterConnectCommunicationPort
连接到这个端口,得到端口的一个句柄。定义如下:
1 2 3 4 5 6 7 8 9 10 NTSTATUS FltCreateCommunicationPort ( _In_ PFLT_FILTER Filter, _Outptr_ PFLT_PORT* ServerPort, _In_ POBJECT_ATTRIBUTES ObjectAttributes, _In_opt_ PVOID ServerPortCookie, _In_ PFLT_CONNECT_NOTIFY ConnectNotifyCallback, _In_ PFLT_DISCONNECT_NOTIFY DisconnectNotifyCallback, _In_opt_ PFLT_MESSAGE_NOTIFY MessageNotifyCallback, _In_ LONG MaxConnections ) ;
微过滤驱动用FltSendMessage
想用户模式客户程序发送一条消息,用户模式客户程序用FilterGetMessage
等待消息到达,或用FilterSendMessage
给驱动发送消息。若驱动期望等到一个回应,用户模式客户程序用FilterReplyMessage
发送回应。
建立通信端口的方法如下,安全描述符可用FltBuildDefaultSecurityDescriptor
创建:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 NTSTATUS DriverEntry (_In_ PDRIVER_OBJECT DriverObject,_In_ PUNICODE_STRING RegistryPath) { NTSTATUS status; UNREFERENCED_PARAMETER (RegistryPath); PT_DBG_PRINT (PTDBG_TRACE_ROUTINES,("FileBackup!DriverEntry: Entered\n" )); status = FltRegisterFilter (DriverObject,&FilterRegistration,&gFilterHandle); FLT_ASSERT (NT_SUCCESS (status)); if (!NT_SUCCESS (status)) return status; do { UNICODE_STRING name = RTL_CONSTANT_STRING (L"\\FileBackupPort" ); PSECURITY_DESCRIPTOR sd; status = FltBuildDefaultSecurityDescriptor (&sd, FLT_PORT_ALL_ACCESS); if (!NT_SUCCESS (status)) break ; OBJECT_ATTRIBUTES attr; InitializeObjectAttributes (&attr, &name, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, nullptr , sd); status = FltCreateCommunicationPort (gFilterHandle, &FilterPort, &attr, nullptr , PortConnectNotify, PortDisconnectNotify, PortMessageNotify, 1 ); FltFreeSecurityDescriptor (sd); if (!NT_SUCCESS (status)) break ; status = FltStartFiltering (gFilterHandle); } while (false ); if (!NT_SUCCESS (status)) FltUnregisterFilter (gFilterHandle); return status; }
用户模式客户程序用FilterConnectCommunicationPort
连接一个打开了的端口:
1 2 3 4 5 6 7 8 HRESULT WINAPI FilterConnectCommunicationPort ( IN LPCWSTR lpPortName, IN DWORD dwOptions, IN OPTIONAL LPVOID lpContext, IN DWORD dwSizeOfContext, IN OPTIONAL LPSECURITY_ATTRIBUTES lpSecurityAttributes, OUT PHANDLE hPort ) ;
FilterConnectCommunicationPort
会引起驱动的客户连接通知回调,回调函数原型如下。若驱动同意接受客户程序连接,则返回STATUS_SUCCESS,否则客户程序在FilterConnectCommunicationPort
返回值得到一个失败HRESULT。当该API调用成功,客户程序便可以与驱动通信。
1 2 3 4 5 6 7 NTSTATUS PortConnectNotify ( _In_ PFLT_PORT ClientPort, _In_opt_ PVOID ServerPortCookie, _In_reads_bytes_opt_(SizeOfContext) PVOID ConnectionContext, _In_ ULONG SizeOfContext, _Outptr_result_maybenull_ PVOID* ConnectionPortCookie ) ;
微过滤驱动用FltSendMessage
向客户程序发消息:
1 2 3 4 5 6 7 8 9 NTSTATUS FLTAPI FltSendMessage ( _In_ PFLT_FILTER Filter, _In_ PFLT_PORT* ClientPort, _In_ PVOID SenderBuffer, _In_ ULONG SenderBufferLength, _Out_ PVOID ReplyBuffer, _Inout_opt_ PULONG ReplyLength, _In_opt_ PLARGE_INTEGER Timeout ) ;
客户程序接收到的缓冲区有个FILTER_MESSAGE_HEADER结构,后面跟着驱动发送的实际数据:
1 2 3 4 typedef struct _FILTER_MESSAGE_HEADER { ULONG ReplyLength, ULONGLONG MessageId; } FILTER_MESSAGE_HEADER,*PFILTER_MESSAGE_HEADER;
WDK定义的FilterSendMessage
原型:
1 2 3 4 5 6 7 8 HRESULT WINAPI FilterSendMessage ( IN HANDLE hPort, IN OPTIONAL LPVOID lpInBuffer, IN DWORD dwInBufferSize, IN OUT OPTIONAL LPVOID lpOutBuffer, IN DWORD dwOutBufferSize, OUT LPDWORD lpBytesReturned ) ;
通过DLL使用通信端口 一般情况下,编写一个DLL专门用来与内核层Minifilter驱动程序通信,应用程序调用这个与内核通信。其中编写这个DLL时需要包含WDK头文件FltUser.h,还要搞到fltLib.lib和fltMgr.lib。
NPdll.h中的一些声明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include "windows.h" #include <stdio.h> #include <FltUser.h> #pragma comment(lib, "user32.lib" ) #pragma comment(lib, "kernel32.lib" ) #pragma comment(lib, "fltLib.lib" ) #pragma comment(lib, "fltMgr.lib" ) #pragma comment(lib, "ntoskrnl.lib" ) #pragma comment(lib, "hal.lib" ) extern HANDLE g_hPort; #define NPMINI_NAME L"NPminifilter" #define NPMINI_PORT_NAME L"\\NPMiniPort" __declspec(dllexport) int InitialCommunicationPort (VOID) ; __declspec(dllexport) int NPSendMessage (PVOID InputBuffer) ; typedef enum _NPMINI_COMMAND { ENUM_PASS = 0 , ENUM_BLOCK } NPMINI_COMMAND; typedef struct _COMMAND_MESSAGE { NPMINI_COMMAND Command; } COMMAND_MESSAGE, *PCOMMAND_MESSAGE;
DLL主程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include "NPdll.h" HANDLE g_hPort = INVALID_HANDLE_VALUE; #ifdef _MANAGED #pragma managed(push, off) #endif BOOL APIENTRY DllMain (HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: InitialCommunicationPort (); case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break ; }; return TRUE; }; #ifdef _MANAGED #pragma managed(pop) #endif int InitialCommunicationPort (VOID) { DWORD hResult = FilterConnectCommunicationPort (NPMINI_PORT_NAME, 0 , NULL , 0 , NULL , &g_hPort); if (hResult != S_OK) return hResult; return 0 ; }; int NPSendMessage (PVOID InputBuffer) { DWORD bytesReturned = 0 ; DWORD hResult = 0 ; PCOMMAND_MESSAGE commandMessage = (PCOMMAND_MESSAGE)InputBuffer; hResult = FilterSendMessage (g_hPort, commandMessage, sizeof (COMMAND_MESSAGE), NULL , NULL , &bytesReturned); if (hResult != S_OK) return hResult; return 0 ; };
应用层的编写 NPapp.c:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #include "NPapp.h" #include <iostream> #include <tchar.h> using namespace std;int (__stdcall* pNPSendMessage)(PVOID pInBuffer);int (__stdcall* pInitialCommunicationPort)(VOID);CNPApp::CNPApp () { m_hModule = NULL ; LoadNPminifilterDll (); }; CNPApp::~CNPApp () { if (m_hModule) FreeLibrary (m_hModule); }; BOOL CNPApp::LoadNPminifilterDll (VOID) { m_hModule = LoadLibrary (TEXT ("minifilter_dll.dll" )); if (m_hModule != NULL ) { pNPSendMessage = (int (__stdcall*)(PVOID)) GetProcAddress (GetModuleHandle (TEXT ("minifilter_dll.dll" )), "NPSendMessage" ); if (!pNPSendMessage) return false ; return true ; }; return false ; }; VOID CNPApp::NPMessage (COMMAND_MESSAGE data) { if (m_hModule == NULL ) if (LoadNPminifilterDll () == false ) return ; pNPSendMessage (&data); return ; }; int _tmain(int argc,_TCHAR* argv[]) { CNPApp ControlObj; char input; while (true ) { cout << "Enter 'a' for PASS MODE, 'b' for BLOCKMODE or 'q' to EXIT" << endl; cin >> input; if (input == 'a' || input == 'A' ) { COMMAND_MESSAGE data; data.Command = ENUM_PASS; ControlObj.NPMessage (data); printf ("==>NOTEPAD.EXE PASS MODE\n" ); } else if (input == 'b' || input == 'B' ) { COMMAND_MESSAGE data; data.Command = ENUM_BLOCK; ControlObj.NPMessage (data); printf ("==>NOTEPAD.EXE BLOCK MODE\n" ); } else if (input == 'q' || input == 'Q' ) { printf ("EXIT\n" ); break ; } else printf ("Wrong Parameter!!!\n" ); }; system ("pause" ); return 0 ; };
NPapp.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #pragma once #include "windows.h" #include <vector> #include <string> using namespace std;typedef enum _NPMINI_COMMAND { ENUM_PASS = 0 , ENUM_BLOCK } NPMINI_COMMAND; typedef struct _COMMAND_MESSAGE { NPMINI_COMMAND Command; } COMMAND_MESSAGE, * PCOMMAND_MESSAGE; class CNPApp {public : CNPApp (); virtual ~CNPApp (); VOID NPMessage (COMMAND_MESSAGE data) ; private : HINSTANCE m_hModule; BOOL LoadNPminifilterDll (VOID) ; };
Minifilter的安装 编译 要链接fltMgr.lib文件,方法是项目属性->链接器->输入->附加依赖项中加上fltMgr.lib。
编写INF文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 [Version] Signature = "$Windows NT$" Class = "ActivityMonitor" ClassGuid = {b86dff51-a31e-4 bac-b3cf-e8cfe75c9fc2} Provider = %Msft% DriverVer = 06 /16 /2007 ,1.0 .0.1 CatalogFile = NPminifilter.cat [DestinationDirs] DefaultDestDir = 12 MiniFilter.DriverFiles = 12 [DefaultInstall] OptionDesc = %ServiceDescription% CopyFiles = MiniFilter.DriverFiles[DefaultInstall.Services] AddService = %ServiceName%,,MiniFilter.Service [DefaultUninstall] DelFiles = MiniFilter.DriverFiles[DefaultUninstall.Services] DelService = %ServiceName%,0 x200 [MiniFilter.Service] DisplayName = %ServiceName%Description = %ServiceDescription%ServiceBinary = %12 %\%DriverName%.sys Dependencies = "FltMgr" ServiceType = 2 StartType = 3 ErrorControl = 1 LoadOrderGroup = "FSFilter Activity Monitor" AddReg = MiniFilter.AddRegistry [MiniFilter.AddRegistry] HKR,,"DebugFlags",0x00010001 ,0x0 HKR,,"SupportedFeatures",0x00010001,0x3 HKR,"Instances","DefaultInstance",0x00000000,%DefaultInstance% HKR,"Instances\"%Instance1.Name%,"Altitude",0x00000000,%Instance1.Altitude% HKR,"Instances\"%Instance1.Name%,"Flags",0x00010001,%Instance1.Flags% [MiniFilter.DriverFiles] %DriverName%.sys [SourceDisksFiles] NPminifilter.sys = 1 ,,[SourceDisksNames] 1 = %DiskId1%,,,[Strings] Msft = "Microsoft Corporation" ServiceDescription = "NPminifilter Mini-Filter Driver" ServiceName = "NPminifilter" DriverName = "NPminifilter" DiskId1 = "NPminifilter Device Installation Disk" DefaultInstance = "NPminifilter Instance" Instance1.Name = "NPminifilter Instance" Instance1.Altitude = "370030" Instance1.Flags = 0 x0
上述Class可在各个HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Class\GUID键值\Class处找到,也可用 https://github.com/zodiacon/AllTools 中的FSClass工具查看系统中存在的所有类别。
对于DestinationDirs节的目录魔数,完整有https://learn.microsoft.com/zh-cn/windows-hardware/drivers/install/using-dirids 。当这些数字作为路径一部分时,需要百分号括起来,如%10%\Config。
一个驱动程序包应至少包含SYS、INF和CAT文件。右键INF安装这个驱动,该.sys便安装到%windir%\system32\drivers下,可用OSR Driver Loader检查已安装的Minifilter在系统上的Load Group顺序。该安装过程实际用了InstallHInfFile
。
启动安装后的Minifilter 在INF的目录下敲命令:
第一种:
1 2 net start 驱动名称 net stop 驱动名称
第二种:
1 2 3 4 5 fltmc fltmc load 驱动名称 fltmc unload 驱动名称 fltmc instances fltmc volumes
文件驱动和过滤驱动创建在对象管理器名字空间的FileSystem目录下,WinObj能看。
调试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 1: kd> .load fltkd //加载fltkd.dll调试扩展 2: kd> !help //命令列表 Filter Manager Debugger Extensions: For the following extensions, [detail] is a value from 0 to 1 which specifies the level of detail to show. The default detail level is 0. For the following extensions, [flags] allows you to control what information is displayed for the given object. The default flags is 0, so minimal information is shown. cbd [addr] [flags] [detail] Dump IRP_CTRL or CALLBACK_DATA where [flags] specify what information to dump: 0x00000001 Extended detail on each completion stack irpctrl [addr] [flags] [detail] Dump IRP_CTRL or CALLBACK_DATA where [flags] specify what information to dump: 0x00000001 Extended detail on each completion stack contextlist [addr] [detail] Dump CONTEXT_LIST_CTRL ctx [addr] [detail] Dump CONTEXT_NODE fileList [addr] [flags] [detail] Dump FILE_LIST_CTRL given fileObject or FileList address where [flags] specify what information to dump: 0x00000001 File list ctrl's file context info filter [addr] [flags] [detail] Dump FLT_FILTER where [flags] specify what information to dump: 0x00000001 Basic filter information 0x00000002 Filter's context registration information 0x00000004 Filter's context usage information 0x00000008 Filter's object usage/reference information 0x00000010 Filter's Verifier information 0x00000020 Filter's Port information filters [flags] [detail] Dumps all the filters registered in the system where [flags] specify what information to dump: 0x00000001 Basic filter information 0x00000002 Filter's context registration information 0x00000004 Filter's context usage information 0x00000008 Filter's object usage/reference information 0x00000010 Filter's Verifier information 0x00000020 Filter's Port information fltobj [addr] [detail] Dump FLT_OBJECT where [flags] specify what information to dump: 0x00000001 Basic object information 0x00000002 Rundown log information on the object frame [addr] [flags] [detail] Dump FLTP_FRAME where [flags] specify what information to dump: 0x00000001 Basic frame information 0x00000002 Frame's PRCB information 0x00000004 Frame's VOLUME information frames [flags] [detail] Dumps all the frames in the system where [flags] specify what information to dump: 0x00000001 Basic frame information 0x00000002 Frame's PRCB information 0x00000004 Frame's VOLUME information instance [addr] [flags] [detail] Dump FLT_INSTANCE where [flags] specify what information to dump: 0x00000001 Basic instance information 0x00000002 Instance's context information 0x00000004 Instance's operation callbacks 0x00000008 Instance's completion node information msgq [addr] [flags] [detail] Dumps supplied message queue where [flags] specify what information to dump: 0x00000001 Extended detail on each message namecachelist [addr] [detail] Dump NAME_CACHE_LIST_CTRL oplock [addr] [flags] Dump oplock given address of owning FILE_OBJECT or stuck IRP (tells you what oplock it is waiting for) where [flags] specify what information to dump: 0x00000001 [addr] points to an OPLOCK (default is FILE_OBJECT or IRP) port [addr] Dumps connection port portlist [filter object addr] [flags] [detail] Dumps all port connections of specified filter where [flags] specify what information to dump: 0x00000001 Extended detail on each port relobjs [addr] Dump IRP_CTRL tree [addr] [detail] Dump tree streamList [addr] [flags] [detail] Dump STREAM_LIST_CTRL given fileObject or StreamList address where [flags] specify what information to dump: 0x00000001 Stream list ctrl's stream context info 0x00000002 Stream list ctrl's stream handle context info 0x00000004 Stream list ctrl's normalized name info 0x00000008 Stream list ctrl's opened name info 0x00000010 Stream list ctrl's short name info 0x00000020 Stream list ctrl's section context info traceflags [flags] Sets trace flags. Displays current setting if not supplied tracelevel [level] Sets trace level. Displays current setting if not supplied volume [addr] [flags] [detail] Dump FLT_VOLUME given deviceObject or FltVolume address where [flags] specify what information to dump: 0x00000001 Basic volume information 0x00000002 Volume's context information 0x00000004 Volume's operation callbacks 0x00000008 Volume's name cache information 0x00000010 Volume's stream list control information 0x00000020 Volume's file list control information volumes [flags] [detail] Dump all the volumes to which the filter manager is attached where [flags] specify what information to dump: 0x00000001 Basic volume information 0x00000002 Volume's context information 0x00000004 Volume's operation callbacks 0x00000008 Volume's name cache information 0x00000010 Volume's stream list control information 0x00000020 Volume's file list control information work [detail] Dump Throttled worker queue information stats Dumps statistics 1: kd> !filters //所有已装入微过滤驱动 Filter List: ffff8c8e719170d0 "Frame 0" FLT_FILTER: ffff8c8e738844d0 "wcifs" "189900" FLT_FILTER: ffff8c8e7373d530 "CldFlt" "180451" FLT_FILTER: ffff8c8e7208c260 "FileCrypt" "141100" FLT_FILTER: ffff8c8e720d65b0 "npsvctrig" "46000" FLT_INSTANCE: ffff8c8e7230c9f0 "npsvctrig" "46000" FLT_FILTER: ffff8c8e73db98a0 "Wof" "40700" FLT_INSTANCE: ffff8c8e720629a0 "Wof Instance" "40700" FLT_FILTER: ffff8c8e73db88a0 "FileInfo" "40500" FLT_INSTANCE: ffff8c8e720028e0 "FileInfo" "40500" FLT_INSTANCE: ffff8c8e720619a0 "FileInfo" "40500" FLT_INSTANCE: ffff8c8e72311010 "FileInfo" "40500" 0: kd> !filter ffff8c8e73db88a0 //指定地址微过滤驱动信息 FLT_FILTER: ffff8c8e73db88a0 "FileInfo" "40500" FLT_OBJECT: ffff8c8e73db88a0 [02000000] Filter RundownRef : 0x0000000000003800 (7168) PointerCount : 0x00000001 PrimaryLink : [ffff8c8e719170d0-ffff8c8e73db98b0] Frame : ffff8c8e71917020 "Frame 0" Flags : [00000032] FilteringInitiated +30!! DriverObject : ffff8c8e73db78f0 FilterLink : [ffff8c8e719170d0-ffff8c8e73db98b0] PreVolumeMount : 0000000000000000 (null) PostVolumeMount : 0000000000000000 (null) FilterUnload : fffff8066af23e30 fileinfo!FIUnload InstanceSetup : 0000000000000000 (null) InstanceQueryTeardown : 0000000000000000 (null) InstanceTeardownStart : 0000000000000000 (null) InstanceTeardownComplete : 0000000000000000 (null) ActiveOpens : (ffff8c8e73db8a58) mCount=0 Communication Port List : (ffff8c8e73db8aa8) mCount=0 Client Port List : (ffff8c8e73db8af8) mCount=0 VerifierExtension : 0000000000000000 Operations : ffff8c8e73db8b50 OldDriverUnload : 0000000000000000 (null) SupportedContexts : (ffff8c8e73db89d0) VolumeContexts : (ffff8c8e73db89d0) InstanceContexts : (ffff8c8e73db89d8) ALLOCATE_CONTEXT_NODE: ffff8c8e73db7ab0 "FileInfo" [01] LookasideList (size=672) FileContexts : (ffff8c8e73db89e0) StreamContexts : (ffff8c8e73db89e8) ALLOCATE_CONTEXT_NODE: ffff8c8e73db7bf0 "FileInfo" [01] LookasideList (size=80) StreamHandleContexts : (ffff8c8e73db89f0) TransactionContext : (ffff8c8e73db89f8) (null) : (ffff8c8e73db8a00) InstanceList : (ffff8c8e73db8908) FLT_INSTANCE: ffff8c8e720028e0 "FileInfo" "40500" FLT_INSTANCE: ffff8c8e720619a0 "FileInfo" "40500" FLT_INSTANCE: ffff8c8e72311010 "FileInfo" "40500" 1: kd> !portlist ffff8c8e73db88a0 //指定过滤服务器端口列表 FLT_FILTER: ffff8c8e73db88a0 Client Port List : Mutex (ffff8c8e73db8af8) List [ffff8c8e73db8b30-ffff8c8e73db8b30] mCount=0 2: kd> !port ffff8c8e73db8af8 //指定客户端口信息 FLT_PORT_OBJECT: ffff8c8e73db8af8 FilterLink : [0000000000000001-0000000000000000] ServerPort : 0000000000000000 Cookie : 0000000000060001 Lock : (ffff8c8e73db8b20) MsgQ : (ffff8c8e73db8b58) NumEntries=897091712 Enabled MessageId : 0x0000000000000000 DisconnectEvent : (ffff8c8e73db8c30) Disconnected : FALSE 1: kd> !instance ffff8c8e7230c9f0 //指定地址实例信息 FLT_INSTANCE: ffff8c8e7230c9f0 "npsvctrig" "46000" FLT_OBJECT: ffff8c8e7230c9f0 [01000000] Instance RundownRef : 0x0000000000000000 (0) PointerCount : 0x00000002 PrimaryLink : [ffff8c8e72246118-ffff8c8e72246118] OperationRundownRef : ffff8c8e720ec680 Number : 4 PoolToFree : ffff8c8e722f3c00 OperationsRefs : ffff8c8e722f3c00 (18) PerProcessor Ref[0] : 0x0000000000000006 (3) PerProcessor Ref[1] : 0x000000000000000e (7) PerProcessor Ref[2] : 0x0000000000000012 (9) PerProcessor Ref[3] : 0xfffffffffffffffe (-1) Flags : [00000000] Volume : ffff8c8e72246010 "\Device\NamedPipe" Filter : ffff8c8e720d65b0 "npsvctrig" TrackCompletionNodes : ffff8c8e722f5280 ContextLock : (ffff8c8e7230ca70) Context : ffffe08dae046c70 CallbackNodes : (ffff8c8e7230ca90) VolumeLink : [ffff8c8e72246118-ffff8c8e72246118] FilterLink : [ffff8c8e720d6680-ffff8c8e720d6680] 2: kd> !volumes //所有卷对象 Volume List: ffff8c8e71917150 "Frame 0" FLT_VOLUME: ffff8c8e7200e010 "\Device\Mup" FLT_INSTANCE: ffff8c8e720028e0 "FileInfo" "40500" FLT_VOLUME: ffff8c8e72009010 "\Device\HarddiskVolume3" FLT_INSTANCE: ffff8c8e720629a0 "Wof Instance" "40700" FLT_INSTANCE: ffff8c8e720619a0 "FileInfo" "40500" FLT_VOLUME: ffff8c8e72246010 "\Device\NamedPipe" FLT_INSTANCE: ffff8c8e7230c9f0 "npsvctrig" "46000" FLT_VOLUME: ffff8c8e72245050 "\Device\Mailslot" FLT_VOLUME: ffff8c8e7222e010 "\Device\HarddiskVolume1" FLT_INSTANCE: ffff8c8e72311010 "FileInfo" "40500" 2: kd> !volume ffff8c8e7200e010 //指定地址的卷详细信息 FLT_VOLUME: ffff8c8e7200e010 "\Device\Mup" FLT_OBJECT: ffff8c8e7200e010 [04000000] Volume RundownRef : 0x0000000000000004 (2) PointerCount : 0x00000001 PrimaryLink : [ffff8c8e72009020-ffff8c8e71917150] Frame : ffff8c8e71917020 "Frame 0" Flags : [000000e5] NetworkFS SetupNotifyCalled EnableNameCaching FilterAttached StandardLinkInfoNotSupported FileSystemType : [0000000d] FLT_FSTYPE_MUP VolumeLink : [ffff8c8e72009020-ffff8c8e71917150] DeviceObject : ffff8c8e71fe12d0 DiskDeviceObject : 0000000000000000 FrameZeroVolume : ffff8c8e7200e010 VolumeInNextFrame : 0000000000000000 Guid : "" CDODeviceName : "\Device\Mup" CDODriverName : "\FileSystem\Mup" TargetedOpenCount : 0 Callbacks : (ffff8c8e7200e130) ContextLock : (ffff8c8e7200e518) VolumeContexts : (ffff8c8e7200e520) Count=0 StreamListCtrls : (ffff8c8e7200e528) rCount=1 FileListCtrls : (ffff8c8e7200e5a8) rCount=0 NameCacheCtrl : (ffff8c8e7200e628) InstanceList : (ffff8c8e7200e0b0) FLT_INSTANCE: ffff8c8e720028e0 "FileInfo" "40500"
实战 防cmd.exe删除文件 删除文件时有两种方法,一种是用IRP_MJ_SET_INFORMATION;另一种是FILE_DELETE_ON_CLOSE打开文件,所有句柄关闭后文件被删除。第二种方法可直接在用户模式将FILE_FLAG_DELETE_ON_CLOSE传入CreateFile
。所以此时修改FLT_OPERATION_REGISTRATION数组以支持这两种选择:
1 2 3 4 5 CONST FLT_OPERATION_REGISTRATION Callbacks[] = { { IRP_MJ_CREATE, 0 , DelProtectPreCreate, nullptr }, { IRP_MJ_SET_INFORMATION, 0 , DelProtectPreSetInformation, nullptr }, { IRP_MJ_OPERATION_END } };
创建前回调处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 extern "C" NTSTATUS ZwQueryInformationProcess ( _In_ HANDLE ProcessHandle, _In_ PROCESSINFOCLASS ProcessInformationClass, _Out_ PVOID ProcessInformation, _In_ ULONG ProcessInformationLength, _Out_opt_ PULONG ReturnLength ) ;_Use_decl_annotations_ FLT_PREOP_CALLBACK_STATUS DelProtectPreCreate (PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID*) { UNREFERENCED_PARAMETER (FltObjects); if (Data->RequestorMode == KernelMode) return FLT_PREOP_SUCCESS_NO_CALLBACK; auto & params = Data->Iopb->Parameters.Create; auto returnStatus = FLT_PREOP_SUCCESS_NO_CALLBACK; if (params.Options & FILE_DELETE_ON_CLOSE) { KdPrint (("Delete on close: %wZ\n" , &Data->Iopb->TargetFileObject->FileName)); if (!IsDeleteAllowed (PsGetCurrentProcess ())) { Data->IoStatus.Status = STATUS_ACCESS_DENIED; returnStatus = FLT_PREOP_COMPLETE; KdPrint (("Prevent delete from IRP_MJ_CREATE by cmd.exe\n" )); } } return returnStatus; } bool IsDeleteAllowed (const PEPROCESS Process) { bool currentProcess = PsGetCurrentProcess () == Process; HANDLE hProcess; if (currentProcess) hProcess = NtCurrentProcess (); else { auto status = ObOpenObjectByPointer (Process, OBJ_KERNEL_HANDLE, nullptr , 0 , nullptr , KernelMode, &hProcess); if (!NT_SUCCESS (status)) return true ; } auto size = 300 ; bool allowDelete = true ; auto processName = (UNICODE_STRING*)ExAllocatePoolWithTag (PagedPool, size, DRIVER_TAG); if (processName) { RtlZeroMemory (processName, size); auto status = ZwQueryInformationProcess (hProcess, ProcessImageFileName, processName, size - sizeof (WCHAR), nullptr ); if (NT_SUCCESS (status)) { KdPrint (("Delete operation from %wZ\n" , processName)); if (processName->Length >0 && wcsstr (processName->Buffer, L"\\System32\\cmd.exe" ) != nullptr || wcsstr (processName->Buffer, L"\\SysWOW64\\cmd.exe" ) != nullptr ) allowDelete = false ; } ExFreePool (processName); } if (!currentProcess) ZwClose (hProcess); return allowDelete; }
上面那段params变量的结构为:
1 2 3 4 5 6 7 8 9 10 11 struct { PIO_SECURITY_CONTEXT SecurityContext; ULONG Options; USHORT POINTER_ALIGNMENT FileAttributes; USHORT ShareAccess; ULONG POINTER_ALIGNMENT EaLength; PVOID EaBuffer; LARGE_INTEGER AllocationSize; } Create;
上面用的ObOpenObjectByPointer
定义如下:
1 2 3 4 5 6 7 8 9 NTSTATUS ObOpenObjectByPointer ( _In_ PVOID Object, _In_ ULONG HandleAttributes, _In_opt_ PACCESS_STATE PassedAccessState, _In_ ACCESS_MASK DesiredAccess, _In_opt_ POBJECT_TYPE ObjectType, _In_ KPROCESSOR_MODE AccessMode, _Out_ PHANDLE Handle ) ;
设置信息前回调:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 FLT_PREOP_CALLBACK_STATUS DelProtectPreSetInformation (_Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, PVOID*) { UNREFERENCED_PARAMETER (FltObjects); UNREFERENCED_PARAMETER (Data); auto & params = Data->Iopb->Parameters.SetFileInformation; if (params.FileInformationClass != FileDispositionInformation && params.FileInformationClass != FileDispositionInformationEx) return FLT_PREOP_SUCCESS_NO_CALLBACK; auto info = (FILE_DISPOSITION_INFORMATION*)params.InfoBuffer; if (!info->DeleteFile) return FLT_PREOP_SUCCESS_NO_CALLBACK; auto returnStatus = FLT_PREOP_SUCCESS_NO_CALLBACK; auto process = PsGetThreadProcess (Data->Thread); NT_ASSERT (process); if (!IsDeleteAllowed (process)) { Data->IoStatus.Status = STATUS_ACCESS_DENIED; returnStatus = FLT_PREOP_COMPLETE; KdPrint (("Prevent delete from IRP_MJ_SET_INFORMATION by cmd.exe\n" )); } return returnStatus; }
这时params结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct { ULONG Length; FILE_INFORMATION_CLASS POINTER_ALIGNMENT FileInformationClass; PFILE_OBJECT ParentOfTarget; union { struct { BOOLEAN ReplaceIfExists; BOOLEAN AdvanceOnly; }; ULONG ClusterCount; HANDLE DeleteHandle; }; PVOID InfoBuffer; } SetFileInformation;
接下来暴露一个控制设备对象CDO:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #define IOCTL_DELPROTECT_ADD_EXE CTL_CODE(0x8000, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_DELPROTECT_REMOVE_EXE CTL_CODE(0x8000, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_DELPROTECT_CLEAR CTL_CODE(0x8000, 0x802, METHOD_NEITHER, FILE_ANY_ACCESS) const int MaxExecutables = 32 ;WCHAR* ExeNames[MaxExecutables]; int ExeNamesCount;FastMutex ExeNamesLock; NTSTATUS DriverEntry (_In_ PDRIVER_OBJECT DriverObject,_In_ PUNICODE_STRING RegistryPath) { NTSTATUS status; UNREFERENCED_PARAMETER (RegistryPath); PT_DBG_PRINT (PTDBG_TRACE_ROUTINES,("DelProtect!DriverEntry: Entered\n" )); PDEVICE_OBJECT DeviceObject = nullptr ; UNICODE_STRING devName = RTL_CONSTANT_STRING (L"\\device\\delprotect" ); UNICODE_STRING symLink = RTL_CONSTANT_STRING (L"\\??\\delprotect" ); auto symLinkCreated = false ; do { status = IoCreateDevice (DriverObject, 0 , &devName, FILE_DEVICE_UNKNOWN, 0 , FALSE, &DeviceObject); if (!NT_SUCCESS (status)) break ; status = IoCreateSymbolicLink (&symLink, &devName); if (!NT_SUCCESS (status)) break ; symLinkCreated = true ; status = FltRegisterFilter (DriverObject, &FilterRegistration, &gFilterHandle); FLT_ASSERT (NT_SUCCESS (status)); if (!NT_SUCCESS (status)) break ; DriverObject->DriverUnload = DelProtectUnloadDriver; DriverObject->MajorFunction[IRP_MJ_CREATE] = DriverObject->MajorFunction[IRP_MJ_CLOSE] = DelProtectCreateClose; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DelProtectDeviceControl; ExeNamesLock.Init (); status = FltStartFiltering (gFilterHandle); } while (false ); if (!NT_SUCCESS (status)) { if (gFilterHandle) FltUnregisterFilter (gFilterHandle); if (symLinkCreated) IoDeleteSymbolicLink (&symLink); if (DeviceObject) IoDeleteDevice (DeviceObject); } return status; }
设备控制分发例程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 NTSTATUS DelProtectDeviceControl (PDEVICE_OBJECT, PIRP Irp) { auto stack = IoGetCurrentIrpStackLocation (Irp); auto status = STATUS_SUCCESS; switch (stack->Parameters.DeviceIoControl.IoControlCode) { case IOCTL_DELPROTECT_ADD_EXE: { auto name = (WCHAR*)Irp->AssociatedIrp.SystemBuffer; if (!name) { status = STATUS_INVALID_PARAMETER; break ; } if (FindExecutable (name)) break ; AutoLock locker (ExeNamesLock) ; if (ExeNamesCount == MaxExecutables) { status = STATUS_TOO_MANY_NAMES; break ; } for (int i = 0 ; i < MaxExecutables; i++) if (ExeNames[i] == nullptr ) { auto len = (::wcslen (name) + 1 ) * sizeof (WCHAR); auto buffer = (WCHAR*)ExAllocatePoolWithTag (PagedPool, len, DRIVER_TAG); if (!buffer) { status = STATUS_INSUFFICIENT_RESOURCES; break ; } ::wcscpy_s (buffer, len / sizeof (WCHAR), name); ExeNames[i] = buffer; ++ExeNamesCount; break ; } break ; } case IOCTL_DELPROTECT_REMOVE_EXE: { auto name = (WCHAR*)Irp->AssociatedIrp.SystemBuffer; if (!name) { status = STATUS_INVALID_PARAMETER; break ; } AutoLock locker (ExeNamesLock) ; auto found = false ; for (int i = 0 ; i < MaxExecutables; i++) if (::_wcsicmp(ExeNames[i], name) == 0 ) { ExFreePool (ExeNames[i]); ExeNames[i] = nullptr ; --ExeNamesCount; found = true ; break ; } if (!found) status = STATUS_NOT_FOUND; break ; } case IOCTL_DELPROTECT_CLEAR: ClearAll (); break ; default : status = STATUS_INVALID_DEVICE_REQUEST; break ; } Irp->IoStatus.Status = status; Irp->IoStatus.Information = 0 ; IoCompleteRequest (Irp, IO_NO_INCREMENT); return status; } bool FindExecutable (PCWSTR name) { AutoLock locker (ExeNamesLock) ; if (ExeNamesCount == 0 ) return false ; for (int i = 0 ; i < MaxExecutables; i++) if (ExeNames[i] && ::_wcsicmp(ExeNames[i], name) == 0 ) return true ; return false ; } void ClearAll () { AutoLock locker (ExeNamesLock) ; for (int i = 0 ; i < MaxExecutables; i++) if (ExeNames[i]) { ExFreePool (ExeNames[i]); ExeNames[i] = nullptr ; } ExeNamesCount = 0 ; }
保护某目录中文件不被删除 保存目录的数据结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 struct DirectoryEntry { UNICODE_STRING DosName; UNICODE_STRING NtName; void Free () { if (DosName.Buffer) { ExFreePool (DosName.Buffer); DosName.Buffer = nullptr ; } if (NtName.Buffer) { ExFreePool (NtName.Buffer); NtName.Buffer = nullptr ; } } }; const int MaxDirectories = 32 ;DirectoryEntry DirNames[MaxDirectories]; int DirNamesCount;FastMutex DirNamesLock; #define IOCTL_DELPROTECT_ADD_DIR CTL_CODE(0x8000, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_DELPROTECT_REMOVE_DIR CTL_CODE(0x8000, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_DELPROTECT_CLEAR CTL_CODE(0x8000, 0x802, METHOD_NEITHER, FILE_ANY_ACCESS) NTSTATUS DelProtectDeviceControl (PDEVICE_OBJECT, PIRP Irp) { auto stack = IoGetCurrentIrpStackLocation (Irp); auto status = STATUS_SUCCESS; switch (stack->Parameters.DeviceIoControl.IoControlCode) { case IOCTL_DELPROTECT_ADD_DIR: { auto name = (WCHAR*)Irp->AssociatedIrp.SystemBuffer; if (!name) { status = STATUS_INVALID_PARAMETER; break ; } auto bufferLen = stack->Parameters.DeviceIoControl.InputBufferLength; if (bufferLen > 1024 ) { status = STATUS_INVALID_PARAMETER; break ; } name[bufferLen / sizeof (WCHAR) - 1 ] = L'\0' ; auto dosNameLen = ::wcslen (name); if (dosNameLen < 3 ) { status = STATUS_BUFFER_TOO_SMALL; break ; } AutoLock locker (DirNamesLock) ; UNICODE_STRING strName; RtlInitUnicodeString (&strName, name); if (FindDirectory (&strName, true ) >= 0 ) { break ; } if (DirNamesCount == MaxDirectories) { status = STATUS_TOO_MANY_NAMES; break ; } for (int i = 0 ; i < MaxDirectories; i++) { if (DirNames[i].DosName.Buffer == nullptr ) { auto len = (dosNameLen + 2 ) * sizeof (WCHAR); auto buffer = (WCHAR*)ExAllocatePoolWithTag (PagedPool, len, DRIVER_TAG); if (!buffer) { status = STATUS_INSUFFICIENT_RESOURCES; break ; } ::wcscpy_s (buffer, len / sizeof (WCHAR), name); if (name[dosNameLen - 1 ] != L'\\' ) ::wcscat_s (buffer, dosNameLen + 2 , L"\\" ); status = ConvertDosNameToNtName (buffer, &DirNames[i].NtName); if (!NT_SUCCESS (status)) { ExFreePool (buffer); break ; } RtlInitUnicodeString (&DirNames[i].DosName, buffer); KdPrint (("Add: %wZ <=> %wZ\n" , &DirNames[i].DosName, &DirNames[i].NtName)); ++DirNamesCount; break ; } } break ; } case IOCTL_DELPROTECT_REMOVE_DIR: { auto name = (WCHAR*)Irp->AssociatedIrp.SystemBuffer; if (!name) { status = STATUS_INVALID_PARAMETER; break ; } auto bufferLen = stack->Parameters.DeviceIoControl.InputBufferLength; if (bufferLen > 1024 ) { status = STATUS_INVALID_PARAMETER; break ; } name[bufferLen / sizeof (WCHAR) - 1 ] = L'\0' ; auto dosNameLen = ::wcslen (name); if (dosNameLen < 3 ) { status = STATUS_BUFFER_TOO_SMALL; break ; } AutoLock locker (DirNamesLock) ; UNICODE_STRING strName; RtlInitUnicodeString (&strName, name); int found = FindDirectory (&strName, true ); if (found >= 0 ) { DirNames[found].Free (); DirNamesCount--; } else status = STATUS_NOT_FOUND; break ; } case IOCTL_DELPROTECT_CLEAR: ClearAll (); break ; default : status = STATUS_INVALID_DEVICE_REQUEST; break ; } Irp->IoStatus.Status = status; Irp->IoStatus.Information = 0 ; IoCompleteRequest (Irp, IO_NO_INCREMENT); return status; } int FindDirectory (PCUNICODE_STRING name, bool dosName) { if (DirNamesCount == 0 ) return -1 ; for (int i = 0 ; i < MaxDirectories; i++) { const auto & dir = dosName ? DirNames[i].DosName : DirNames[i].NtName; if (dir.Buffer && RtlEqualUnicodeString (name, &dir, TRUE)) return i; } return -1 ; }
然后DOS名转NT名:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 NTSTATUS ConvertDosNameToNtName (_In_ PCWSTR dosName, _Out_ PUNICODE_STRING ntName) { ntName->Buffer = nullptr ; auto dosNameLen = ::wcslen (dosName); if (dosNameLen < 3 ) return STATUS_BUFFER_TOO_SMALL; if (dosName[2 ] != L'\\' || dosName[1 ] != L':' ) return STATUS_INVALID_PARAMETER; kstring symLink (L"\\??\\" , PagedPool, DRIVER_TAG) ; symLink.Append (dosName, 2 ); UNICODE_STRING symLinkFull; symLink.GetUnicodeString (&symLinkFull); OBJECT_ATTRIBUTES symLinkAttr; InitializeObjectAttributes (&symLinkAttr, &symLinkFull, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, nullptr , nullptr ); HANDLE hSymLink = nullptr ; auto status = STATUS_SUCCESS; do { status = ZwOpenSymbolicLinkObject (&hSymLink, GENERIC_READ, &symLinkAttr); if (!NT_SUCCESS (status)) break ; USHORT maxLen = 1024 ; ntName->Buffer = (WCHAR*)ExAllocatePoolWithTag (PagedPool, maxLen, DRIVER_TAG); if (!ntName->Buffer) { status = STATUS_INSUFFICIENT_RESOURCES; break ; } ntName->MaximumLength = maxLen; status = ZwQuerySymbolicLinkObject (hSymLink, ntName, nullptr ); if (!NT_SUCCESS (status)) break ; } while (false ); if (!NT_SUCCESS (status)) if (ntName->Buffer) { ExFreePool (ntName->Buffer); ntName->Buffer = nullptr ; } else RtlAppendUnicodeToString (ntName, dosName + 2 ); if (hSymLink) ZwClose (hSymLink); return status; }
判断是否处于受保护目录:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 bool IsDeleteAllowed (_In_ PFLT_CALLBACK_DATA Data) { PFLT_FILE_NAME_INFORMATION nameInfo = nullptr ; auto allow = true ; do { auto status = FltGetFileNameInformation (Data, FLT_FILE_NAME_QUERY_DEFAULT | FLT_FILE_NAME_NORMALIZED, &nameInfo); if (!NT_SUCCESS (status)) break ; status = FltParseFileNameInformation (nameInfo); if (!NT_SUCCESS (status)) break ; UNICODE_STRING path; path.Length = path.MaximumLength = nameInfo->Volume.Length + nameInfo->Share.Length + nameInfo->ParentDir.Length; path.Buffer = nameInfo->Volume.Buffer; KdPrint (("Checking directory: %wZ\n" , &path)); AutoLock locker (DirNamesLock) ; if (FindDirectory (&path, false ) >= 0 ) { allow = false ; KdPrint (("File not allowed to delete: %wZ\n" , &nameInfo->Name)); } } while (false ); if (nameInfo) FltReleaseFileNameInformation (nameInfo); return allow; }
创建前回调和信息前回调:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 _Use_decl_annotations_ FLT_PREOP_CALLBACK_STATUS DelProtectPreCreate (PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID*) { UNREFERENCED_PARAMETER (FltObjects); if (Data->RequestorMode == KernelMode) return FLT_PREOP_SUCCESS_NO_CALLBACK; auto & params = Data->Iopb->Parameters.Create; if (params.Options & FILE_DELETE_ON_CLOSE) { KdPrint (("Delete on close: %wZ\n" , &FltObjects->FileObject->FileName)); if (!IsDeleteAllowed (Data)) { Data->IoStatus.Status = STATUS_ACCESS_DENIED; return FLT_PREOP_COMPLETE; } } return FLT_PREOP_SUCCESS_NO_CALLBACK; } _Use_decl_annotations_ FLT_PREOP_CALLBACK_STATUS DelProtectPreSetInformation (PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext) { UNREFERENCED_PARAMETER (FltObjects); UNREFERENCED_PARAMETER (CompletionContext); if (Data->RequestorMode == KernelMode) return FLT_PREOP_SUCCESS_NO_CALLBACK; auto & params = Data->Iopb->Parameters.SetFileInformation; if (params.FileInformationClass != FileDispositionInformation && params.FileInformationClass != FileDispositionInformationEx) return FLT_PREOP_SUCCESS_NO_CALLBACK; auto info = (FILE_DISPOSITION_INFORMATION*)params.InfoBuffer; if (!info->DeleteFile) return FLT_PREOP_SUCCESS_NO_CALLBACK; if (IsDeleteAllowed (Data)) return FLT_PREOP_SUCCESS_NO_CALLBACK; Data->IoStatus.Status = STATUS_ACCESS_DENIED; return FLT_PREOP_COMPLETE; }
文件备份 当文件以写访问方式打开,则在写入前自动对文件备份,需要时可把文件恢复到前一个状态。备份放在文件的另一个流中,需要时将该流内容与默认流交换,相当于恢复。
驱动不应附加到非NTFS卷上,可通过修改微过滤驱动的项目模板中的实例设置回调:
1 2 3 4 5 6 7 8 9 10 11 12 NTSTATUS FileBackupInstanceSetup (_In_ PCFLT_RELATED_OBJECTS FltObjects,_In_ FLT_INSTANCE_SETUP_FLAGS Flags,_In_ DEVICE_TYPE VolumeDeviceType,_In_ FLT_FILESYSTEM_TYPE VolumeFilesystemType) { UNREFERENCED_PARAMETER (FltObjects); UNREFERENCED_PARAMETER (Flags); UNREFERENCED_PARAMETER (VolumeDeviceType); UNREFERENCED_PARAMETER (VolumeFilesystemType); PAGED_CODE (); if (VolumeFilesystemType != FLT_FSTYPE_NTFS) { KdPrint (("Not attaching to non-NTFS volume\n" )); return STATUS_FLT_DO_NOT_ATTACH; } return STATUS_SUCCESS; }
接下来要截取写操作,需要一个IRP_MJ_WRITE的操作前回调,还需要一个文件上下文跟踪一些状态。驱动可能需要处理创建后回调和IRP_MJ_CLEANUP清除操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 CONST FLT_OPERATION_REGISTRATION Callbacks[] = { { IRP_MJ_CREATE, 0 , nullptr , FileBackupPostCreate }, { IRP_MJ_WRITE, FLTFL_OPERATION_REGISTRATION_SKIP_PAGING_IO, FileBackupPreWrite }, { IRP_MJ_CLEANUP, 0 , nullptr , FileBackupPostCleanup }, { IRP_MJ_OPERATION_END } }; struct FileContext { Mutex Lock; UNICODE_STRING FileName; BOOLEAN Written; }; const FLT_CONTEXT_REGISTRATION Contexts[] = { { FLT_FILE_CONTEXT, 0 , nullptr , sizeof (FileContext), DRIVER_CONTEXT_TAG }, { FLT_CONTEXT_END } }; CONST FLT_REGISTRATION FilterRegistration = { sizeof (FLT_REGISTRATION), FLT_REGISTRATION_VERSION, 0 , Contexts, Callbacks, FileBackupUnload, FileBackupInstanceSetup, FileBackupInstanceQueryTeardown, FileBackupInstanceTeardownStart, FileBackupInstanceTeardownComplete, nullptr , nullptr , nullptr };
设置创建后回调,用它为感兴趣的文件分配文件上下文。之所以不使用操作前回调,是因为可不考虑打开文件操作会不会由于别的驱动的操作前回调而失败。只有当文件成功打开了,驱动才去检查文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 FLT_POSTOP_CALLBACK_STATUS FileBackupPostCreate (PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID CompletionContext, FLT_POST_OPERATION_FLAGS Flags) { UNREFERENCED_PARAMETER (CompletionContext); if (Flags & FLTFL_POST_OPERATION_DRAINING) return FLT_POSTOP_FINISHED_PROCESSING; const auto & params = Data->Iopb->Parameters.Create; if (Data->RequestorMode == KernelMode || (params.SecurityContext->DesiredAccess & FILE_WRITE_DATA) == 0 || Data->IoStatus.Information == FILE_DOES_NOT_EXIST) return FLT_POSTOP_FINISHED_PROCESSING; FilterFileNameInformation fileNameInfo (Data) ; if (!fileNameInfo) return FLT_POSTOP_FINISHED_PROCESSING; if (!NT_SUCCESS (fileNameInfo.Parse ())) return FLT_POSTOP_FINISHED_PROCESSING; if (!IsBackupDirectory (&fileNameInfo->ParentDir)) return FLT_POSTOP_FINISHED_PROCESSING; if (fileNameInfo->Stream.Length > 0 ) return FLT_POSTOP_FINISHED_PROCESSING; FileContext* context; auto status = FltAllocateContext (FltObjects->Filter, FLT_FILE_CONTEXT, sizeof (FileContext), PagedPool, (PFLT_CONTEXT*)&context); if (!NT_SUCCESS (status)) { KdPrint (("Failed to allocate file context (0x%08X)\n" , status)); return FLT_POSTOP_FINISHED_PROCESSING; } context->Written = FALSE; context->FileName.MaximumLength = fileNameInfo->Name.Length; context->FileName.Buffer = (WCHAR*)ExAllocatePoolWithTag (PagedPool, fileNameInfo->Name.Length, DRIVER_TAG); if (!context->FileName.Buffer) { FltReleaseContext (context); return FLT_POSTOP_FINISHED_PROCESSING; } RtlCopyUnicodeString (&context->FileName, &fileNameInfo->Name); context->Lock.Init (); status = FltSetFileContext (FltObjects->Instance, FltObjects->FileObject, FLT_SET_CONTEXT_KEEP_IF_EXISTS, context, nullptr ); if (!NT_SUCCESS (status)) { KdPrint (("Failed to set file context (0x%08X)\n" , status)); ExFreePool (context->FileName.Buffer); } FltReleaseContext (context); return FLT_POSTOP_FINISHED_PROCESSING; } bool IsBackupDirectory (_In_ PCUNICODE_STRING directory) { ULONG maxSize = 1024 ; if (directory->Length > maxSize) return false ; auto copy = (WCHAR*)ExAllocatePoolWithTag (PagedPool, maxSize + sizeof (WCHAR), DRIVER_TAG); if (!copy) return false ; RtlZeroMemory (copy, maxSize + sizeof (WCHAR)); wcsncpy_s (copy, 1 + maxSize / sizeof (WCHAR), directory->Buffer, directory->Length / sizeof (WCHAR)); _wcslwr(copy); bool doBackup = wcsstr (copy, L"\\pictures\\" ) || wcsstr (copy, L"\\documents\\" ); ExFreePool (copy); return doBackup; }
对于写前回调,要在真正写操作前复制一份文件数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 FLT_PREOP_CALLBACK_STATUS FileBackupPreWrite (PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID* CompletionContext) { UNREFERENCED_PARAMETER (CompletionContext); UNREFERENCED_PARAMETER (Data); FileContext* context; auto status = FltGetFileContext (FltObjects->Instance, FltObjects->FileObject, (PFLT_CONTEXT*)&context); if (!NT_SUCCESS (status) || context == nullptr ) return FLT_PREOP_SUCCESS_NO_CALLBACK; { AutoLock<Mutex> locker (context->Lock) ; if (!context->Written) { status = BackupFile (&context->FileName, FltObjects); if (!NT_SUCCESS (status)) KdPrint (("Failed to backup file! (0x%X)\n" , status)); else if (SendClientPort) { USHORT nameLen = context->FileName.Length; USHORT len = sizeof (FileBackupPortMessage) + nameLen; auto msg = (FileBackupPortMessage*)ExAllocatePoolWithTag (PagedPool, len, DRIVER_TAG); if (msg) { msg->FileNameLength = nameLen / sizeof (WCHAR); RtlCopyMemory (msg->FileName, context->FileName.Buffer, nameLen); LARGE_INTEGER timeout; timeout.QuadPart = -10000 * 100 ; FltSendMessage (gFilterHandle, &SendClientPort, msg, len, nullptr , nullptr , &timeout); ExFreePool (msg); } } context->Written = TRUE; } } FltReleaseContext (context); return FLT_PREOP_SUCCESS_NO_CALLBACK; } NTSTATUS BackupFile (_In_ PUNICODE_STRING FileName, _In_ PCFLT_RELATED_OBJECTS FltObjects) { HANDLE hTargetFile = nullptr ; HANDLE hSourceFile = nullptr ; IO_STATUS_BLOCK ioStatus; auto status = STATUS_SUCCESS; void * buffer = nullptr ; LARGE_INTEGER fileSize; status = FsRtlGetFileSize (FltObjects->FileObject, &fileSize); if (!NT_SUCCESS (status) || fileSize.QuadPart == 0 ) return status; do { OBJECT_ATTRIBUTES sourceFileAttr; InitializeObjectAttributes (&sourceFileAttr, FileName, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, nullptr , nullptr ); status = FltCreateFile (FltObjects->Filter, FltObjects->Instance, &hSourceFile, FILE_READ_DATA | SYNCHRONIZE, &sourceFileAttr, &ioStatus, nullptr , FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT | FILE_SEQUENTIAL_ONLY, nullptr , 0 , IO_IGNORE_SHARE_ACCESS_CHECK); if (!NT_SUCCESS (status)) break ; UNICODE_STRING targetFileName; const WCHAR backupStream[] = L":backup" ; targetFileName.MaximumLength = FileName->Length + sizeof (backupStream); targetFileName.Buffer = (WCHAR*)ExAllocatePoolWithTag (PagedPool, targetFileName.MaximumLength, DRIVER_TAG); if (targetFileName.Buffer == nullptr ) return STATUS_INSUFFICIENT_RESOURCES; RtlCopyUnicodeString (&targetFileName, FileName); RtlAppendUnicodeToString (&targetFileName, backupStream); OBJECT_ATTRIBUTES targetFileAttr; InitializeObjectAttributes (&targetFileAttr, &targetFileName, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, nullptr , nullptr ); status = FltCreateFile (FltObjects->Filter, FltObjects->Instance, &hTargetFile, GENERIC_WRITE | SYNCHRONIZE, &targetFileAttr, &ioStatus, nullptr , FILE_ATTRIBUTE_NORMAL, 0 , FILE_OVERWRITE_IF, FILE_SYNCHRONOUS_IO_NONALERT | FILE_SEQUENTIAL_ONLY, nullptr , 0 , 0 ); ExFreePool (targetFileName.Buffer); if (!NT_SUCCESS (status)) break ; ULONG size = 1 << 21 ; buffer = ExAllocatePoolWithTag (PagedPool, size, DRIVER_TAG); if (!buffer) { status = STATUS_INSUFFICIENT_RESOURCES; break ; } LARGE_INTEGER offset = { 0 }; LARGE_INTEGER writeOffset = { 0 }; ULONG bytes; auto saveSize = fileSize; while (fileSize.QuadPart > 0 ) { status = ZwReadFile (hSourceFile, nullptr , nullptr , nullptr , &ioStatus, buffer, (ULONG)min ((LONGLONG)size, fileSize.QuadPart), &offset, nullptr ); if (!NT_SUCCESS (status)) break ; bytes = (ULONG)ioStatus.Information; status = ZwWriteFile (hTargetFile, nullptr , nullptr , nullptr , &ioStatus, buffer, bytes, &writeOffset, nullptr ); if (!NT_SUCCESS (status)) break ; offset.QuadPart += bytes; writeOffset.QuadPart += bytes; fileSize.QuadPart -= bytes; } FILE_END_OF_FILE_INFORMATION info; info.EndOfFile = saveSize; NT_VERIFY (NT_SUCCESS (ZwSetInformationFile (hTargetFile, &ioStatus, &info, sizeof (info), FileEndOfFileInformation))); } while (false ); if (buffer) ExFreePool (buffer); if (hSourceFile) FltClose (hSourceFile); if (hTargetFile) FltClose (hTargetFile); return status; }
客户程序关闭文件时,就不用上下文了,此时释放,由此需要清理后回调。IRP_MJ_CLOSE在文件最后一个句柄关闭时调用,由于缓存可能不总是被即使调用。这里用IRP_MJ_CLEANUP,他在文件对象不再被需要了,即使最后一个句柄未关闭时调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 FLT_POSTOP_CALLBACK_STATUS FileBackupPostCleanup (PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID CompletionContext, FLT_POST_OPERATION_FLAGS Flags) { UNREFERENCED_PARAMETER (Flags); UNREFERENCED_PARAMETER (CompletionContext); UNREFERENCED_PARAMETER (Data); FileContext* context; auto status = FltGetFileContext (FltObjects->Instance, FltObjects->FileObject, (PFLT_CONTEXT*)&context); if (!NT_SUCCESS (status) || context == nullptr ) return FLT_POSTOP_FINISHED_PROCESSING; if (context->FileName.Buffer) ExFreePool (context->FileName.Buffer); FltReleaseContext (context); FltDeleteContext (context); return FLT_POSTOP_FINISHED_PROCESSING; }
恢复备份的工作放到用户模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 int wmain (int argc, const wchar_t * argv[]) { if (argc < 2 ) { printf ("Usage: FileRestore <filename>\n" ); return 0 ; } std::wstring stream (argv[1 ]) ; stream += L":backup" ; HANDLE hSource = ::CreateFile (stream.c_str (), GENERIC_READ, FILE_SHARE_READ, nullptr , OPEN_EXISTING, 0 , nullptr ); if (hSource == INVALID_HANDLE_VALUE) return Error ("Failed to locate backup" ); HANDLE hTarget = ::CreateFile (argv[1 ], GENERIC_WRITE, 0 , nullptr , OPEN_EXISTING, 0 , nullptr ); if (hTarget == INVALID_HANDLE_VALUE) return Error ("Failed to locate file" ); LARGE_INTEGER size; if (!::GetFileSizeEx (hSource, &size)) return Error ("Failed to get file size" ); ULONG bufferSize = (ULONG)min ((LONGLONG)1 << 21 , size.QuadPart); void * buffer = VirtualAlloc (nullptr , bufferSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (!buffer) return Error ("Failed to allocate buffer" ); DWORD bytes; while (size.QuadPart > 0 ) { if (!::ReadFile (hSource, buffer, (DWORD)(min ((LONGLONG)bufferSize, size.QuadPart)), &bytes, nullptr )) return Error ("Failed to read data" ); if (!::WriteFile (hTarget, buffer, bytes, &bytes, nullptr )) return Error ("Failed to write data" ); size.QuadPart -= bytes; } printf ("Restore successful!\n" ); ::CloseHandle (hSource); ::CloseHandle (hTarget); ::VirtualFree (buffer, 0 , MEM_RELEASE); return 0 ; }
这里解决用户模式通信问题。其中DriverEntry
例程上文有。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 PFLT_PORT FilterPort; PFLT_PORT SendClientPort; _Use_decl_annotations_ NTSTATUS PortConnectNotify (PFLT_PORT ClientPort, PVOID ServerPortCookie, PVOID ConnectionContext, ULONG SizeOfContext, PVOID* ConnectionPortCookie) { UNREFERENCED_PARAMETER (ServerPortCookie); UNREFERENCED_PARAMETER (ConnectionContext); UNREFERENCED_PARAMETER (SizeOfContext); UNREFERENCED_PARAMETER (ConnectionPortCookie); SendClientPort = ClientPort; return STATUS_SUCCESS; } void PortDisconnectNotify (PVOID ConnectionCookie) { UNREFERENCED_PARAMETER (ConnectionCookie); FltCloseClientPort (gFilterHandle, &SendClientPort); SendClientPort = nullptr ; } struct FileBackupPortMessage { USHORT FileNameLength; WCHAR FileName[1 ]; }; void PortDisconnectNotify (PVOID ConnectionCookie) { UNREFERENCED_PARAMETER (ConnectionCookie); FltCloseClientPort (gFilterHandle, &SendClientPort); SendClientPort = nullptr ; }
再搞一个客户程序,打开端口并倾听文件备份消息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include "..\FileBackup\FileBackupCommon.h" #pragma comment(lib, "fltlib" ) void HandleMessage (const BYTE* buffer) { auto msg = (FileBackupPortMessage*)buffer; std::wstring filename (msg->FileName, msg->FileNameLength) ; printf ("file backed up: %ws\n" , filename.c_str ()); } int main () { HANDLE hPort; auto hr = ::FilterConnectCommunicationPort (L"\\FileBackupPort" , 0 , nullptr , 0 , nullptr , &hPort); if (FAILED (hr)) { printf ("Error connecting to port (HR=0x%08X)\n" , hr); return 1 ; } BYTE buffer[1 << 12 ]; auto message = (FILTER_MESSAGE_HEADER*)buffer; for (;;) { hr = ::FilterGetMessage (hPort, message, sizeof (buffer), nullptr ); if (FAILED (hr)) { printf ("Error receiving message (0x%08X)\n" , hr); break ; } HandleMessage (buffer + sizeof (FILTER_MESSAGE_HEADER)); } ::CloseHandle (hPort); return 0 ; }