nt文件系统(2)--minifilter



前言

前面总结了 sfilter 相关的内容,接下来就该轮到minifilter了。

minifilter基础

minifilter可以简单理解为微软自己开发了一个相当于sfilter的驱动,并提供了相关接口供其他驱动使用。

由于sfilter本身的复杂性极高,一个是在不同版本系统下的兼容性问题,一个是与核心需要的业务逻辑相比,无关的代码太多,故使用 minifilter是一个很好的选择,但是由于 minifilter本身也是基于传统的设备过滤驱动实现的,故在系统中存在其他 sfilter 的时候优先级会比别人低。

minifilter框架

DriverEntry

微型文件过滤驱动程序的DriverEntry例程必须按顺序执行以下步骤:

  1. 初始化所需的全局变量
  2. 通过调用FltRegisterFilter注册一个过滤器。
  3. 通过调用FltStartFiltering启动过滤器。
  4. 返回相应的 NTSTATUS 值。
  1. 在卸载驱动时调用 FltUnregisterFilter 卸载过滤器

相关函数及结构

1
2
3
4
5
NTSTATUS FLTAPI FltRegisterFilter(
PDRIVER_OBJECT Driver,
const FLT_REGISTRATION *Registration,
PFLT_FILTER *RetFilter
);

共有三个参数,第一个参数传入本驱动对象的指针,第二个参数是一个 FLT_REGISTRATION 结构,其中便存在注册过滤例程的地方了,第三个参数是一个返回值,其作用相当于一个句柄。

1
2
3
NTSTATUS FLTAPI FltStartFiltering(
PFLT_FILTER Filter
);

一个参数,即注册时返回的句柄。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
PFLT_TRANSACTION_NOTIFICATION_CALLBACK TransactionNotificationCallback;
PFLT_NORMALIZE_NAME_COMPONENT_EX NormalizeNameComponentExCallback;
PFLT_SECTION_CONFLICT_NOTIFICATION_CALLBACK SectionNotificationCallback;
} FLT_REGISTRATION, *PFLT_REGISTRATION;

具体可以参考 MSDN 相关

其中最为重要的便是 OperationRegistration 成员。

1
2
3
4
5
6
7
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;

MajorFunction 主功能号,Flags 指定过滤哪种类型的请求,可以为0, Pre IRP完成前执行的回调例程,Post IRP完成后执行的完成例程,Reserved1 保留供系统使用。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const FLT_OPERATION_REGISTRATION
fileMonitorCallbacks[] =
{
{
IRP_MJ_CREATE,
0
PreNtCreateFile,
PostNtCreateFile
},
{
IRP_MJ_WRITE,
FLTFL_OPERATION_REGISTRATION_SKIP_PAGING_IO,// 忽略分页读/写 请求
PreNtWriteFile,
PostNtWriteFile
},
{
IRP_MJ_OPERATION_END//这个是必须要加的
}
};

PFLT_PRE_OPERATION_CALLBACK FLT_POSTOP_CALLBACK_STATUS 原型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PFLT_PRE_OPERATION_CALLBACK PfltPreOperationCallback;

FLT_PREOP_CALLBACK_STATUS PfltPreOperationCallback(
PFLT_CALLBACK_DATA Data,
PCFLT_RELATED_OBJECTS FltObjects,
PVOID *CompletionContext
)
{...}

PFLT_POST_OPERATION_CALLBACK PfltPostOperationCallback;

FLT_POSTOP_CALLBACK_STATUS PfltPostOperationCallback(
PFLT_CALLBACK_DATA Data,
PCFLT_RELATED_OBJECTS FltObjects,
PVOID CompletionContext,
FLT_POST_OPERATION_FLAGS Flags
)
{...}

其参数 FLT_CALLBACK_DATA 相当于对原来的 IRP 进行了包装,避免直接与IRP打交道。
另外可以通过其返回值轻易控制 IRP的状态,完成(FLT_PREOP_COMPLETE),挂起(FLT_PREOP_PENDING),传递(FLT_PREOP_SUCCESS_WITH_CALLBACK) 等等。

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 Thread;
PFLT_IO_PARAMETER_BLOCK 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;

