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; //结构的大小 就是sizeof(FLT_REGISTRATION)
USHORT Version; //结构的版本 一般FLT_REGISTRATION_VERSION
FLT_REGISTRATION_FLAGS Flags; //标志位
//当FLTFL_REGISTRATION_DO_NOT_SUPPORT_SERVICE_STOP时停止服务后Minifilter不会响应且不会调用FilterUnloadCallback 即不支持停止请求
//FLTFL_REGISTRATION_SUPPORT_NPFS_MSFS时驱动支持命名管道和邮件槽并过滤针对这些文件系统的请求
//FLTFL_REGISTRATION_SUPPORT_DAX_VOLUME时驱动支持直接附加到直接访问卷DAX
CONST FLT_CONTEXT_REGISTRATION *ContextRegistration; //上下文处理函数注册
CONST FLT_OPERATION_REGISTRATION *OperationRegistration; //操作回调函数集
PFLT_FILTER_UNLOAD_CALLBACK FilterUnloadCallback; //卸载回调函数 NULL则无法卸载
PFLT_INSTANCE_SETUP_CALLBACK InstanceSetupCallback; //卷实例安装回调 STATUS_SUCCESS允许附加 STATUS_FLT_DO_NOT_ATTACH拒绝 可NULL
PFLT_INSTANCE_QUERY_TEARDOWN_CALLBACK InstanceQueryTeardownCallback; //控制实例销毁函数 在一个手工解除绑定的请求时调用 对应FltDetachVolume和FilterDetach 可NULL则失败
PFLT_INSTANCE_TEARDOWN_CALLBACK InstanceTeardownStartCallback; //实例解绑定函数 开始脱离时调用 为NULL不会阻止脱离
PFLT_INSTANCE_TEARDOWN_CALLBACK InstanceTeardownCompleteCallback; //实例解绑定完成函数 可NULL
//剩下这些不常用
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 // FLT_MGR_LONGHORN
#if FLT_MGR_WIN8
PFLT_SECTION_CONFLICT_NOTIFICATION_CALLBACK SectionNotificationCallback;
#endif // FLT_MGR_WIN8
} FLT_REGISTRATION, *PFLT_REGISTRATION;

一个可行的设置例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//  This defines what we want to filter with FltMgr
const FLT_REGISTRATION FilterRegistration = {
sizeof( FLT_REGISTRATION ), // Size
FLT_REGISTRATION_VERSION, // Version
0, // Flags
NULL, // Context
Callbacks, // Operation callbacks
NPUnload, // MiniFilterUnload
NPInstanceSetup, // InstanceSetup
NPInstanceQueryTeardown, // InstanceQueryTeardown
NPInstanceTeardownStart, // InstanceTeardownStart
NPInstanceTeardownComplete, // InstanceTeardownComplete
NULL, // GenerateFileName
NULL, // GenerateDestinationFileName
NULL // NormalizeNameComponent
};

上面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;
//FLTFL_OPERATION_REGISTRATION_SKIP_CHCHED_IO 缓存I/O(如快速I/O操作)则不调用回调函数
//FLTFL_OPERATION_REGISTRATION_SKIP_PAGING_IO 对换页I/O(IRP操作)不调用回调函数
//FLTFL_OPERATION_REGISTRATION_SKIP_NON_DASD_IO 直接访问卷则不调用回调函数
PFLT_PRE_OPERATION_CALLBACK PreOperation; //这俩至少一个不为NULL
PFLT_POST_OPERATION_CALLBACK PostOperation;
PVOID Reserved1;
} FLT_OPERATION_REGISTRATION, *PFLT_OPERATION_REGISTRATION;

OperationRegistration域的设置例子:

