Minifilter笔记

Minfilter与legacy filter区别

比sfilter加载顺序更易控制.

altitude被绑定到合适的位置。 
Minfilter在注册表服务项中有一项Altitude值
此值越高位置越靠前 (待考证
每一个minifilter驱动必须有一个叫做altitude的唯一标识符.一个minifilter驱动的altitude定义了它加载时在I/O栈中相对其他minifilter驱动的位置。值越小,栈中位置就越低
FSFilter Anti-Virus 320000-329999 此组包括在文件I/O期间探测并杀毒的过滤驱动. 
FSFilter Encryption 140000-149999此组包括在文件I/O期间加密和解密数据的过滤驱动. 

可卸载能力.

Callback模型仅需处理必要操作的能力. 
不再需要给一个IRP配置一个完成例程,Minfilter每个过滤功能有2个回调函数,一个是“事前”回调(PreCallBack),一个是“事后”回调(PosCallBack)
相当于PosCallBack就是sfilter中的IRP完成例程
要调用PosCallBack只需要PreCallBack 返回 FLT_PREOP_SUCCESS_WITH_CALLBACK
而返回FLT_PREOP_SUCCESS_NO_CALLBACK则告诉系统,处理好这件事后不用调用PosCallBack了
一个相当于sfilter中的Cpy,一个是skip
阻止下发返回FLT_PREOP_COMPLETE

兼容性更好

名字处理更容易
FltGetFileNameInformation
只需要传入回调函数CALL_DATA data 和一个PFLT_FILE_NAME_INFORMATION指针就可以获得相关文件的信息 然后再调用
FltParseFileNameInformation就可以获得路径了
例子:(注意 获取路径需要在Pos中获取(即创建成功后才能获取到真实的数据))

//在postcreate里获得
 
 
PFLT_FILE_NAME_INFORMATION	pNameInfo = NULL;
 
 
ntStatus = FltGetFileNameInformation(Data,
		FLT_FILE_NAME_NORMALIZED| 
		FLT_FILE_NAME_QUERY_DEFAULT,
		&pNameInfo);
FltParseFileNameInformation(pNameInfo);
 
 
pNameInfo->Name.Buffer
pNameInfo->Volume
 
 
FltReleaseFileNameInformation(pNameInfo);

安装开可以.inf/动态加载

通信方式基于完成端口

同样遵循IRQL,锁等内核开发通信机制

有专门的函数,比如FltCreatefile,原来用的ZwCreateFile

总体框架

Minifilter笔记_第1张图片

 I/O管理器会把应用层IO请求封装成IRP包发给下层,首先发给MiniFilter管理器,(IRP在MiniFilter会发生重组,变成FLT_CALLBACK_DATA),把这个结构体传给MiniFilter驱动,以后就没IRP了,高度是注册表记录的。高度越高,优先处理。再给Sfilter驱动,再发给文件驱动,再发给磁盘驱动。

Altitude值

高度是注册表记录的。高度越高,优先处理。

FSFilter Anti-Virus 320000-329999 此组包括在文件I/O期间探测并杀毒的过滤驱动. 
FSFilter Encryption 140000-149999此组包括在文件I/O期间加密和解密数据的过滤驱动.

 

结构

const FLT_REGISTRATION fileMonitorRegistration = 
{
sizeof( FLT_REGISTRATION ),                 	//  Size,
FLT_REGISTRATION_VERSION,              			//  Version
0,                                         		//  Flags一般为0
ContextRegistration,                          	//  ContextRegistration //上下文数组,为每个文件对象开辟一个私有空间
fileMonitorCallbacks,                         	//  Operation callbacks//最重要的,每个回调函数都在这
fileMonUnload,                              	//  FilterUnload
fileMonInstanceSetup,                         	//  InstanceSetup。//实例,绑定卷设备对象时候发生 ,可以记录下卷设备属性
NULL,                               	   		//  InstanceQueryTeardown
fileMonInstanceTeardownStart,                  	//  InstanceTeardownStart,卸载的操作
NULL,                              				//  InstanceTeardownComplete
NULL,                               			//  GenerateFileName
NULL,                               			//  GenerateDestinationFileName
NULL                                			//  NormalizeNameComponent
};

Minifilter笔记_第2张图片

完成函数初始化之后,通过FLTRegisterFilter(DriverObject,&fileMonitorRegistration,&g_pFilter)注册,得到句柄pFilter,通过FltStartFiltering(g_pFilter)就启动了。

fileMonitorCallbacks 例子:
可以只需要一个回调如IRP_MJ_CLEANUP

const FLT_OPERATION_REGISTRATION 
fileMonitorCallbacks[] =
{
	{ 
		IRP_MJ_CREATE,
		FLTFL_OPERATION_REGISTRATION_SKIP_PAGING_IO,//这个是可以忽略的IRP
		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//这个是必须要加的
	}
};

然后中间比如也函数的例子

FLT_PREOP_CALLBACK_STATUS
HOOK_PreNtCreateFile (
PFLT_CALLBACK_DATA Data,//就是irp被封装成的那个
PCFLT_RELATED_OBJECTS FltObjects,//与MiniFilter相关的一些对象,volume,instance,FileObject,FileObject->DeviceObject
PVOID *CompletionContext //上下文资源,为函数分配缓存,一般用不到
//分配的一个context资源
)
{
	//sandbox?
	//主防??
	//杀毒引擎??
	//加解密??
	return XXX;//返回值,返回MiniFilter管理器的。决定是否下发比如pre-op操作
//FLT_PREOP_SUCCESS_WITH_CALLBACK,下发之后告诉post
//FLT_PREOP_SUCCESS_NO_CALLBACK
//FLT_PREOP_COMPLETE,完成之后不把callback_date下发
//post-op操作返回值
//FLT_POSTOP_FINISHED_PROCESSING
//FLT_POSTOP_MORE_PROCESSING_REQUIRED(比如IRQL较高,要在工作者线程操作)
}
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;
}

 另外可以通过FLT_IS_FASTIO_OPERATION判断是什么操作,fastio,irp,sfilter之类的操作

MiniFilter启动

NTSTATUS initFileMonitor (PDRIVER_OBJECT DriverObject )
{
return FltRegisterFilter( DriverObject,
		&fileMonitorRegistration,
		&g_pFilter );
}
 
 
 
 
NTSTATUS startFileMonitor( )
{
	if(g_pFilter)
		return FltStartFiltering( g_pFilter );
	return STATUS_INSUFFICIENT_RESOURCES;
}
 
 
VOID stopFileMonitor( )
{
	if(g_pFilter)
	{
		FltUnregisterFilter( g_pFilter );
		g_pFilter = NULL;
	}
}

.inf安装

