原理
正常的IRP流程是R3 API调用时,会将请求封装成一个IRP经过IO管理器到达文件系统,然后在发往磁盘存储系统,最后到达硬件。
使用MiniFilter后会在IO栈中添加MiniFilter管理器。当IRP到达文件系统之前时,首先会被该管理器拦截。管理器会将IRP封装成CALLBACK_DATA,由管理器中存在的多个minifilter驱动依次处理。这些驱动位置是固定的,位置由altitude属性决定。处理结束后把结果返回给管理器,管理器根据结果决定是否把IRP继续下发。
MiniFilter可以有多个祯【多个管理器,每个管理器有多个minifilter驱动】IRP发给帧1,帧1处理后继续下发到Legacy Filter驱动【例如Sfilter】然后继续下发到帧0,最后下发到文件系统、磁盘存储系统最后到达硬件。
Minifilter管理器处理CALLBACK_DATA时会依次获取高altitude值的minifilter驱动的处理结果然后根据结果决定是否继续下发到低altitude值的minifilter驱动。
每一个minifilter驱动必须有Altitude这个唯一标识符。在加载时值越小相对于其他minifilter驱动在I/O栈中的位置越低。Altitude值取值范围在20000到429999。在320000到329999组中包含了在文件I/O期间探测杀毒的过滤驱动,140000到149999组中包含了在文件I/O期间加解密的过滤驱动。
使用
将Minfilter驱动往Minfilter框架中注册前需要先对驱动的结构体fileMonitorRegistration初始化,结构体类型为FLT_REGISTRATION。成员描述的有结构体大小、版本号、Flags值、各种函数等,其中ContextRegistration指向上下文管理的函数,fileMonitorCallbacks指向回调函数结构体数组【存放关于每个IRP处理的组函数】fileMonUnload指向用于卸载驱动的函数,fileMonInstanceSetup指向的函数在驱动绑定到卷设备时会被调用【驱动在启动时会遍历卷设备并为每个卷设备绑定自身的实例】,fileMonInstanceTeardownStart指向的函数在驱动从卷设备卸载时会被调用。
在fileMonitorCallbacks中根据每个IRP可注册一组【pre(处理前)post(处理后)】回调函数,不注册的由Minfilter管理器处理。结构体如下:
const FLT_OPERATION_REGISTRATION
fileMonitorCallbacks[] =
{
{
IRP_MJ_CREATE,
FLTFL_OPERATION_REGISTRATION_SKIP_PAGING_IO,
HOOK_PreNtCreateFile,
HOOK_PostNtCreateFile
},
{
IRP_MJ_CLEANUP,
0,
HOOK_PreNtCleanup,
NULL
},
{
IRP_MJ_WRITE,
0,
HOOK_PreNtWriteFile,
HOOK_PostNtWriteFile
},
{
IRP_MJ_SET_INFORMATION,
0,
HOOK_PreNtSetInformationFile,
HOOK_PostNtSetInformationFile
},
{
IRP_MJ_OPERATION_END
}
... ...
};
注册的函数参数基本类似,关于CreateFile的一组函数如下:
//______________________________pre:
FLT_PREOP_CALLBACK_STATUS
HOOK_PreNtCreateFile (
PFLT_CALLBACK_DATA Data,//IRP封装
PCFLT_RELATED_OBJECTS FltObjects,//相关对象,例如文件、卷设备、实例和设备对象
PVOID *CompletionContext//上下文对象,可用于让pre分配传给post操作
//分配的一个context资源
)
{
//sandbox?,主防?,杀毒引擎?,加解密?
return XXX;
}
//______________________________post:
FLT_POSTOP_CALLBACK_STATUS
HOOK_PostNtCreateFile (
PFLT_CALLBACK_DATA Data,
PCFLT_RELATED_OBJECTS FltObjects,
PVOID CompletionContext,
//在PRE-OP里返回 //FLT_PREOP_SUCCESS_WITH_CALLBACK
//时获取里面的上下文,并最后释放
FLT_POST_OPERATION_FLAGS Flags
)
{
return XXX;
}
//_______________________________end
函数返回值用于返回给MiniFilter管理器控制其操作
PRE的返回值有:
FLT_PREOP_SUCCESS_WITH_CALLBACK//继续下发,而且调用对应的post函数
FLT_PREOP_SUCCESS_NO_CALLBACK//继续下发,不需要调用post函数
FLT_PREOP_COMPLETE//终止下发,具体执行成功还是失败在CALLBACK_DATA中设置。例如成功处理:
//Data->IoStatus.Status=STATUS_ACCESS_DENIED;
//Data->IoStatus.Information=0;
//return FLT_PREOP_COMPLETE;
PRE其他的返回值还有:
FLT_PREOP_PENDING,
FLT_PREOP_DISALLOW_FASTIO,
FLT_PREOP_SYNCHRONIZE
POST的返回值有:
FLT_POSTOP_FINISHED_PROCESSING//无更多处理
FLT_POSTOP_MORE_PROCESSING_REQUIRED//需要做更多的处理,例如需要等待线程结束
读写缓冲区
Data->lopb->Parameters.Read/Write.MdlAddress/ReadBuffer/Length;
由于Data不仅处理IRP还处理FASTIO和文件过滤驱动,可以使用三个宏分别判定:
FLT_IS_IRP_OPERATION
FLT_IS_FASTIO_OPERATION
FLT_IS_FS_FILTER_OPERATION
安装与卸载
安装Minifilter过滤驱动有两种方式,.inf文件安装和代码动态加载
.inf文件安装需要准备一个与MiniFilter驱动关联的.inf文件,然后通过右键安装或SetupCopyOEMinf安装。之后系统会将驱动sys文件拷贝到C:\WINDOWS\SYSTEM32\Dirver目录下并在注册表中创建驱动服务(不会触发DirverEntry)这时需要在CMD中使用”net start/stop 驱动名称”启动或卸载驱动服务。
另一种就是通过动态代码安装。这种方式与NT驱动的驱动安装大同小异,不同在于CreateService设置Group为”FSFilter Acticity Moniter”,DependOnService值为”Fltmgr”,还要在SYSTEM\CurrentControlSet\Services\DriverName\Instances子健并在其下面建立驱动名称子健,再在新建的子健下设置Altitude的值。
初始化fileMonitorRegistration结构体后在DriverEntry中调用FltRegisterFilter注册并获取Minfilter的句柄然后使用FltStartFiltering启动即可:
FltRegisterFilter( DriverObject,&fileMonitorRegistration,&g_pFilter );
FltStartFiltering( g_pFilter );
FltUnregisterFilter( g_pFilter );//DirverUnload中使用该函数注销过滤设备
框架提供的操作方法
路径获取
//在postCreate中使用
PFLT_FILE_NAME_INFORMATION pNameInfo=NULL;//定义一个文件信息指针
FltGetFileNameInformation(Data,FLT_FILE_NAME_NORMALIZEDI|FLT_FILE_NAME_QUERY_DEFAULT,&pNameInfo);
//函数内部申请了内存并设置了引用计数,将文件信息保存到文件信息指针中。
FltParseFileNameInformation(pNameInfo);//解析文件信息,解析后文件地址依然为盘符。需要进一步转换
pNameInfo->Name;pNameInfo->Volume;
FltReleaseFileNameInformation(pNameInfo);//引用计数减一,最后释放内存
//IRP_MJ_SETINFO中重命名获取:
PFLT_FILE_NAME_INFORMATION pNameInfo=(PFLT_FILE_NAME_INFORMATION)Data->lopb->Parameters.SetFileInformation.InfoBuffer;
//或使用:
FltGetDestinationFileNameInformation//重命名获取
Parameters.SetFile.FileInformationClass == FileRenameInformation//判断setInformation为修改名称操作
监控进程创建
IRP_MJ_ACQUIRE_FOR_SECTION_SYNCHRONIZATION//该IRP会在进程创建时调用
Data->Iopb->Parameters.AcquireForSectionSynchronization.PageProtection == PAGE_EXECUTE//创建进程
文件操作
//与Zw系列函数对应,例如:
FltCreateFile、FltReadFile、FltWriteFile、FltClose、FltQueryXxx、FltSetXxx、FltGetXxx、FltPerformXxx
//该系列函数比Zw系列多了头两个参数:参1、参2为实例【MiniFilter实例或在FltObject->XXX实例,看情况选择】
在MiniFilter中不能使用Zw函数,否则可能的导致重入【再次调用zw函数,无限循环】
MiniFilter上下文
FltAllocateContext//申请
FltReleaseContext//释放【内部使用了引用计数】
//分配的类别
FltGet/Stream/StreamHandle/Instance/Volume/File Context
FltSet/Stream/StreamHandle/Instance/Volume/File Context
//分别为流【FCB】、流句柄【FO】、实例、卷、文件【vista以上】上下文。Get获取,Set设置。
Instance上下文例子:
typedef struct _INSTANCE_CONTEXT {
…//自己定义结构体内容
} INSTANCE_CONTEXT, *PINSTANCE_CONTEXT;
PINSTANCE_CONTEXT pContext = NULL;
//分配与设置
ntStatus = FltGetInstanceContext(FltObjects->Instance, & pContext);
if(NT_SUCCESS(Status) == FALSE)//如果还未分配上下文就申请一个
{
ntStatus = FltAllocateContext(g_pFilter,FLT_INSTANCE_CONTEXT,
sizeof(INSTANCE_CONTEXT),
PagedPool,& pContext);
if(NT_SUCCESS(Status) == FALSE)
{
return ntStatus ;
}
RtlZeroMemory(pContext, sizeof(INSTANCE_CONTEXT));
}
pContext->xxx = xxx;;//给自定义的结构成员赋值
FltSetInstanceContext(FltObjects->Instance,FLT_SET_CONTEXT_REPLACE_IF_EXISTS,pContext,NULL);//将内存设置到上下文中
if (pContext)
{
FltReleaseContext(pContext);//这里不使用了,所以直接减引用
}
//获取访问
PINSTANCE_CONTEXT pContext = NULL;
Status = FltGetInstanceContext(FltObjects->Instance,&pContext);
pContext->xxx = xxx;
注册卸载上下文的函数在驱动注册结构体fileMonitorRegistration的ContextRegistration成员中。内部保存了每个类型的上下文的清理函数数组。
R3R0端口通信
R3主动:
//首先R0创建端口,R3通过端口名称与R0通信。
FltCreateCommunicationPort( g_pFilter,
&g_pServerPort,&oa,//设置PORT的名字
NULL,//安全描述符,可传入&oa
fnConnectFromClient, //获得R3端口g_pClientPort等【R3请求链接时调用,函数内可获取R3端口以及R3进程信息】
fnDisconnectFromClient,//【断开链接时调用】
fnMessageFromClient, //处理从R3 FilterSendMessage的请求【R3发送数据后处理数据时调用】
1 );//R0创建端口
FltBuildDefaultSecurityDescriptor(&sd,FLT_PORT_ALL_ACCESS);//建立只允许管理员和系统访问的安全描述符
InitializeObjectAttributes(&oa,&portName,OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE,NULL,sd)//关联到oa
FltFreeSecurityDescriptor(sd);//使用后释放描述符
FltCloseClientPort//R0关闭R3端口
//R0处理函数
NTSTATUS fnMessageFromClient(
IN PVOID PortCookie,
IN PVOID InputBuffer OPTIONAL,
IN ULONG InputBufferLength,
OUT PVOID OutputBuffer OPTIONAL,
IN ULONG OutputBufferLength,
OUT PULONG ReturnOutputBufferLength)
{
__try
{
ProbeForRead(InputBuffer, InputBufferLength, sizeof(ULONG));//验证地址为R3地址
//获取InputBuffer
//Do something
ProbeForWrite(OutputBuffer, OutputBufferLength, sizeof(ULONG));//验证地址为R3地址
//将返回的值拷贝到Outputbuffer
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
return STATUS_NOT_IMPLEMENTED;
}
return STATUS_SUCCESS;
}
R3发送
//获取R0端口,参1为端口名称,参6接收端口
FilterConnectCommunicationPort( ScannerPortName,0,NULL,0,NULL,&Port );
FilterSendMessage(
Port,//R0端口
&request,//R3发给R0数据
sizeof(REQUEST),//R3发给R0数据长度
&reply,//R0返回给R3的结果
sizeof(REPLY),//R0返回给R3的结果的长度
&dwRtn//实际传输的字节数
);
R0主动
//发送消息给R3
timeout.QuadPart = (LONGLONG)40 * -10000000i64; // 40 秒
Status = FltSendMessage( g_pFilter,//MiniFilter全局句柄
&g_pClientPort,//R3端口
&request,//发送的数据
sizeof(SCANNER_NOTIFICATION),//数据的大小
&reply,//R3函数FilterReplyMessage返回的结果
&replySize,//返回值的长度
&timeout );//等待时间
FltCloseCommunicationPort( g_pServerPort ); //R0关闭端口
R3接收
completion = CreateIoCompletionPort( port,NULL,0,1);//创建完成端口
//函数是异步的,需要完成端口判断端口是否完成【数据是否收到】
FilterGetMessage(
Port,//拿数据的端口
&message->MessageHeader,//message为R0传上来的数据结构
FIELD_OFFSET( SANDBOX_MESSAGE, Ovlp ),
&message->Ovlp );
GetQueuedCompletionStatus(completion, &outSize, &key, &pOvlp, INFINITE );//永久等待
FilterReplyMessage(Port,
(PFILTER_REPLY_HEADER) &replyMessage,
sizeof( replyMessage ) );//R3返回消息到R0的FltSendMassage等待函数中