1
2
3
4
5
6
7
8
9
10
//  operation registration
const FLT_OPERATION_REGISTRATION Callbacks[] = {
{ IRP_MJ_CREATE, //请求的主功能号
0, //标志位 生成请求处理用0 FLTFL_OPERATION_REGISTRATION_SKIP_CACHED_IO不过滤缓冲读写请求 FLTFL_OPERATION_REGISTRATION_SKIP_PAGING_IO不过滤分页读写请求
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
//  This defines what we want to filter with FltMgr
CONST FLT_REGISTRATION FilterRegistration = {
sizeof(FLT_REGISTRATION),
FLT_REGISTRATION_VERSION,
0, // Flags
nullptr, // Context
Callbacks, // Operation callbacks
DelProtectUnload, // MiniFilterUnload
DelProtectInstanceSetup, // InstanceSetup
DelProtectInstanceQueryTeardown, // InstanceQueryTeardown
DelProtectInstanceTeardownStart, // InstanceTeardownStart
DelProtectInstanceTeardownComplete, // InstanceTeardownComplete
};
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) { //操作前回调原型 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)) { //如果成功则解析文件名信息 比较是否有NOTEPAD.EXE子字符串
if (gCommand == ENUM_BLOCK) {
FltParseFileNameInformation(nameInfo);
if (NPUnicodeStringToChar(&nameInfo->Name, FileName)) //字符串转CHAR大写利于比对字符串
if (strstr(FileName, "NOTEPAD.EXE") > 0) {
Data->IoStatus.Status = STATUS_ACCESS_DENIED; //拒绝
Data->IoStatus.Information = 0;
FltReleaseFileNameInformation(nameInfo);
return FLT_PREOP_COMPLETE; //请求结束 不用再下传了
};
};
//release resource
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; //请求者来自用户模式UserMode还是内核模式KernelMode
} 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; //TxF mini-version
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, //请求文件格式 FLT_FILE_NAME_NORMALIZED全路径名 FLT_FILE_NAME_QUERY_DEFAULT先缓存中找再文件系统查询
OUT PFLT_FILE_NAME_INFORMATION *FileNameInformation //结果
);
typedef struct _FLT_FILE_NAME_INFORMATION {
USHORT Size;
// For each bit that is set in the NamesParsed flags field, the corresponding substring from Name has been appropriately parsed into one of the unicode strings below.
FLT_FILE_NAME_PARSED_FLAGS NamesParsed;
// The name format that this FLT_FILE_NAME_INFORMATION structure represents.
FLT_FILE_NAME_OPTIONS Format;
// For normalized and opened names, this name contains the version of name in the following format:
// [Volume name][Full path to file][File name][Stream Name]
// For example, the above components would map to this example name as follows:
// \Device\HarddiskVolume1\Documents and Settings\MyUser\My Documents\Test Results.txt:stream1
// [Volume name] = "\Device\HarddiskVolume1"
// [Full path to file] = "\Documents and Settings\MyUser\My Documents\"
// [File name] = "Test Results.txt"
// [Stream name] = ":stream1"
// For short names, only the short name for the final name component is returned in the Name unicode string. Therefore, if you requested the short name of the file object representing an open on the file:
// \Device\HarddiskVolume1\Documents and Settings\MyUser\My Documents\Test Results.txt
// The name returned in Name will be at most 8 characters followed by a '.' then at most 3 more characters, like:
// testre~1.txt
UNICODE_STRING Name;
// The Volume is only filled in for name requested in normalized and opened formats.
UNICODE_STRING Volume; //符号链接映射的实际设备名 如C:为\Device\HarddiskVolume3
// The share component of the file name requested. This will only be set for normalized and opened name formats on files that opened across redirectors. For local files, this string will always be 0 length.
UNICODE_STRING Share; //一般为空
// To exemplify what each of the following substrings refer to, let's look again at the first example string from above:
// \Device\HarddiskVolume1\Documents and Settings\MyUser\My Documents\Test Results.txt:stream1
// Extension = "txt"
// Stream = ":stream1"
// FinalComponent = "Test Results.txt:stream1"
// ParentDir = "\Documents and Settings\MyUser\My Documents\"
// This can be parsed from a normalized, opened, or short name.
UNICODE_STRING Extension; //文件扩展名 如txt
// The following parse formats are only available for normalized and opened name formats, but not short names.
UNICODE_STRING Stream;
UNICODE_STRING FinalComponent; //文件名和流名(如果不是默认流) 如myfile.txt
UNICODE_STRING ParentDir; //目录 如\mydir1\mydir2\
} FLT_FILE_NAME_INFORMATION, *PFLT_FILE_NAME_INFORMATION;

上述FileNameInformation需要用FltReleaseFileNameInformation释放。

NTFS中,文件数据是$DATA流(默认流),也是保存在同一文件里。资源管理器等工具不会查看别的流,GetFileSize等标准API也不返回。但可用DeleteFile删除、用FindFirstStreamFindNextStream枚举。

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 this create was failing anyway, don't bother scanning now.
if (!NT_SUCCESS(Data->IoStatus.Status) || (STATUS_REPARSE == Data->IoStatus.Status))
return FLT_POSTOP_FINISHED_PROCESSING;
// Check if we are interested in this file.
status = FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &nameInfo); //回调包中获得名字信息
if (!NT_SUCCESS(status))
return FLT_POSTOP_FINISHED_PROCESSING; //完成I/O所有处理 返回控制给过滤管理器
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