inf文件安装minifilter
这个就是抄和改,要注意里面的ClassGUID和Class必须对上
这是查询网址
http://msdn.microsoft.com/en-us/library/windows/hardware/ff540394(v=vs.85).aspx

[Version]
Signature   = "$Windows NT$"
Class       = "ActivityMonitor"             ;This is determined by the work this filter driver does
ClassGuid   = {b86dff51-a31e-4bac-b3cf-e8cfe75c9fc2}    ;This value is determined by the Class
Provider    = %Msft%
DriverVer   = 06/16/2007,1.0.0.0
CatalogFile = nullfilter.cat


[DestinationDirs]
DefaultDestDir          = 12
NullFilter.DriverFiles  = 12            ;%windir%\system32\drivers

;;
;; Default install sections
;;

[DefaultInstall]
OptionDesc  = %ServiceDescription%
CopyFiles   = NullFilter.DriverFiles

[DefaultInstall.Services]
AddService  = %ServiceName%,,NullFilter.Service

;;
;; Default uninstall sections
;;

[DefaultUninstall]
DelFiles   = NullFilter.DriverFiles

[DefaultUninstall.Services]
DelService = %ServiceName%,0x200      ;Ensure service is stopped before deleting

;
; Services Section
;

[NullFilter.Service];注册表
DisplayName      = %ServiceName%
Description      = %ServiceDescription%
ServiceBinary    = %12%\%DriverName%.sys    ;%windir%\system32\drivers\
Dependencies     = "FltMgr";以来的服务
ServiceType      = 2                        ;SERVICE_FILE_SYSTEM_DRIVER
StartType        = 3                        ;SERVICE_DEMAND_START
ErrorControl     = 1                        ;SERVICE_ERROR_NORMAL
LoadOrderGroup   = "FSFilter Activity Monitor"
AddReg           = NullFilter.AddRegistry

;
; Registry Modifications
;

[NullFilter.AddRegistry];高度值
HKR,"Instances","DefaultInstance",0x00000000,%DefaultInstance%
HKR,"Instances\"%Instance1.Name%,"Altitude",0x00000000,%Instance1.Altitude%
HKR,"Instances\"%Instance1.Name%,"Flags",0x00010001,%Instance1.Flags%

;
; Copy Files
;

[NullFilter.DriverFiles]
%DriverName%.sys

[SourceDisksFiles]
nullfilter.sys = 1,,

[SourceDisksNames]
1 = %DiskId1%,,,

;;
;; String Section
;;

[Strings]
Msft                    = "Microsoft Corporation"
ServiceDescription      = "NullFilter mini-filter driver"
ServiceName             = "NullFilter"
DriverName              = "NullFilter"
DiskId1                 = "NullFilter Device Installation Disk"

;Instances specific information.
DefaultInstance         = "Null Instance"
Instance1.Name          = "Null Instance"
Instance1.Altitude      = "370020"
Instance1.Flags         = 0x1          ; Suppress automatic attachments


Minifilter笔记_第3张图片

就是安装,然后启动。通过net start 服务名,启动,或者通过API,InstallHinfSecton(),安装卸载。

或者通过代码安装(指定高度值,操作注册表)

 strcpy(szTempStr,"SYSTEM\\CurrentControlSet\\Services\\");
    strcat(szTempStr,lpszDriverName);
    strcat(szTempStr,"\\Instances");
    if(RegCreateKeyEx(HKEY_LOCAL_MACHINE,szTempStr,0,"",REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,&hKey,(LPDWORD)&dwData)!=ERROR_SUCCESS)
    {
        return FALSE;
    }
    // 注册表驱动程序的DefaultInstance 值 
    strcpy(szTempStr,lpszDriverName);
    strcat(szTempStr," Instance");
    if(RegSetValueEx(hKey,"DefaultInstance",0,REG_SZ,(CONST BYTE*)szTempStr,(DWORD)strlen(szTempStr))!=ERROR_SUCCESS)
    {
        return FALSE;
    }
    RegFlushKey(hKey);//刷新注册表
    RegCloseKey(hKey);
 

    //-------------------------------------------------------------------------------------------------------
    // SYSTEM\\CurrentControlSet\\Services\\DriverName\\Instances\\DriverName Instance子健下的键值项 
    //-------------------------------------------------------------------------------------------------------
    strcpy(szTempStr,"SYSTEM\\CurrentControlSet\\Services\\");
    strcat(szTempStr,lpszDriverName);
    strcat(szTempStr,"\\Instances\\");
    strcat(szTempStr,lpszDriverName);
    strcat(szTempStr," Instance");
    if(RegCreateKeyEx(HKEY_LOCAL_MACHINE,szTempStr,0,"",REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,&hKey,(LPDWORD)&dwData)!=ERROR_SUCCESS)
    {
        return FALSE;
    }
    // 注册表驱动程序的Altitude 值
    strcpy(szTempStr,lpszAltitude);
    if(RegSetValueEx(hKey,"Altitude",0,REG_SZ,(CONST BYTE*)szTempStr,(DWORD)strlen(szTempStr))!=ERROR_SUCCESS)
    {
        return FALSE;
    }
    // 注册表驱动程序的Flags 值
    dwData=0x0;
    if(RegSetValueEx(hKey,"Flags",0,REG_DWORD,(CONST BYTE*)&dwData,sizeof(DWORD))!=ERROR_SUCCESS)
    {
        return FALSE;
    }
    RegFlushKey(hKey);//刷新注册表
    RegCloseKey(hKey);

    return TRUE;

路径获取

//在postcreate里获得
 
 
PFLT_FILE_NAME_INFORMATION	pNameInfo = NULL;
 
 
ntStatus = FltGetFileNameInformation(Data,
		FLT_FILE_NAME_NORMALIZED| 
		FLT_FILE_NAME_QUERY_DEFAULT,
		&pNameInfo);//二级指针,函数内部分配内存
FltParseFileNameInformation(pNameInfo);
 
 
pNameInfo->Name.Buffer//是设备名,还要转符号链接名
pNameInfo->Volume
 
 
FltReleaseFileNameInformation(pNameInfo);//引用计数减1,到0释放内存
Flt

重命名获取

PFILE_RENAME_INFORMATION     
	pFileRenameInfomation = (PFILE_RENAME_INFORMATION)Data->Iopb->Parameters.SetFileInformation.InfoBuffer;  
	//或者
	FltGetDestinationFileNameInformation //重命名获得

 枚举驱动个数

MiniFilter:FltEnumerateFilters,返回过滤器对象(FLT_FILTER)的地址,根据过滤器,对象地址,加上偏移,获得各组回调函数。

进程创建

前面是hook NtCreateSection,其实NtCreateSection对应着一个

