minifilter是sfilter后微软推出的过滤驱动框架。相比于sfilter,他更容易使用,需要程序员做的编码更简洁。
系统为minifilter专门制作了一个过滤管理器,这个管理器本身其实是一个传统过滤驱动,它向minifilter的使用者提供许多接口,让原本复杂的文件过滤驱动变得方便简单。之所以简单是因为传统的过滤驱动把大量的工作放在绑定设备上,而现在这些工作都交给minifilter中的过滤管理器来完成。
缺点:纯粹的使用minifilter提供的接口看不见设备对象和IRP的,所以编程自由度不大。
对minifilter的编写的第一步是向过滤管理器注册一个微过滤器,这个未过滤器是一个组件,包含了一些在文件操作的时候可能需要的回调函数。
驱动入口中最简单的版本是只包含两个函数:注册函数和开始函数。
- NTSTARTUS DriverEntry(__in PDRIVER_OBJECT DriverObject,__in PUNICODE_STRING RegistryPath)
- {
- NTSTATUS status;
- UNREFERENCED_PARAMETER(registryPath);
-
- status = FltRegisterFilter(DriverObject,&FilterRegisteraion,&gFilterHandle);
- ASSERT(NT_SUCCESS(status));
- if(NT_SUCCESS(status)){
- status = FltStartFiltering(gFilterHandle);
- if(!NT_SUCCESSS(status)){
- FltUnregisterFilter(gFilterHandle);
- }
- }
- return status;
- }
1.注册函数FltRegisterFilter
它的第二个参数是个结构体要自己填写完整,这个结构体中的内容包含微过滤器的方方面面。
上面说的结构名字叫FLT_REGISTRATION,这个结构中有些部分是基本信息,有些部分是回调函数。所以这个结构有点面向对象语言中类的感觉。这个结构中最NB(中文意思是“重要”)的字段就是FLT_OPERATION_REGISTRATION(大家都知道,当操作系统要对文件系统进行操作的时候,其实就是通过发送各种各样的事件请求(IRP),而文件系统就负责处理好这些请求),而字段FLT_OPERATION_REGISTRATION就是一个事件请求数组(这个数组包含该过滤驱动可以处理的所有事件),每个数组元素本身又是一个五元组,包括1.当前要处理的事件,2.标识位(这个标志位一般写0,表示全过滤,还有两种值分别是不过滤缓存读写和不过滤页读写操作),3.事件被处理前函数(Pre-operation),4.事件被处理完成后的函数(Post-operation)。
【关于过滤函数:现在的过滤函数是有标准格式的(??是吗?),因为现在访问不到IRP,所以minifilter设置了一个结构用来存放请求包中的所有信息,在个结构往往是过滤函数的第一个参数。。。。传统的过滤驱动,和对应的请求相绑定的直接处理函数的格式好像也是固定的。。。想自定义处理函数格式也行,就要封装在第一级处理函数里面。】
第一个参数是知道我们自己的这个驱动的驱动对象。这个对象是我们的驱动在初始化的时候,系统帮我们生成的。
第三个参数是一个输出值,当我们的驱动成功的注册的时候,这个参数就是指向我们的微过滤驱动的句柄。
2.开始函数FltStartFiltering
就一个参数——过滤驱动的句柄,就是上面注册函数的输出值。
自己编写设备过滤驱动,说白了就是自己编写想要过滤的事件的预处理(pre-operation)或者完成(post-operation)函数。就像上面介绍的,对应每一个事件的预处理和完成函数都在结构FLT_REGISTRATION中的FLT_OPERATION_REGISTRATION结构里声明好的。
所以我们只要编写出每一个在FLT_OPERATION_REGISTRATION中给出的预处理和完成函数的函数体就行了。
比如在FLT_OPERATION_REGISTRATION中我们写了一个字段{IRP_IO_CREATE,0,NPPreCreate,NPPostCreate}。这里我们就针对事件IRP_IO_CREATE设置了这个事件的预处理函数NPPreCreate和完成函数NPPostCreate。
作为简单的例子,下面实现NPPreCreate:
- FLT_PREOP_CALLBACK_STATUS NPPreCreate(
- __inout PFLT_CALLBACK_DATA Data,
- __in PCFLT_RELATED_OBJECTS FltObjects,
- __deref_out_opt PVOID *CompletionContext
- )
- {
- ...
- Data->IoStatus.Status = STATUS_ACCESS_DENIED;
- Data->IoStatus.Information = 0;
- return FLT_PREOP_COMPLETE;
- }
这里就是一个过滤函数的框架,先看下这个函数的参数表。第一个参数是最重要的,PFLT_CALLBACK_DATA Data。对于接触过传统sfilter框架过滤驱动的朋友一定知道,在我们的过滤驱动过滤到上层发来的请求时,拦截到的就是一个IRP请求包,这个包中有这一次请求的几乎所有信息。现在在MINIFILTER框架中,眼前的这个参数就是当年的IRP,只是变了名字,我们操作时需要的(IRP)信息都在这个结构里。
一次文件请求操作的基本原理:
对于没接触过文件过滤驱动的朋友,这里简单说下文件过滤驱动的工作原理。当上层发生了某些文件系统请求的时候,这个请求会打包成IRP,发给底层的文件系统处理,当任务处理完的时候,文件系统会把任务完成情况打成IRP包回发给上层,通知上层请求被处理了。当我们把自己编写的过滤驱动安装到文件系统上后,我们的过滤驱动就可以拦截到IRP。这个时候,我们可以打开看看IRP里的东西,做点记录,然后什么都不改变的把IRP继续往下发,当然,也可以直接填写IRP中的一些字段然后直接回发给上层,上层收到IRP后就认为上一次的请求结束了,它根本不管这个IRP是不是文件系统发给他的。
所以大家可以看到上面的例子函数里,填写了PFLT_CALLBACK_DATA结构中的两个字段。最后的返回值FLT_PREOP_COMPLETE就表示请求已经结束了,不用再把IRP往下传了。
在minifilter中,我们除了要自己编写希望过滤的请求的预处理和完成函数。还有其他的回调函数可以编写,不过他们都是可选的,不一定要实现。具体有哪些这里就不列举了。
MINIFILTER的另一个特色:与应用层的通信
我们在编写文件过滤驱动的时候,不免会经常和上层的应用程序进行通信,在传统的文件过滤驱动框架中,想要实现驱动层和应用层的通信一般都是通过DiviceIoControl配合内核模块中的处理控制请求,或者申请一块共享存储区实现,都比较麻烦。minifilter中专门提供了通信用的API,并且这种API在使用的时候,就像是网络编程中的socket通信一样方便简单。
下面的代码是建立一个通信端口的过程:
- PSECURITY_DESCRIPTOR sd;
- OBJECT_ATTRIBUTES oa;
-
- status = FltBuildDefaultSecurityDescriptor(&sd,FLT_PORT_ALL_ACCESS);
-
- if(!NT_SUCCESS(status)){
- goto final;
- }
- RtlInitUnicodeString(&uniString,MINISPY_PORT_NAME);
-
- InitializeObejectAttributes(&oa,&uniString,OBJ_KERNEL_HANDLE|OBJ_CASE_INSENSITIVE,NULL,sd);
-
- status = FltCreateCommunicationPort(gFilterHandle,&gServerPort,&oa,NULL,NPMiniConnect,NPMiniDisconnect,NPMiniMessage,1);
首先函数FltBuildDefaultSecurityDescriptor是用来申请一个安全叙述子(额,简单点,就是给使用通信端口的用户申请个权限,这里可以看到申请的权限是FLT_PORT_ALL_ACCESS,意思就是:用户层的程序连接到设置了这个权限级别的通信端口后,可以获得所有操作权限)。
下面的InitializeObejectAttributes就是用来给我们要创建的对象(名称是:MINISPY_PORT_NAME)设置一些属性值。
最后最重要的FltCreateCommunicationPort就是给这个端口定义所需要的三个函数,同时注册这个端口(注册了才能用)。这里注册的三个函数分别是:
NPMiniConnect用户层和内核层建立连接的时候会调用该函数
NPMiniDisconnect用户层和内核层断开连接的时候会调用该函数
NPMiniMessage用户层和内核层进行通讯的时候会调用
当然,既然称他们为回调函数,那他们就不是我们用户层的程序去调用的,工作原理是这样的,我们在用户层通过两个api:FilterConnectCommunicationPort和FilterSendMessage来发出请求,然后通讯端口会根据我们的请求自己去调用这三个函数完成具体的工作。其中前者对应NPMiniConnect,后者对应NPMiniMessage。
完成上面三个回调函数后,内核中的通讯代码已经准备好了。
下面就是完成通讯的另一端内容:用户层(应用层)。这里要注意的是在编写程序的时候,需要包含几个minifilter必须的头文件和库文件:FltUser.h,fltLib.lib,fltMgr.lib,具体如下:
#include <FltUser.h>
#progma comment(lib,"fltLib.lib")
#progma comment(lib,"fltMgr.lib")
后面的代码需要了解的就是一个简单的流程:先FilterConnectCommunicationPort连接上目标端口,再FilterSendMessage发送消息。原理就介绍这么多,具体的代码在下载连接中,其中,用户层和通讯端口的连接是做成了dll,首先要生成dll,然后要使用dll就用哪个app工程,里面会装载dll。另一个minifilter可以生成一个sys系统文件,把系统文件用inf文件安装上,并在cmd里面输入net start XXX(这里是sys的文件名,其实是服务名,只不过两个名称大部分情况下是一样的)通讯端口就打开了,然后app就可以试着连接和通讯了。