如果过滤器不提供这个,不允许手工解除绑定,但卷和过滤器的卸载仍然可以。如果调用成功,那么InstanceTeardownStartCallbackInstanceTeardownCompleteCallback将会被调用。返回错误时手工解除绑定失败,推荐错误代码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:这次销毁操作是一个手工的请求,即FilterDetachFltDetachVolume
  • 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 {
// Identifies the type of this context
FLT_CONTEXT_TYPE ContextType; //标识上下文附加到什么类型对象上
// Local flags
FLT_CONTEXT_REGISTRATION_FLAGS Flags;
// Routine to call to cleanup the context before it is deleted. This may be NULL if not cleanup is needed.
PFLT_CONTEXT_CLEANUP_CALLBACK ContextCleanupCallback;
// Defines the size & pool tag the mini-filter wants for the given context. FLT_VARIABLE_SIZED_CONTEXTS may be specified for the size if variable sized contexts are used. A size of zero is valid. If an empty pooltag value is specified, the FLTMGR will use a context type specific tag.
// If an explicit size is specified, the FLTMGR internally optimizes the allocation of that entry.
// NOTE: These fields are ignored if Allocate & Free routines are specifed.
SIZE_T Size;
ULONG PoolTag; //分配池标记
// Specifies the ALLOCATE and FREE routines that should be used when allocating a context for this mini-filter.
// NOTE: The above size & PoolTag fields are ignored when these routinesare defined.
PFLT_CONTEXT_ALLOCATE_CALLBACK ContextAllocateCallback; //分配回调函数
PFLT_CONTEXT_FREE_CALLBACK ContextFreeCallback; //释放回调函数
// Reserved for future expansion
PVOID Reserved1;
} FLT_CONTEXT_REGISTRATION, *PFLT_CONTEXT_REGISTRATION;
//ContextType取值:
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 //文件流 FAT等单一流数据文件系统把这个看作FLT_FILE_CONTEXT
#define FLT_STREAMHANDLE_CONTEXT 0x0010 //流句柄 可附加到FILE_OBJECT流上
#define FLT_TRANSACTION_CONTEXT 0x0020 //事务 附加在正在进行的文件系统事务上
#if FLT_MGR_WIN8
#define FLT_SECTION_CONTEXT 0x0040 //节 附加到由FltCreateSectionForDataScan创建的节
#endif // FLT_MGR_WIN8
#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, //过滤器指针 FltRegisterFilter返回或FLT_RELATED_OBJECTS结构中有
_In_ FLT_CONTEXT_TYPE ContextType, //上下文类型宏
_In_ SIZE_T ContextSize, //上下文大小 单位字节
_In_ POOL_TYPE PoolType, //PagedPool或NonPagedPool 卷上下文必须NonPagedPool
_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, //FLT_SET_CONTEXT_REPLACE_IF_EXISTS或FLT_SET_CONTEXT_KEEP_IF_EXISTS
_In_ PFLT_CONTEXT NewContext, //要设置的上下文
_Outptr_ PFLT_CONTEXT* OldContext //FLT_SET_CONTEXT_REPLACE_IF_EXISTS时接收旧上下文
);

上下文是引用计数的,分配和设置上下文都增加引用计数。FltReleaseContext较少引用计数。一般不用FltDeleteContext手动删除上下文,一般等对应文件系统对象被销毁时自动被过滤管理器删除。

别的回调想获取该上下文时,用FltGetXxxContext,这里“Xxx”可以是File、Instance、Volume、Stream、StreamHandle、Transaction或Section之一,并将上下文引用计数加一,用完后要调用FltReleaseContext

I/O请求