监控IRPIRP_MJ_ACQUIRE_FOR_SECTION_SYNCHRONIZATION在
Data->Iopb->Parameters.AcquireForSectionSynchronization.PageProtection == PAGE_EXECUTE(等于这个就是创建进程)
在x64可以用这个监控进程 不过不需要,不会触发patchguard

CALLBACK IRQL

pre操作在PASSIVE_LEVEL或者APC_LEVEL,通常在PASSIVE_LEVEL级别

post操作若果pre操作返回一个FLT_PREOP_SYNCHRONIZE,post肯定小于等于APC,跟pre操作同一个上下文级别,

如果post操作用来处理fastio,在PASSIVE_LEVEL级别。

post-create,也是PASSIVE_LEVEl

其他情况就不一定了,可能dis级别,就要开工作者线程了。

文件操作

在过滤驱动中,我们不能使用默认的文件操作,这会引起重入(Zw回去ssdt找nt,然后调用irp_mj_xx,然后到MiniFilter,如果在这用zw就有回到开始了)
Minfilter给我提供了专门的函数
FltCreateFile
FltReadFile
FltWriteFile
FltClose
FltQueryXxx
FltSetXxx
FltGetXxx
FltPerformXxx

ntStatus = FltCreateFile(pFilter,//创建MiniFilter是那个句柄
	pDstInstance,//data的Fltobject的instance
	&hDstFile,
	GENERIC_WRITE | SYNCHRONIZE,
	&objDstAttrib,
	&ioStatus,
	0,
	FILE_ATTRIBUTE_NORMAL,
	FILE_SHARE_READ | 
	FILE_SHARE_WRITE | 
	FILE_SHARE_DELETE,
	FILE_CREATE,
	CreateOptions,
	NULL,0,0);

Minfilter上下文

Context上下文:其实就是附着在某个对象上的一段数据,这段数据由自己定义:
FltAllocateContext(对象,类型比如(g_pFilter,FLT_INSTANCE_CONTEXT)实例上下文)
FltReleaseContext

因为是缓存,所以上下文可以加快速度

分类

 

Stream Context - 流上下文,也就是大家常用的FCB(File Control Block)的上下文,文件和FCB是一对一的关系;
FltGetStreamContext
FltSetStreamContext
Stream Handle Context - 流句柄上下文,也就是大家常见的FO(File Object)的上下文,一个文件可以对应多个FO,属一对多关系;
FltGetStreamHandleContext
FltSetStreamHandleContext
Instance Context - 实例上下文,也就是过滤驱动在文件系统的设备堆栈上创建的一个过滤器实例;跟卷上下文一一对应
FltGetInstanceContext
FltSetInstanceContext
Volume Context - 卷上下文,卷就是大家通常看到的C,D,E盘以及网络重定向器,一般情况下一个卷对应一个过滤器实例对象,在实际应用上经常用Instance Context来代替Volume Context。
FltGetVolumeContext 
FltSetVolumeContext
文件上下文(vista之后)
FltGetFileContext
FltSetFileContext
 

 上下文在注册框架结构体时候fileMonitorRegistration里,有个数组记录着上下文相关的一组回调函数ContextRegistration记录着上下文的清理函数。

引用计数为0,就自动释放,但是还要清理函数,因为上下文还要其他资源,比如申请的内存等,就像c++的析构函数只能清理自己一样(我记得好像也有个这个差不多的知识点)

PFLT_FILTER g_pFilter = NULL;
const FLT_CONTEXT_REGISTRATION 
ContextRegistration[] = 
{//在释放context之前调用,可以在此释放context里的内存等
{ 	
FLT_INSTANCE_CONTEXT,
0,
CtxContextCleanup,
CTX_INSTANCE_CONTEXT_SIZE,
CTX_INSTANCE_CONTEXT_TAG 
},
{	 
	FLT_FILE_CONTEXT,
0,
CtxContextCleanup,
CTX_FILE_CONTEXT_SIZE,
CTX_FILE_CONTEXT_TAG 
},
{ 	
	FLT_STREAM_CONTEXT,
0,
CtxContextCleanup,
CTX_STREAM_CONTEXT_SIZE,
CTX_STREAM_CONTEXT_TAG
 },
{	
	FLT_STREAMHANDLE_CONTEXT,
0,
CtxContextCleanup,
CTX_STREAMHANDLE_CONTEXT_SIZE,
CTX_STREAMHANDLE_CONTEXT_TAG
 },
{ FLT_CONTEXT_END }
};

context例子

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 STATUS_SUCCESS;
	}
	RtlZeroMemory(pContext, sizeof(INSTANCE_CONTEXT));// 清零
}
pContext ->m_DeviceType = VolumeDeviceType;
pContext->m_FSType = VolumeFilesystemType;
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;

R0与R3通信

 MiniFilter通信基于端口port

在R0,我们创建一个Port,R3在通信前会得到Port的句柄,我们就可以通过这个port进行通信了

R0创建端口代码