minifilter 本身也提供了一系列函数用于处理 FLT_CALLBACK_DATA 的相关信息。

例如可以通过 FltGetFileNameInformation 获取文件名信息,再通过 FltParseFileNameInformation 拆分为更加易用的格式,使用完毕之后再通过 FltReleaseFileNameInformation 回收相关资源。
又例如给文件对象绑定上下文信息等等操作。

通过设置 IoStatus 则可以控制请求的结果信息。
FLT_IO_PARAMETER_BLOCK 也存放着许多需要关注的信息,如要 得到读写的具体内容等就要从该结构入手。
由于篇幅限制,就不一一展开,在MSDN文档上有对其详细的描述。

minifilter之应用层通信

除了常规的设备通信方式外,minifilter内部封装了一套更为方便的通信机制。

主要函数如下:
FltBuildDefaultSecurityDescriptor 创建默认的安全描述符
FltCreateCommunicationPort 创建一个服务端口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
NTSTATUS status;
PSECURITY_DESCRIPTOR sd;
OBJECT_ATTRIBUTES oa;
UNICODE_STRING uniString;

status=FltBuildDefaultSecurityDescriptor(&sd,FLT_PORT_ALL_ACCESS);
RtlInitUnicodeString( &uniString, MINISPY_PORT_NAME );

//初始化对象属性
InitializeObjectAttributes( &oa,
&uniString,
OBJ_KERNEL_HANDLE|OBJ_CASE_INSENSITIVE,
NULL,
sd );

//内核建立通信端口
status = FltCreateCommunicationPort(gFilterHandle,&gServerPort,&oa,NULL,MiniConnect,MiniDisconnect,MiniMessage,1);
FltFreeSecurityDescriptor( sd );

其中 gFilterHandle 及注册minifilter返回的句柄,MiniConnect MiniDisconnect MiniMessage 为事件回调,在相应事件到达时触发。
相关函数
FltCloseClientPortMiniDisconnect 中关闭用户端口
FltSendMessage 向正在等待的用户层发送
FltCloseCommunicationPort 在驱动卸载时关闭监听端口

##小拓展
Ps.刚刚在看相关博客时就发现一个他写了个bug,网上的代码真的不要随便用

实际上之前用内核管道做过测试,这种异步IO的方式本身效率不高,并不适合频繁传输,
对频繁传输,还是推荐使用 IO方式中的其他方式,在同一进程上下文时,直接操作进程,并通过共享事件的方式做互相通知更为高效。

有人肯定会说,别人都是说线程上下文,为什么你说的是进程上下文?
能否直接操作r3进程内存,看的是当前是进程上下文,一个进程中的多个线程的上下文都可以被直接操作,只是说判断某个线程是否属于某个进程要麻烦一丢丢,并非在某个特定的线程上下文才能访问进程空间。
例如调用 同步的DeviceIoControl 的线程会陷入内核调用派遣函数,那么派遣函数就和 DeviceIoControl 在同一线程上下文,也就在同一进程上下文,此时是可以直接操作进程空间的。
一般可以认为在异步中,其上下文是不可控的,这是由于实现异步的交付者是不可控的,你要说是某进程内部实现的协程也是可以的。这里主要指系统级的异步,极有可能会在其他进程的上下文交付。

minifilter 动态安装卸载

网上随便找了一篇,并未测试,按照他的说法是在注册表中创建相关子键和键值

SYSTEM\\CurrentControlSet\\Services\\DriverName\\Instances
SYSTEM\\CurrentControlSet\\Services\\DriverName\\Instances\\DriverName Instance

话说回来,网上大多数流传已久的驱动加载方式本来就有点问题,会出现重启都卸载不干净的情况,编码不规范造成的,这块是个细节。

总结

本文是官方文档和众多参考文章汇集而成,由于 minifilter 并不需要像 sfilter 那样悉知细节,故相较之要简单许多。