一般内核代码用ZwCreateFile等打开文件句柄,用ZwReadFileZwWriteFileZwDeviceIoControlFile等进行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, // filter object 过滤器对象
_In_opt_ PFLT_INSTANCE Instance, // filter instance 实例对象
_Out_ PHANDLE FileHandle, // resulting handle 返回打开的文件句柄
_In_ ACCESS_MASK DesiredAccess, // access mask 访问掩码
_In_ POBJECT_ATTRIBUTES ObjectAttributes, // object attributes
_Out_ PIO_STATUS_BLOCK IoStatusBlock, // resulting status
_In_opt_ PLARGE_INTEGER AllocationSize, // allocation size
_In_ ULONG FileAttributes, // file attributes
_In_ ULONG ShareAccess, // share flags
_In_ ULONG CreateDisposition, // create disposition
_In_ ULONG CreateOptions, // create options (sync I/O) 创建选项 FILE_SYNCHRONOUS_IO_NONALERT表示文件句柄进行同步操作
_In_reads_bytes_opt_(EaLength) PVOID EaBuffer, // extended attributes
_In_ ULONG EaLength, // EA length
_In_ ULONG Flags // flags 标志 IO_IGNORE_SHARED_ACCESS_CHECK表示文件系统对该请求忽略共享访问检查
);

然后驱动那这返回的句柄去用标准I/O的API,如ZwReadFileZwWriteFile等,这些仍只针对下面的驱动层次。另一种方法是用FltCreateFileExFltCreateFileEx2返回的FILE_OBJECT调用FltReadFileFltWriteFile

操作完成后要将返回的句柄用FltClose,若文件对象也返回了,也要用ObDeferenceObject减少引用计数。

Minifilter与应用通信

建立通信端口

之前用DeviceIoControl方式,但这样必须由用户模式客户程序初始化通信,若驱动想主动发送给客户程序则无法做到。

微过滤驱动用FltCreateCommunicationPort创建一个过滤器通信端口,为来自客户程序的连接和消息注册回调。用户模式客户程序用FilterConnectCommunicationPort连接到这个端口,得到端口的一个句柄。定义如下:

1
2
3
4
5
6
7
8
9
10
NTSTATUS FltCreateCommunicationPort(
_In_ PFLT_FILTER Filter, //FilterRegisterFilter返回的指针
_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"));
// Register with FltMgr to tell it our callback routines
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;
// Start filtering i/o
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, //一般FLT_PORT_FLAG_SYNC_HANDLE指定句柄只能同步使用
IN OPTIONAL LPVOID lpContext, //连接时发送一个缓冲区到驱动 如用于身份验证
IN DWORD dwSizeOfContext, //lpContext缓冲区大小 单位字节
IN OPTIONAL LPSECURITY_ATTRIBUTES lpSecurityAttributes, //一般NULL
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, //ConnectionContext大小
_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 //驱动在消息到达客户程序并等待回应之前等待时间 NULL永远等待 正的则为1601年1月1日午夜算起的绝对时间值 负的则为相对时间 单位100ns
);

客户程序接收到的缓冲区有个FILTER_MESSAGE_HEADER结构,后面跟着驱动发送的实际数据:

1
2
3
4
typedef struct _FILTER_MESSAGE_HEADER{
ULONG ReplyLength, //可回应的最多期望字节数
ULONGLONG MessageId; //用于区别不同消息 FilterReplyMessage时用
} 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 //调用成功时返回lpOutBuffer大小
);

通过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; //储存列举NPMINI_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: //载入DLL成功时呼叫此功能码
InitialCommunicationPort(); //初始化通信端口
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH: //卸载DLL成功时呼叫此功能码
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-4bac-b3cf-e8cfe75c9fc2} ;GUID 这个数值视类别种类而定
Provider = %Msft% ;驱动发布者名 引用String节的Msft键值
DriverVer = 06/16/2007,1.0.0.1 ;版本日期时间 留空则设为构建日期版本
CatalogFile = NPminifilter.cat ;目录文件 其中保存有数字签名

[DestinationDirs]
DefaultDestDir = 12 ;默认drivers目录
MiniFilter.DriverFiles = 12 ;代表%windir%\system32\drivers

[DefaultInstall]
OptionDesc = %ServiceDescription% ;简单描述
CopyFiles = MiniFilter.DriverFiles

[DefaultInstall.Services]
AddService = %ServiceName%,,MiniFilter.Service ;指向的节指出要写入注册表名为%ServiceName%键中的信息 中间是0

[DefaultUninstall]
DelFiles = MiniFilter.DriverFiles

[DefaultUninstall.Services]
DelService = %ServiceName%,0x200 ;服务停止后才删除