RtlInitUnicodeString( &uniString, ScannerPortName );
    //  We secure the port so only ADMINs & SYSTEM can acecss it.
    //
	//设置通信端口权限 ,只有管理员和系统进程才能操作
    status = FltBuildDefaultSecurityDescriptor( &sd, FLT_PORT_ALL_ACCESS );
    if (NT_SUCCESS( status )) {
        InitializeObjectAttributes( &oa,
                                    &uniString,
                                    OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
                                    NULL,
                                    sd );
		//创建通信端口,并设置对应的回调函数
        status = FltCreateCommunicationPort( ScannerData.Filter,
                                             &ScannerData.ServerPort,
                                             &oa,//设置的名字
                                             NULL,
                                             ScannerPortConnect,//当R3连接时回调 主要是记录R3的进程ID或EPROCESS以便放过本进程 还有记录R3的通信端口,给后面主动通信的时候用
                                             ScannerPortDisconnect,//当R3离线时回调 主要是关闭R3端口和设置R3的进程信息为NULL
                                             NULL,//处理R3主动函数 比如R3下新的规则,
                                             1 );//最后一个常为1
        //
        //  Free the security descriptor in all cases. It is not needed once
        //  the call to FltCreateCommunicationPort() is made.
        //
		//设置好后需要释放权限的设置
        FltFreeSecurityDescriptor( sd );
        //下面就是判断是否创建成功,成功后就开始开启过滤

R3主动发消息给R0,就是R0通过FilterConnectCommunicationPort创建端口,然后R3通过FilterSendMessage发消息,R0通过fnMessageFromClient接收消息。

filterSendMessage(
    Port,//内核端口
    &request,//传入信息
    sizeof(REQUEST),//信息大小
    &reply,//返回信息
    sizeof(REPLY),//返回大小
    &dwRtn//字节数
);
//内核层处理函数如下
fnMessageFromClient(
    PVOID PortCookie,
    PVOID InputBuffer OPTIONAL,
    ULONG InputBuffer InputBufferLength,
    PVOID OutputBuffer OPTIONAL,
    ULONG OutputBufferLength,
    PULONG ReturnOutputBufferLength
)
{

    __try{
        ProbeForRead(InputBuffer,InputBufferLength,sizeof(ULONG));
        //get inputbuffer and do something
        ProbeForWrite(OutBuffer,outputBufferLength,sizeof(ULONG));
        //copy result to out putbuffer

    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        return STATUS_NOT_IMPLEMENTED;
    }
    return STATUS_SUCCESS;
}

R0发给R3,通过FltSendMessage,可以指定超时时间

//发送消息给R3
timeout.QuadPart = (LONGLONG)40 * -10000000i64; // 内核等待 40 seconds
Status = FltSendMessage( g_pFilter,			&g_pClientPort,//给R3发消息			
						&request,			
						sizeof(SCANNER_NOTIFICATION),			
						&reply,			
						&replySize,			
						&timeout );

 R3通过FiterGetMessage得到消息,FilterReplyMessage将结果返回R0

那么R3是怎么处理R0发送到数据呢。

开线程

首先使用FilterConnectCommunicationPort 连接端口,基于这个端口创建完成端口,在完成端口上发一个异步读请求,然后进入循环,在GetQueuedCompletionStatus函数上监听阻塞,等到有数据时唤醒返回,Message就
有数据了,然后操作,然后将结果放在replymessage,FilterReplyMessage发送回R0,数据,这个函数也是阻塞让出cpu的。然后再次尝试getmessage,再次循环阻塞。

使用CreateIoCompletionPort 只需要注意第一个参数最后一个参数即可,最后一个参数:为这个工作线程设置几个线程
这样有助于高效率 一般小于64 ,也可以设置请求次数 这样回复也会高效率一些。

   FilterConnectCommunicationPort( ScannerPortName,//与R0的名字一致
                                         	       0,
		                          	       NULL,
                                         	       0,
                                         	       NULL,
                                         	       &port );//R0端口
 
 
//处理从R0来的请求,即R0调用FltSendMessage的请求
completion = CreateIoCompletionPort( port,NULL,0,1);
FilterGetMessage( Port,
			&message->MessageHeader,
			FIELD_OFFSET( SANDBOX_MESSAGE, Ovlp ),
			&message->Ovlp );
while(1)
{
GetQueuedCompletionStatus( lpContext->Completion, &outSize, &key, &pOvlp, INFINITE );
//过滤,扫描
FilterReplyMessage(Port,
			(PFILTER_REPLY_HEADER) &replyMessage,
			sizeof( replyMessage ) );
FilterGetMessage( Port,
			&message->MessageHeader,
			FIELD_OFFSET( SANDBOX_MESSAGE, Ovlp ),
			&message->Ovlp );
}

FILTER_REPLY_HEADER结构,里面有一项是固定的,有一项是可以自定义的,包括增加自己的结构
同样的Message对应的结构里面有一项也是可以自定义的 ,这要跟内核对应起来就可以了,内核里面只有自定义的那一项

//r0结构体
typedef struct _SCANNER_NOTIFICATION 
{
    ULONG BytesToScan;
    ULONG Reserved; 
    UCHAR Contents[SCANNER_READ_BUFFER_SIZE];
    
} SCANNER_NOTIFICATION, *PSCANNER_NOTIFICATION;
 
 
typedef struct _SCANNER_REPLY 
{
    BOOLEAN SafeToOpen;
} SCANNER_REPLY, *PSCANNER_REPLY;
//r3结构体
typedef struct _SCANNER_MESSAGE 
{
	FILTER_MESSAGE_HEADER MessageHeader;
	SCANNER_NOTIFICATION Notification;//可以自定义的 ,跟内核结构对应起来
	OVERLAPPED Ovlp;
} SCANNER_MESSAGE, *PSCANNER_MESSAGE;
 
 
typedef struct _SCANNER_REPLY_MESSAGE 
{
	FILTER_REPLY_HEADER ReplyHeader;
	SCANNER_REPLY Reply;//可以自定义的,跟内核结构对应起来
} SCANNER_REPLY_MESSAGE,
  *PSCANNER_REPLY_MESSAGE;

主动通信如上,需要注意应用程序可能已经退出了,带sys还在,那要怎么办呢,
R3的程序退出时,R0中那个断开连接的回调就会触发,我们需要在那个回调中设置用户通信端口为NULL
其它过滤函数中需要判断这个是不是NULL 不是NULL就通信,是NULL就放行

R3通信端口

其实这个是一个R3端口的句柄,当用户使用FilterConnectCommunicationPort建立连接时,
内核中那个连接函数就会被调用,在那里面就有用户的通信端口,

SCANNER Demo

MiniFilter的文件过滤驱动,对固定扩展名的文件过滤操作。比如做一些特殊的特征码的写操作进行拦截。

代码片段

driverEntry部分


    status = FltRegisterFilter( DriverObject,
                                &FilterRegistration,
                                &ScannerData.Filter );


    if (!NT_SUCCESS( status )) {

        return status;
    }

    //
    //  Create a communication port.
    //

    RtlInitUnicodeString( &uniString, ScannerPortName );

    //
    //  We secure the port so only ADMINs & SYSTEM can acecss it.
    //

    status = FltBuildDefaultSecurityDescriptor( &sd, FLT_PORT_ALL_ACCESS );//创建安全描述符,防止端口被非管理员用户打开

    if (NT_SUCCESS( status )) {

        InitializeObjectAttributes( &oa,
                                    &uniString,
                                    OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
                                    NULL,
                                    sd );

        status = FltCreateCommunicationPort( ScannerData.Filter,
                                             &ScannerData.ServerPort,
                                             &oa,
                                             NULL,
                                             ScannerPortConnect,
                                             ScannerPortDisconnect,
                                             NULL,//作业,补充
                                             1 );
        //
        //  Free the security descriptor in all cases. It is not needed once
        //  the call to FltCreateCommunicationPort() is made.
        //

        FltFreeSecurityDescriptor( sd );

        if (NT_SUCCESS( status )) {

            //
            //  Start filtering I/O.
            //

            status = FltStartFiltering( ScannerData.Filter );

            if (NT_SUCCESS( status )) {

                return STATUS_SUCCESS;
            }

            FltCloseCommunicationPort( ScannerData.ServerPort );
        }
    }

    FltUnregisterFilter( ScannerData.Filter );

    return status;

回调部分

const FLT_OPERATION_REGISTRATION Callbacks[] = {

    { IRP_MJ_CREATE,
      0,
      ScannerPreCreate,
      ScannerPostCreate},

    { IRP_MJ_CLEANUP,
      0,
      ScannerPreCleanup,
      NULL},

    { IRP_MJ_WRITE,
      0,
      ScannerPreWrite,
      NULL},

    { IRP_MJ_OPERATION_END}
};

FLT_PREOP_CALLBACK_STATUS
ScannerPreCreate (
    __inout PFLT_CALLBACK_DATA Data,
    __in PCFLT_RELATED_OBJECTS FltObjects,
    __deref_out_opt PVOID *CompletionContext
    )
{
    UNREFERENCED_PARAMETER( FltObjects );
    UNREFERENCED_PARAMETER( CompletionContext );

    PAGED_CODE();

    //
    //  See if this create is being done by our user process.
    //

    if (IoThreadToProcess( Data->Thread ) == ScannerData.UserProcess) {//拿到发送创建请求的Eprocess结构

        DbgPrint( "!!! scanner.sys -- allowing create for trusted process \n" );

        return FLT_PREOP_SUCCESS_NO_CALLBACK;
    }

    return FLT_PREOP_SUCCESS_WITH_CALLBACK;
}
FLT_POSTOP_CALLBACK_STATUS
ScannerPostCreate (
    __inout PFLT_CALLBACK_DATA Data,
    __in PCFLT_RELATED_OBJECTS FltObjects,
    __in_opt PVOID CompletionContext,
    __in FLT_POST_OPERATION_FLAGS Flags
    )
/*++

Routine Description:

    Post create callback.  We can't scan the file until after the create has
    gone to the filesystem, since otherwise the filesystem wouldn't be ready
    to read the file for us.

Arguments:

    Data - The structure which describes the operation parameters.

    FltObject - The structure which describes the objects affected by this
        operation.

    CompletionContext - The operation context passed fron the pre-create
        callback.

    Flags - Flags to say why we are getting this post-operation callback.

Return Value:

    FLT_POSTOP_FINISHED_PROCESSING - ok to open the file or we wish to deny
                                     access to this file, hence undo the open

--*/
{
    PSCANNER_STREAM_HANDLE_CONTEXT scannerContext;
    FLT_POSTOP_CALLBACK_STATUS returnStatus = FLT_POSTOP_FINISHED_PROCESSING;
    PFLT_FILE_NAME_INFORMATION nameInfo;
    NTSTATUS status;
    BOOLEAN safeToOpen, scanFile;

    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;
    }

    FltParseFileNameInformation( nameInfo );//w文件路径解析,扩展名,路径,等

    //
    //  Check if the extension matches the list of extensions we are interested in
    //

    scanFile = ScannerpCheckExtension( &nameInfo->Extension );//看扩展名是不是需要的

    //
    //  Release file name info, we're done with it
    //

    FltReleaseFileNameInformation( nameInfo );

    if (!scanFile) {

        //
        //  Not an extension we are interested in
        //

        return FLT_POSTOP_FINISHED_PROCESSING;
    }
//然后重新打开文件,交给应用层去扫描
    (VOID) ScannerpScanFileInUserMode( FltObjects->Instance,
                                       FltObjects->FileObject,
                                       &safeToOpen );

    if (!safeToOpen) {

        //
        //  Ask the filter manager to undo the create.
        //

        DbgPrint( "!!! scanner.sys -- foul language detected in postcreate !!!\n" );

        DbgPrint( "!!! scanner.sys -- undoing create \n" );

        FltCancelFileOpen( FltObjects->Instance, FltObjects->FileObject );

        Data->IoStatus.Status = STATUS_ACCESS_DENIED;
        Data->IoStatus.Information = 0;

        returnStatus = FLT_POSTOP_FINISHED_PROCESSING;

    } else if (FltObjects->FileObject->WriteAccess) {//写打开,所以要把信息保存到上下文里

        //
        //
        //  The create has requested write access, mark to rescan the file.
        //  Allocate the context.
        //

        status = FltAllocateContext( ScannerData.Filter,
                                     FLT_STREAMHANDLE_CONTEXT,//file_object,因为信息是
                                     sizeof(SCANNER_STREAM_HANDLE_CONTEXT),
                                     PagedPool,
                                     &scannerContext );

        if (NT_SUCCESS(status)) {

            //
            //  Set the handle context.
            //

            scannerContext->RescanRequired = TRUE;//关闭时候要重新扫描

            (VOID) FltSetStreamHandleContext( FltObjects->Instance,//关联
                                              FltObjects->FileObject,
                                              FLT_SET_CONTEXT_REPLACE_IF_EXISTS,
                                              scannerContext,
                                              NULL );

            //
            //  Normally we would check the results of FltSetStreamHandleContext
            //  for a variety of error cases. However, The only error status 
            //  that could be returned, in this case, would tell us that
            //  contexts are not supported.  Even if we got this error,
            //  we just want to release the context now and that will free
            //  this memory if it was not successfully set.
            //

            //
            //  Release our reference on the context (the set adds a reference)
            //

            FltReleaseContext( scannerContext );
        }
    }

    return returnStatus;
}

然后看下扫描代码

ScannerpScanFileInUserMode就是内核把信息读出来发给应用层去扫描

读请求相关操作

NTSTATUS
ScannerpScanFileInUserMode (
    __in PFLT_INSTANCE Instance,
    __in PFILE_OBJECT FileObject,
    __out PBOOLEAN SafeToOpen
    )
/*++

Routine Description:

    This routine is called to send a request up to user mode to scan a given
    file and tell our caller whether it's safe to open this file.

    Note that if the scan fails, we set SafeToOpen to TRUE.  The scan may fail
    because the service hasn't started, or perhaps because this create/cleanup
    is for a directory, and there's no data to read & scan.

    If we failed creates when the service isn't running, there'd be a
    bootstrapping problem -- how would we ever load the .exe for the service?

Arguments:

    Instance - Handle to the filter instance for the scanner on this volume.

    FileObject - File to be scanned.

    SafeToOpen - Set to FALSE if the file is scanned successfully and it contains
                 foul language.

Return Value:

    The status of the operation, hopefully STATUS_SUCCESS.  The common failure
    status will probably be STATUS_INSUFFICIENT_RESOURCES.

--*/

{
    NTSTATUS status = STATUS_SUCCESS;
    PVOID buffer = NULL;
    ULONG bytesRead;
    PSCANNER_NOTIFICATION notification = NULL;
    FLT_VOLUME_PROPERTIES volumeProps;
    LARGE_INTEGER offset;
    ULONG replyLength, length;
    PFLT_VOLUME volume = NULL;

    *SafeToOpen = TRUE;

    //
    //  If not client port just return.
    //

    if (ScannerData.ClientPort == NULL) {

        return STATUS_SUCCESS;
    }

    try {

        //
        //  Obtain the volume object .
        //

        status = FltGetVolumeFromInstance( Instance, &volume );//从instance里拿到卷设备对象

        if (!NT_SUCCESS( status )) {

            leave;
        }

        //
        //  Determine sector size. Noncached I/O can only be done at sector size offsets, and in lengths which are
        //  multiples of sector size. A more efficient way is to make this call once and remember the sector size in the
        //  instance setup routine and setup an instance context where we can cache it.
        //

        status = FltGetVolumeProperties( volume,//主要那大小
                                         &volumeProps,
                                         sizeof( volumeProps ),
                                         &length );
        //
        //  STATUS_BUFFER_OVERFLOW can be returned - however we only need the properties, not the names
        //  hence we only check for error status.
        //

        if (NT_ERROR( status )) {

            leave;
        }

        length = max( SCANNER_READ_BUFFER_SIZE, volumeProps.SectorSize );//1024和扇区最大值

        //
        //  Use non-buffered i/o, so allocate aligned pool
        //

        buffer = FltAllocatePoolAlignedWithTag( Instance,//分配内存读文件
                                                NonPagedPool,
                                                length,
                                                'nacS' );

        if (NULL == buffer) {

            status = STATUS_INSUFFICIENT_RESOURCES;
            leave;
        }

        notification = ExAllocatePoolWithTag( NonPagedPool,
                                              sizeof( SCANNER_NOTIFICATION ),//用来存放发给引用层文件数据的结构体
                                              'nacS' );

        if(NULL == notification) {

            status = STATUS_INSUFFICIENT_RESOURCES;
            leave;
        }

        //
        //  Read the beginning of the file and pass the contents to user mode.
        //

        offset.QuadPart = bytesRead = 0;
        status = FltReadFile( Instance,
                              FileObject,
                              &offset,
                              length,
                              buffer,
                              FLTFL_IO_OPERATION_NON_CACHED |
                               FLTFL_IO_OPERATION_DO_NOT_UPDATE_BYTE_OFFSET,
                              &bytesRead,//实际读的大小
                              NULL,
                              NULL );

        if (NT_SUCCESS( status ) && (0 != bytesRead)) {

            notification->BytesToScan = (ULONG) bytesRead;//实际读的大小放到结构体这个成员里

            //
            //  Copy only as much as the buffer can hold
            //

            RtlCopyMemory( ¬ification->Contents,
                           buffer,
                           min( notification->BytesToScan, SCANNER_READ_BUFFER_SIZE ) );

            replyLength = sizeof( SCANNER_REPLY );

            status = FltSendMessage( ScannerData.Filter,
                                     &ScannerData.ClientPort,
                                     notification,//request
                                     sizeof(SCANNER_NOTIFICATION),
                                     notification,//reply
                                     &replyLength,
                                     NULL );//等待时间,null是无限等待

            if (STATUS_SUCCESS == status) {

                *SafeToOpen = ((PSCANNER_REPLY) notification)->SafeToOpen;//告诉结果,直接强转成接受的结构

            } else {

                //
                //  Couldn't send message
                //

                DbgPrint( "!!! scanner.sys --- couldn't send message to user-mode to scan file, status 0x%X\n", status );
            }
        }

    } finally {

        if (NULL != buffer) {

            FltFreePoolAlignedWithTag( Instance, buffer, 'nacS' );
        }

        if (NULL != notification) {

            ExFreePoolWithTag( notification, 'nacS' );
        }

        if (NULL != volume) {

            FltObjectDereference( volume );
        }
    }

    return status;
}