[MiniFilter.Service]
DisplayName = %ServiceName%
Description = %ServiceDescription%
ServiceBinary = %12%\%DriverName%.sys ;%windir%\system32\drivers
Dependencies = "FltMgr" ;依赖服务/驱动列表 这里依赖过滤管理器本身
ServiceType = 2 ;文件系统相关驱动类型 SERVICE_FILE_SYSTEM_DRIVER 标准驱动为1
StartType = 3 ;启动类型 3为SERVICE_DEMAND_START表示当有需求加载时才启动此驱动功能 0为SERVICE_BOOT_START表示开机启动时自动加载
ErrorControl = 1 ;SERVICE_ERROR_NORMAL
LoadOrderGroup = "FSFilter Activity Monitor" ;必须这个 小过滤驱动分组名
AddReg = MiniFilter.AddRegistry ;指向注册表入口指示节

[MiniFilter.AddRegistry]
HKR,,"DebugFlags",0x00010001 ,0x0 ;HKR为本服务所在子键HKLM\System\CurrentControlSet\Services\Sample
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" ;高度值 要在ActivityMonitor分组高度范围内
Instance1.Flags = 0x0 ; Allow all attachments

上述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) { //检查FILE_DELETE_ON_CLOSE标志是否存在于创建请求中
// delete operation
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); // ensure string will be NULL-terminated
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) //这里只防cmd.exe删文件
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;
// The low 24 bits contains CreateOptions flag values.
// The high 8 bits contains the CreateDisposition values.
ULONG Options;
USHORT POINTER_ALIGNMENT FileAttributes;
USHORT ShareAccess;
ULONG POINTER_ALIGNMENT EaLength;
PVOID EaBuffer; //Not in IO_STACK_LOCATION parameters list
LARGE_INTEGER AllocationSize; //Not in IO_STACK_LOCATION parameters list
} Create;

上面用的ObOpenObjectByPointer定义如下:

1
2
3
4
5
6
7
8
9
NTSTATUS ObOpenObjectByPointer(
_In_ PVOID Object, //要获取句柄的对象 啥对象都行
_In_ ULONG HandleAttributes, //标志 OBJ_KERNEL_HANDLE返回内核句柄 可用于任何进程上下文
_In_opt_ PACCESS_STATE PassedAccessState, //一般NULL
_In_ ACCESS_MASK DesiredAccess, //访问掩码 若AccessMode为KernelMode 则该参数为0则返回的句柄有全部功能
_In_opt_ POBJECT_TYPE ObjectType, //将对象与该类型比较 可以是*PsProcessType、*PsThreadType等
_In_ KPROCESSOR_MODE AccessMode, //一般KernelMode
_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) //检查是不是删除操作
// not a delete operation
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;
// what process did this originate from?
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; //Not in IO_STACK_LOCATION parameters list
} 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"));
// create a standard device object and symbolic link
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;
// Register with FltMgr to tell it our callback routines
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();
// Start filtering i/o
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; //DOS名
UNICODE_STRING NtName; //NT名
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) {
// just too long for a directory
status = STATUS_INVALID_PARAMETER;
break;
}
// make sure there is a NULL terminator somewhere
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);
// append a backslash if it's missing
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) {
// just too long for a directory
status = STATUS_INVALID_PARAMETER;
break;
}
// make sure there is a NULL terminator somewhere
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;
// make sure we have a driver letter
if (dosName[2] != L'\\' || dosName[1] != L':')
return STATUS_INVALID_PARAMETER;
kstring symLink(L"\\??\\", PagedPool, DRIVER_TAG);
symLink.Append(dosName, 2); // driver letter and colon 驱动器字母
// prepare to open symbolic link
UNICODE_STRING symLinkFull;
symLink.GetUnicodeString(&symLinkFull);
OBJECT_ATTRIBUTES symLinkAttr;
InitializeObjectAttributes(&symLinkAttr, &symLinkFull, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, nullptr, nullptr); //初始化OBJECT_ATTRIBUTES
HANDLE hSymLink = nullptr;
auto status = STATUS_SUCCESS;
do {
// open symbolic link
status = ZwOpenSymbolicLinkObject(&hSymLink, GENERIC_READ, &symLinkAttr); //打开符号链接
if (!NT_SUCCESS(status))
break;
USHORT maxLen = 1024; // arbitrary
ntName->Buffer = (WCHAR*)ExAllocatePoolWithTag(PagedPool, maxLen, DRIVER_TAG);
if (!ntName->Buffer) {
status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
ntName->MaximumLength = maxLen;
// read target of symbolic link
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); // directory
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;
// concatenate volume+share+directory
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) {
// delete operation
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)
// not a delete operation
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), // Size
FLT_REGISTRATION_VERSION, // Version
0, // Flags
Contexts, // Context
Callbacks, // Operation callbacks
FileBackupUnload, // MiniFilterUnload
FileBackupInstanceSetup,
FileBackupInstanceQueryTeardown,
FileBackupInstanceTeardownStart,
FileBackupInstanceTeardownComplete,
nullptr, // GenerateFileName
nullptr, // GenerateDestinationFileName
nullptr // NormalizeNameComponent
};