demo中引用层直接用wdk写的,如果用vs编译,注意包含如下头文件和库,和库的文件夹

#include
 Windows Kits\10\Include\10.0.16299.0\um
#pragma comment(lib,"FltLib.lib)
    Windows Kits\10\lib\10.0.16299.0\um\x64

这是R0通过RtlSendMessage发给应用层,那么应用层如何接受R0的数据,如下


//应用层接受两个命令行参数可以0,或者reqno(5) threadno(2,Max64)//第一个参数一个线程可以请求处理的个数,第二个线程数,最小2,最大64
int _cdecl
main (
    __in int argc,
    __in_ecount(argc) char *argv[]
    )
{
    DWORD requestCount = SCANNER_DEFAULT_REQUEST_COUNT;//线程数之类的默认在这几个
    DWORD threadCount = SCANNER_DEFAULT_THREAD_COUNT;
    HANDLE threads[SCANNER_MAX_THREAD_COUNT];
    SCANNER_THREAD_CONTEXT context;
    HANDLE port, completion;
    PSCANNER_MESSAGE msg;
    DWORD threadId;
    HRESULT hr;
    DWORD i, j;

    //
    //  Check how many threads and per thread requests are desired.
    //

    if (argc > 1) {

        requestCount = atoi( argv[1] );

        if (requestCount <= 0) {

            Usage();
            return 1;
        }

        if (argc > 2) {

            threadCount = atoi( argv[2] );
        }

        if (threadCount <= 0 || threadCount > 64) {

            Usage();
            return 1;
        }
    }

    //
    //  Open a commuication channel to the filter
    //

    printf( "Scanner: Connecting to the filter ...\n" );

    hr = FilterConnectCommunicationPort( ScannerPortName,
                                         0,
                                         NULL,
                                         0,
                                         NULL,
                                         &port );

    if (IS_ERROR( hr )) {

        printf( "ERROR: Connecting to filter port: 0x%08x\n", hr );
        return 2;
    }

    //
    //  Create a completion port to associate with this handle.
    //

    completion = CreateIoCompletionPort( port,
                                         NULL,
                                         0,
                                         threadCount );

    if (completion == NULL) {

        printf( "ERROR: Creating completion port: %d\n", GetLastError() );
        CloseHandle( port );
        return 3;
    }

    printf( "Scanner: Port = 0x%p Completion = 0x%p\n", port, completion );

    context.Port = port;
    context.Completion = completion;

    //
    //  Create specified number of threads.
    //

    for (i = 0; i < threadCount; i++) {

        threads[i] = CreateThread( NULL,
                                   0,
                                   ScannerWorker,
                                   &context,//存着端口和完成端口
                                   0,
                                   &threadId );

        if (threads[i] == NULL) {

            //
            //  Couldn't create thread.
            //

            hr = GetLastError();
            printf( "ERROR: Couldn't create thread: %d\n", hr );
            goto main_cleanup;
        }

        for (j = 0; j < requestCount; j++) {//内层循环是每个线程处理的请求个数的每个

            //
            //  Allocate the message.
            //

#pragma prefast(suppress:__WARNING_MEMORY_LEAK, "msg will not be leaked because it is freed in ScannerWorker")
            msg = malloc( sizeof( SCANNER_MESSAGE ) );//每个请求分配一结构体

            if (msg == NULL) {

                hr = ERROR_NOT_ENOUGH_MEMORY;
                goto main_cleanup;
            }

            memset( &msg->Ovlp, 0, sizeof( OVERLAPPED ) );

            //
            //  Request messages from the filter driver.
            //

            hr = FilterGetMessage( port,//发送一个异步读请求
                                   &msg->MessageHeader,
                                   FIELD_OFFSET( SCANNER_MESSAGE, Ovlp ),
                                   &msg->Ovlp );

            if (hr != HRESULT_FROM_WIN32( ERROR_IO_PENDING )) {

                free( msg );
                goto main_cleanup;
            }
        }
    }

    hr = S_OK;

    WaitForMultipleObjectsEx( i, threads, TRUE, INFINITE, FALSE );

main_cleanup:

    printf( "Scanner:  All done. Result = 0x%08x\n", hr );

    CloseHandle( port );
    CloseHandle( completion );

    return hr;
}

 线程回调片段如下

DWORD
ScannerWorker(
    __in PSCANNER_THREAD_CONTEXT Context
    )
/*++

Routine Description

    This is a worker thread that


Arguments

    Context  - This thread context has a pointer to the port handle we use to send/receive messages,
                and a completion port handle that was already associated with the comm. port by the caller

Return Value

    HRESULT indicating the status of thread exit.

--*/
{
    PSCANNER_NOTIFICATION notification;
    SCANNER_REPLY_MESSAGE replyMessage;
    PSCANNER_MESSAGE message;
    LPOVERLAPPED pOvlp;
    BOOL result;
    DWORD outSize;
    HRESULT hr;
    ULONG_PTR key;

#pragma warning(push)
#pragma warning(disable:4127) // conditional expression is constant

    while (TRUE) {

#pragma warning(pop)

        //
        //  Poll for messages from the filter component to scan.
        //

        result = GetQueuedCompletionStatus( Context->Completion, &outSize, &key, &pOvlp, INFINITE );//接受完成端口的数据,内核层发出信息,这里完成端口就会有信号
        //
        //  Obtain the message: note that the message we sent down via FltGetMessage() may NOT be
        //  the one dequeued off the completion queue: this is solely because there are multiple
        //  threads per single port handle. Any of the FilterGetMessage() issued messages can be
        //  completed in random order - and we will just dequeue a random one.
        //

        message = CONTAINING_RECORD( pOvlp, SCANNER_MESSAGE, Ovlp );//从链表得到数据,message就是内核中发的数据notification

        if (!result) {

            //
            //  An error occured.
            //

            hr = HRESULT_FROM_WIN32( GetLastError() );
            break;
        }

        printf( "Received message, size %d\n", pOvlp->InternalHigh );

        notification = &message->Notification;

        assert(notification->BytesToScan <= SCANNER_READ_BUFFER_SIZE);
        __analysis_assume(notification->BytesToScan <= SCANNER_READ_BUFFER_SIZE);

		//这个地方,可以修改成弹窗的代码:result=PopupWindow(notification);
        result = ScanBuffer( notification->Contents, notification->BytesToScan );//扫描,这里也可以弹窗

        replyMessage.ReplyHeader.Status = 0;
        replyMessage.ReplyHeader.MessageId = message->MessageHeader.MessageId;

        //
        //  Need to invert the boolean -- result is true if found
        //  foul language, in which case SafeToOpen should be set to false.
        //

        replyMessage.Reply.SafeToOpen = !result;

        printf( "Replying message, SafeToOpen: %d\n", replyMessage.Reply.SafeToOpen );

        hr = FilterReplyMessage( Context->Port,
                                 (PFILTER_REPLY_HEADER) &replyMessage,
                                 sizeof( replyMessage ) );
		//这个时候,执行完上面函数后,驱动中FltSendMessage()返回

        if (SUCCEEDED( hr )) {

            printf( "Replied message\n" );

        } else {

            printf( "Scanner: Error replying message. Error = 0x%X\n", hr );
            break;
        }

        memset( &message->Ovlp, 0, sizeof( OVERLAPPED ) );

        hr = FilterGetMessage( Context->Port,
                               &message->MessageHeader,
                               FIELD_OFFSET( SCANNER_MESSAGE, Ovlp ),
                               &message->Ovlp );

        if (hr != HRESULT_FROM_WIN32( ERROR_IO_PENDING )) {

            break;
        }
    }

    if (!SUCCEEDED( hr )) {

        if (hr == HRESULT_FROM_WIN32( ERROR_INVALID_HANDLE )) {

            //
            //  Scanner port disconncted.
            //

            printf( "Scanner: Port is disconnected, probably due to scanner filter unloading.\n" );

        } else {

            printf( "Scanner: Unknown error occured. Error = 0x%X\n", hr );
        }
    }

    free( message );

    return hr;
}

里面的遍历文件代码如下

BOOL
ScanBuffer (
    __in_bcount(BufferSize) PUCHAR Buffer,
    __in ULONG BufferSize
    )
/*++

Routine Description

    Scans the supplied buffer for an instance of FoulString.

    Note: Pattern matching algorithm used here is just for illustration purposes,
    there are many better algorithms available for real world filters

Arguments

    Buffer      -   Pointer to buffer
    BufferSize  -   Size of passed in buffer

Return Value

    TRUE        -    Found an occurrence of the appropriate FoulString
    FALSE       -    Buffer is ok

--*/
{
    PUCHAR p;
    ULONG searchStringLength = sizeof(FoulString) - sizeof(UCHAR);

    for (p = Buffer;
         p <= (Buffer + BufferSize - searchStringLength);
         p++) {

        if (RtlEqualMemory( p, FoulString, searchStringLength )) {

            printf( "Found a string\n" );

            //
            //  Once we find our search string, we're not interested in seeing
            //  whether it appears again.
            //

            return TRUE;
        }
    }

    return FALSE;
}

 关闭操作

再看下关闭操作。就是处理关闭句柄

FLT_PREOP_CALLBACK_STATUS
ScannerPreCleanup (
    __inout PFLT_CALLBACK_DATA Data,
    __in PCFLT_RELATED_OBJECTS FltObjects,
    __deref_out_opt PVOID *CompletionContext
    )