设置创建后回调,用它为感兴趣的文件分配文件上下文。之所以不使用操作前回调,是因为可不考虑打开文件操作会不会由于别的驱动的操作前回调而失败。只有当文件成功打开了,驱动才去检查文件。

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) //非写访问、来自内核模式、新创建的文件跳过
// kernel caller, not write access or a new file - skip
return FLT_POSTOP_FINISHED_PROCESSING;
// get file name
FilterFileNameInformation fileNameInfo(Data); //之前讲的RAII包装器
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 it's not the default stream, we don't care
if (fileNameInfo->Stream.Length > 0) //打开的不是默认流 不处理
return FLT_POSTOP_FINISHED_PROCESSING;
// allocate and initialize a file context
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(); //快速互斥量会导致IRQL提到APC_LEVEL 这里用传统互斥量
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) {
// no counted version of wcsstr :(
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);
// get the file context if exists
FileContext* context;
auto status = FltGetFileContext(FltObjects->Instance, FltObjects->FileObject, (PFLT_CONTEXT*)&context);
if (!NT_SUCCESS(status) || context == nullptr) //上下文不存在 则为不感兴趣的文件
// no context, continue normally
return FLT_PREOP_SUCCESS_NO_CALLBACK;
{
// acquire the fast mutex in case of multiple writes
AutoLock<Mutex> locker(context->Lock);
if (!context->Written) { //若备份未创建
status = BackupFile(&context->FileName, FltObjects); //内核中没有文件复制API 要自己写
if (!NT_SUCCESS(status))
KdPrint(("Failed to backup file! (0x%X)\n", status));
else
// send message to user mode
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; // 100msec
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;
// get source file size
LARGE_INTEGER fileSize;
status = FsRtlGetFileSize(FltObjects->FileObject, &fileSize); //获取文件大小
if (!NT_SUCCESS(status) || fileSize.QuadPart == 0) //大小为0直接跳过
return status;
do {
// open source file
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;
// open target file
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;
// allocate buffer for copying purposes
ULONG size = 1 << 21; // 2 MB
buffer = ExAllocatePoolWithTag(PagedPool, size, DRIVER_TAG);
if (!buffer) {
status = STATUS_INSUFFICIENT_RESOURCES;
break;
}
// loop - read from source, write to target
LARGE_INTEGER offset = { 0 }; // read
LARGE_INTEGER writeOffset = { 0 }; // write
ULONG bytes;
auto saveSize = fileSize;
while (fileSize.QuadPart > 0) {
status = ZwReadFile(hSourceFile, nullptr/*optional KEVENT*/, nullptr, nullptr/*no APC*/, &ioStatus, buffer, (ULONG)min((LONGLONG)size, fileSize.QuadPart)/*# of bytes*/, &offset/*offset*/, nullptr/*optional key*/);
if (!NT_SUCCESS(status))
break;
bytes = (ULONG)ioStatus.Information;
// write to target file
status = ZwWriteFile(hTargetFile/*target handle*/, nullptr/*optional KEVENT*/, nullptr/*APC routine*/, nullptr/*APC context*/, &ioStatus/*I/O status result*/, buffer/*data to write*/, bytes/*# bytes to write*/, &writeOffset/*offset*/, nullptr/*optional key*/);
if (!NT_SUCCESS(status))
break;
// update byte count and offsets
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)
// no context, continue normally
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;
}
// locate the backup stream
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; //客户程序连接端口
//DriverEntry部分略...
_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;
}
//这里不期望用户模式发送消息 PostMessageNotify回调为空
struct FileBackupPortMessage { //消息结构 驱动与客户程序共用
USHORT FileNameLength; //文件名常
WCHAR FileName[1]; //文件名本身
};
//FileBackupPreWrite上文有 略...
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]; // 4 KB 消息缓冲区
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;
}