/*++

Routine Description:

    Pre cleanup callback.  If this file was opened for write access, we want
    to rescan it now.

Arguments:

    Data - The structure which describes the operation parameters.

    FltObject - The structure which describes the objects affected by this
        operation.

    CompletionContext - Output parameter which can be used to pass a context
        from this pre-cleanup callback to the post-cleanup callback.

Return Value:

    Always FLT_PREOP_SUCCESS_NO_CALLBACK.

--*/
{
    NTSTATUS status;
    PSCANNER_STREAM_HANDLE_CONTEXT context;
    BOOLEAN safe;

    UNREFERENCED_PARAMETER( Data );
    UNREFERENCED_PARAMETER( CompletionContext );

    status = FltGetStreamHandleContext( FltObjects->Instance,//这里直接获取上下文因为FileObject里面记录着写关闭信息
                                        FltObjects->FileObject,
                                        &context );

    if (NT_SUCCESS( status )) {

        if (context->RescanRequired) {//写关闭,在打开时候就写入参数了

            (VOID) ScannerpScanFileInUserMode( FltObjects->Instance,//
                                               FltObjects->FileObject,
                                               &safe );

            if (!safe) {

                DbgPrint( "!!! scanner.sys -- foul language detected in precleanup !!!\n" );//这里只是警告,一般应该隔离
            }
        }

        FltReleaseContext( context );//getset都记得要release
    }


    return FLT_PREOP_SUCCESS_NO_CALLBACK;
}

写请求 

FLT_PREOP_CALLBACK_STATUS
ScannerPreWrite (
    __inout PFLT_CALLBACK_DATA Data,
    __in PCFLT_RELATED_OBJECTS FltObjects,
    __deref_out_opt PVOID *CompletionContext
    )
/*++

Routine Description:

    Pre write callback.  We want to scan what's being written now.

Arguments:

    Data - The structure which describes the operation parameters.

    FltObject - The structure which describes the objects affected by this
        operation.

    CompletionContext - Output parameter which can be used to pass a context
        from this pre-write callback to the post-write callback.

Return Value:

    Always FLT_PREOP_SUCCESS_NO_CALLBACK.

--*/
{
    FLT_PREOP_CALLBACK_STATUS returnStatus = FLT_PREOP_SUCCESS_NO_CALLBACK;
    NTSTATUS status;
    PSCANNER_NOTIFICATION notification = NULL;
    PSCANNER_STREAM_HANDLE_CONTEXT context = NULL;
    ULONG replyLength;
    BOOLEAN safe = TRUE;
    PUCHAR buffer;

    UNREFERENCED_PARAMETER( CompletionContext );

    //
    //  If not client port just ignore this write.
    //

    if (ScannerData.ClientPort == NULL) {

        return FLT_PREOP_SUCCESS_NO_CALLBACK;
    }

    status = FltGetStreamHandleContext( FltObjects->Instance,
                                        FltObjects->FileObject,
                                        &context );

    if (!NT_SUCCESS( status )) {//没拿到上下文放行,说明不是我们过滤类型的文件

        //
        //  We are not interested in this file
        //

        return FLT_PREOP_SUCCESS_NO_CALLBACK;

    }

    //
    //  Use try-finally to cleanup
    //

    try {

        //
        //  Pass the contents of the buffer to user mode.
        //

        if (Data->Iopb->Parameters.Write.Length != 0) {//写的长度

            //
            //  Get the users buffer address.  If there is a MDL defined, use
            //  it.  If not use the given buffer address.
            //

            if (Data->Iopb->Parameters.Write.MdlAddress != NULL) {//direct_io

                buffer = MmGetSystemAddressForMdlSafe( Data->Iopb->Parameters.Write.MdlAddress,
                                                       NormalPagePriority );//如果是direct_io重新映射物理地址为内核地址

                //
                //  If we have a MDL but could not get and address, we ran out
                //  of memory, report the correct error
                //

                if (buffer == NULL) {

                    Data->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
                    Data->IoStatus.Information = 0;
                    returnStatus = FLT_PREOP_COMPLETE;
                    leave;
                }

            } else {//否则直接拿buffer内容

                //
                //  Use the users buffer
                //

                buffer  = Data->Iopb->Parameters.Write.WriteBuffer;
            }

            //
            //  In a production-level filter, we would actually let user mode scan the file directly.
            //  Allocating & freeing huge amounts of non-paged pool like this is not very good for system perf.
            //  This is just a sample!
            //

            notification = ExAllocatePoolWithTag( NonPagedPool,//分配notification结构为发给应用层做准备
                                                  sizeof( SCANNER_NOTIFICATION ),
                                                  'nacS' );
            if (notification == NULL) {

                Data->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
                Data->IoStatus.Information = 0;
                returnStatus = FLT_PREOP_COMPLETE;
                leave;
            }

            notification->BytesToScan = min( Data->Iopb->Parameters.Write.Length, SCANNER_READ_BUFFER_SIZE );

            //
            //  The buffer can be a raw user buffer. Protect access to it
            //

            try  {

                RtlCopyMemory( ¬ification->Contents,
                               buffer,
                               notification->BytesToScan );

            } except( EXCEPTION_EXECUTE_HANDLER ) {

                //
                //  Error accessing buffer. Complete i/o with failure
                //

                Data->IoStatus.Status = GetExceptionCode() ;
                Data->IoStatus.Information = 0;
                returnStatus = FLT_PREOP_COMPLETE;
                leave;
            }

            //
            //  Send message to user mode to indicate it should scan the buffer.
            //  We don't have to synchronize between the send and close of the handle
            //  as FltSendMessage takes care of that.
            //

            replyLength = sizeof( SCANNER_REPLY );

            status = FltSendMessage( ScannerData.Filter,
                                     &ScannerData.ClientPort,
                                     notification,
                                     sizeof( SCANNER_NOTIFICATION ),
                                     notification,
                                     &replyLength,
                                     NULL );

            if (STATUS_SUCCESS == status) {

               safe = ((PSCANNER_REPLY) notification)->SafeToOpen;

           } else {

               //
               //  Couldn't send message. This sample will let the i/o through.
               //

               DbgPrint( "!!! scanner.sys --- couldn't send message to user-mode to scan file, status 0x%X\n", status );
           }
        }

        if (!safe) {

            //
            //  Block this write if not paging i/o (as a result of course, this scanner will not prevent memory mapped writes of contaminated
            //  strings to the file, but only regular writes). The effect of getting ERROR_ACCESS_DENIED for many apps to delete the file they
            //  are trying to write usually.
            //  To handle memory mapped writes - we should be scanning at close time (which is when we can really establish that the file object
            //  is not going to be used for any more writes)
            //

            DbgPrint( "!!! scanner.sys -- foul language detected in write !!!\n" );

            if (!FlagOn( Data->Iopb->IrpFlags, IRP_PAGING_IO )) {

                DbgPrint( "!!! scanner.sys -- blocking the write !!!\n" );

                Data->IoStatus.Status = STATUS_ACCESS_DENIED;
                Data->IoStatus.Information = 0;
                returnStatus = FLT_PREOP_COMPLETE;
            }
        }

    } finally {

        if (notification != NULL) {

            ExFreePoolWithTag( notification, 'nacS' );
        }

        if (context) {

            FltReleaseContext( context );
        }
    }

    return returnStatus;
}

 

参考资料

麦洛克菲课程

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(windows,驱动开发,内核)