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期间加密和解密数据的过滤驱动.

图片2

可卸载能力.

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

//重命名的获得:

PFILE_RENAME_INFORMATION
    pFileRenameInfomation = (PFILE_RENAME_INFORMATION)Data->Iopb->Parameters.SetFileInformation.InfoBuffer;

FltGetDestinationFileNameInformation //重命名获得

安装方式(.inf/动态加载)

通信方式(port)

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

FltCreateFile

Minfilter架构

结构

在DriverEntry中只需要注册Fliter和Start

FltRegisterFilter( DriverObject,
		&fileMonitorRegistration,
		&g_pFilter );
FltStartFiltering( g_pFilter );

fileMonitorRegistration是唯一我们需要做的

这是一个FLT_REGISTRATION 结构

const FLT_REGISTRATION fileMonitorRegistration =
{
sizeof( FLT_REGISTRATION ),                 	//  Size
FLT_REGISTRATION_VERSION,              	//  Version
0,                                         		//  Flags
ContextRegistration,                          	//  ContextRegistration //上下文数组
fileMonitorCallbacks,                         		//  Operation callbacks//最重要的
fileMonUnload,                              		//  FilterUnload
fileMonInstanceSetup,                         	//  InstanceSetup
NULL,                               	   		//  InstanceQueryTeardown
fileMonInstanceTeardownStart,                  	//  InstanceTeardownStart
NULL,                              			//  InstanceTeardownComplete
NULL,                               			//  GenerateFileName
NULL,                               			//  GenerateDestinationFileName
NULL                                			//  NormalizeNameComponent
};

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,
PCFLT_RELATED_OBJECTS FltObjects,
PVOID *CompletionContext
//分配的一个context资源
)
{
	//sandbox?
	//主防??
	//杀毒引擎??
	//加解密??
	return XXX;
}
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;
}

上下位数组 例子:

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

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文件安装minifilter

这个就是抄啊改的 没什么介绍的,只需要注意里面的ClassGUID和Class必须对上

这是查询网址

http://msdn.microsoft.com/en-us/library/windows/hardware/ff540394(v=vs.85).aspx

如果需要用自己的加载器加载Minfilter 只需要在注册表服务对应的RegPath下创建REG_SZ类型的Instances子健

在这里键下创建键值项,项值为Altitude

 SYSTEM\\CurrentControlSet\\Services\\DriverName\\Instances子健下的键值项
 例子:
   //-------------------------------------------------------------------------------------------------------
    // SYSTEM\\CurrentControlSet\\Services\\DriverName\\Instances子健下的键值项
    //-------------------------------------------------------------------------------------------------------
    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;

下面说一说回调

FLT_PREOP_CALLBACK_STATUS
HOOK_PreNtCreateFile (
PFLT_CALLBACK_DATA Data,
PCFLT_RELATED_OBJECTS FltObjects,
PVOID *CompletionContext
//分配的一个context资源
)
{
	//sandbox?
	//主防??
	//杀毒引擎??
	//加解密??
	return XXX;
}
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;
}

PRE-OP的返回值:

FLT_PREOP_SUCCESS_WITH_CALLBACK,//常用

FLT_PREOP_SUCCESS_NO_CALLBACK,//常用

FLT_PREOP_PENDING,//挂起IRP 不常用

FLT_PREOP_DISALLOW_FASTIO,//关闭FASTIO

FLT_PREOP_COMPLETE,//阻止

FLT_PREOP_SYNCHRONIZE//不常用

POST-OP的返回值:

FLT_POSTOP_FINISHED_PROCESSING,//常用

FLT_POSTOP_MORE_PROCESSING_REQUIRED

我们可以判断这个Data是什么请求

判断Data是什么操作的宏

FLT_IS_IRP_OPERATION
FLT_IS_FASTIO_OPERATION
FLT_IS_FS_FILTER_OPERATION
		if(FLT_IS_FASTIO_OPERATION(Data))
		{
			ntStatus = STATUS_FLT_DISALLOW_FAST_IO;
			Data->IoStatus.Status = ntStatus;
			Data->IoStatus.Information = 0;
			return FLT_PREOP_DISALLOW_FASTIO;

		}

参数数据的获取:

PFLT_CALLBACK_DATA Data;
PEPROCESS processObject =
		Data->Thread ? IoThreadToProcess(Data->Thread) : PsGetCurrentProcess();//获取EPROCESS
HandleToUlong(PsGetProcessId(processObject));//获取PID

Data->IoStatus.Status = ntStatus;//返回给R3的
Data->IoStatus.Information = 0;//同上

FltObjects->Volume,//卷
FltObjects->Instance,//实例
FltObjects->FileObject,//文件对象
FltObjects->FileObject->DeviceObject//设备对象

Data->Iopb->Parameters.Create.SecurityContext->DesiredAccess //创建的权限 

比如这次是查询目录 (怎么判断是什么操作?每个对应的回调就告诉你了这是什么操作,不可以在Create的回调中收到写操作把)

PVOID	pQueryBuffer 	=
		Data->Iopb->Parameters.DirectoryControl.QueryDirectory.DirectoryBuffer;
ULONG	uQueryBufferSize 	=
		Data->Iopb->Parameters.DirectoryControl.QueryDirectory.Length

//读, 读有可能是使用MDL可能使用其它buff 判断的方法是看这个有没有值,没有值则是另一种

PMDL pReadMdl 		= Data->Iopb->Parameters.Read. MdlAddress;
PVOID pReadBuffer 		= Data->Iopb->Parameters.Read. ReadBuffer;
ULONG uReadLength 		= Data->Iopb->Parameters.Read.Length;

写同上面

路径的获取:

//在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);

pNameInfo里面还有很多东西

WDK里面的例子:

 //  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\"

//重命名的获得:

PFILE_RENAME_INFORMATION
    pFileRenameInfomation = (PFILE_RENAME_INFORMATION)Data->Iopb->Parameters.SetFileInformation.InfoBuffer;

FltGetDestinationFileNameInformation //重命名获得

其实NtCreateSection对应着一个IRP

IRP_MJ_ACQUIRE_FOR_SECTION_SYNCHRONIZATION

Data->Iopb->Parameters.AcquireForSectionSynchronization.PageProtection == PAGE_EXECUTE(等于这个就是创建进程)

在x64可以用这个监控进程 不过不需要

文件操作

在过滤驱动中,我们不能使用默认的文件操作,这会引起重入

Minfilter给我提供了专门的函数

FltCreateFile

FltReadFile

FltWriteFile

FltClose

FltQueryXxx

FltSetXxx

FltGetXxx

FltPerformXxx

其中的一个例子:

ntStatus = FltCreateFile(pFilter,
	pDstInstance,
	&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

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

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;

Minifilter R3与R0通信

不用像NT框架里面的通信方式了,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 );
        //下面就是判断是否创建成功,成功后就开始开启过滤

先说说HIPS的实现,

r3,首先得到R0通信端口的句柄

使用FilterConnectCommunicationPort 只需要注意第一个参数和最后一个参数即可 其它都为0或NULL

然后绑定这个端口(我理解为绑定)

使用CreateIoCompletionPort 只需要注意第一个参数最后一个参数即可,最后一个参数:为这个工作线程设置几个线程

这样有助于高效率 一般小于64 ,也可以设置请求次数 这样回复也会高效率一些(具体看后面的代码)

我们首先使用FiltetGetMessage异步获取一下消息,如果有信息,则下面的GetQueuedCompletionStatus就会恢复(如果没有消息,GetQueuedCompletionStatus就会暂停下来,并让出CPU,等待有信息)

有了信息后我们就可以进行扫描 过滤,搞定之后就调用FilterReplyMessage返回给内核

然后继续调用FilterGetMessage-等待--处理--返回给内核

代码:

   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;

那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 );

主动通信就讲完了,不过需要注意的时,应用程序可能已经退出了,带sys还在,那要怎么办呢,

R3的程序退出时,R0中那个断开连接的回调就会触发,我们需要在那个回调中设置用户通信端口为NULL

其它过滤函数中需要判断这个是不是NULL 不是NULL就通信,是NULL就放行

什么是R3通信端口?

其实这个是一个R3端口的句柄,当用户使用FilterConnectCommunicationPort建立连接时,

内核中那个连接函数就会被调用,在那里面就有用户的通信端口(句柄)(不记得了?往上拉看看吧,我们还在那里面设置本进程的ID呢)

说完了主动通信,我们来说说缓冲区的使用

比如写操作,写的数据可能在MDLbuff中,也可能在USERbuff中,那要怎么操作呢,我记得上面提到过

判断MDLbuff是不是为空,不为空则数据就在这个里面,否则就是userbuff中

注意这里需要使用异常处理结构块,主流程使用tyr and finally 内存操作使用try and except( EXCEPTION_EXECUTE_HANDLER )

这里还介绍了上下文的使用 只不过这里是很简单的处理了下

首先在CreatePre事前回调中,我们设置上下文,然后再后面操作中,我们获取这个上下文,如果获取不到就不是我们要操作的文件

这里其实没啥用,只是演示如果使用上下文,当然这里的上下文结构里面可以自定义,我这里是设置了需要不需要重新扫描

typedef struct _SCANNER_STREAM_HANDLE_CONTEXT {

    BOOLEAN RescanRequired;

} SCANNER_STREAM_HANDLE_CONTEXT, *PSCANNER_STREAM_HANDLE_CONTEXT;

代码例子:内核处理写操作 需要注意的事,有一个IRP是FLT管理器发的,有点特殊,需要放过,见代码尾巴

//处理写关闭
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.
    //
	//如果R3进程退出了
    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.
        //
		//如果写的长度为0 就放行
        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.
            //
			//开始获取数据缓存区 有2个缓存区,需要判断在数据在哪个buff中 判断的方法前面说过了
            if (Data->Iopb->Parameters.Write.MdlAddress != NULL) {

                buffer = MmGetSystemAddressForMdlSafe( Data->Iopb->Parameters.Write.MdlAddress,
                                                       NormalPagePriority );

                //
                //  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 {

                //
                //  Use the users buffer
                //
				//不是MDL就是USERbuff
                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!
            //
			//为发送给R3数据申请内存
            notification = ExAllocatePoolWithTag( NonPagedPool,
                                                  sizeof( SCANNER_NOTIFICATION ),
                                                  'nacS' );
            if (notification == NULL) {

                Data->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
                Data->IoStatus.Information = 0;
                returnStatus = FLT_PREOP_COMPLETE;
                leave;
            }
			//取最小啦 这里设置SCANNER_READ_BUFFER_SIZE为1024
            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.
            //
			//发送数据给R3 处理
            replyLength = sizeof( SCANNER_REPLY );

            status = FltSendMessage( ScannerData.Filter,
                                     &ScannerData.ClientPort,
                                     notification,
                                     sizeof( SCANNER_NOTIFICATION ),
                                     notification,
                                     &replyLength,
                                     NULL );//永远等待,一般40秒吧
			//为什么共用一块内存呢 你猜
            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" );

			//如果不是Flt管理发送的,这个IRP很特殊 ,必须放行,如果不是这个IRP就阻止了,因为它是不安全的
            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;
}

MF实现的一个封转的处理函数,这个函数可以将文件的内容发给R3,让R3处理并返回一个结果

为什么需要这个函数呢?

如果不是写,我们不能直接缓存区数据,那么我们需要读到这个文件的内容发给R3,这个函数就是这个功能

代码:其中包括了Minifilter读文件操作

其实注意的是申请的大小,我们是不知道这个文件到底有多大的,但我们确定的是这个文件一般比这个卷的大小小,所以我们暂时先申请卷的大小

然后下面读的时候会返回文件的大小,到时候就可以知道有多大了

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

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

        //
        //  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,
                                     sizeof(SCANNER_NOTIFICATION),
                                     notification,
                                     &replyLength,
                                     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;
}

思考:传BUFF给R3干什么?

解答:你猜

说一说一种比较特殊的情况

一个文件已写权限打开(创建)了,刚开始我们扫描过它没有问题

问:一个人现在是好人,那么他一辈子都是好人吗?

所以,我们需要会他设置一个上下文,上下文中有一个标志,改标志的作用是告诉Close时在扫描一次

其实这就是介绍上下文使用啦

核心代码:在创建事后回调中(别以为事后回调没有控制权了,它还是可以返回取消前面的操作哦 见最后面的代码)

//前面是扫描过这个文件了而且这个文件是安全的,但它有写权限,那么就要注意了
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,
                                     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 );
        }

然后再close的时候我们处理

这里说一说cleanup和closeup有什么区别,前者是使用zwclose或者closehandle 后者使用Obdef (忘记了对象计数减1的那个)

//处理打开时 有写权限但 打开成功时是安全的,等它关闭的时候的我们来扫描它
//触发这个回调的条件是文件引用技术为0,这个包括内核+R3的计数,一般是在上层使用了ZwClose或者CloseHandle时调用
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,
                                        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 );
    }

    return FLT_PREOP_SUCCESS_NO_CALLBACK;
}

例子:这个例子拦截后缀为txt的文件,如果txt中的内容有foul就被认定为病毒

这个例子演示了

通信方式(HIPS)

上下文的使用

文件名的获得

缓冲的使用

共有头文件

scannuk.h

/*++

Copyright (c) 1999-2002  Microsoft Corporation

Module Name:

    scanuk.h

Abstract:

    Header file which contains the structures, type definitions,
    constants, global variables and function prototypes that are
    shared between kernel and user mode.

Environment:

    Kernel & user mode

--*/

#ifndef __SCANUK_H__
#define __SCANUK_H__

//
//  Name of port used to communicate
//

const PWSTR ScannerPortName = L"\\ScannerPort";

#define SCANNER_READ_BUFFER_SIZE   1024

typedef struct _SCANNER_NOTIFICATION {

    ULONG BytesToScan;
    ULONG Reserved;             // for quad-word alignement of the Contents structure
    UCHAR Contents[SCANNER_READ_BUFFER_SIZE];

} SCANNER_NOTIFICATION, *PSCANNER_NOTIFICATION;

typedef struct _SCANNER_REPLY {

    BOOLEAN SafeToOpen;

} SCANNER_REPLY, *PSCANNER_REPLY;

#endif //  __SCANUK_H__

内核.h文件

/*++

Copyright (c) 1999-2002  Microsoft Corporation

Module Name:

    scrubber.h

Abstract:
    Header file which contains the structures, type definitions,
    constants, global variables and function prototypes that are
    only visible within the kernel.

Environment:

    Kernel mode

--*/
#ifndef __SCANNER_H__
#define __SCANNER_H__

///////////////////////////////////////////////////////////////////////////
//
//  Global variables
//
///////////////////////////////////////////////////////////////////////////

typedef struct _SCANNER_DATA {

    //
    //  The object that identifies this driver.
    //

    PDRIVER_OBJECT DriverObject;

    //
    //  The filter handle that results from a call to
    //  FltRegisterFilter.
    //

    PFLT_FILTER Filter;

    //
    //  Listens for incoming connections
    //

    PFLT_PORT ServerPort;

    //
    //  User process that connected to the port
    //

    PEPROCESS UserProcess;

    //
    //  Client port for a connection to user-mode
    //

    PFLT_PORT ClientPort;

} SCANNER_DATA, *PSCANNER_DATA;

extern SCANNER_DATA ScannerData;

typedef struct _SCANNER_STREAM_HANDLE_CONTEXT {

    BOOLEAN RescanRequired;

} SCANNER_STREAM_HANDLE_CONTEXT, *PSCANNER_STREAM_HANDLE_CONTEXT;

#pragma warning(push)
#pragma warning(disable:4200) // disable warnings for structures with zero length arrays.

typedef struct _SCANNER_CREATE_PARAMS {

    WCHAR String[0];

} SCANNER_CREATE_PARAMS, *PSCANNER_CREATE_PARAMS;

#pragma warning(pop)

///////////////////////////////////////////////////////////////////////////
//
//  Prototypes for the startup and unload routines used for
//  this Filter.
//
//  Implementation in scanner.c
//
///////////////////////////////////////////////////////////////////////////
DRIVER_INITIALIZE DriverEntry;
NTSTATUS
DriverEntry (
    __in PDRIVER_OBJECT DriverObject,
    __in PUNICODE_STRING RegistryPath
    );

NTSTATUS
ScannerUnload (
    __in FLT_FILTER_UNLOAD_FLAGS Flags
    );

NTSTATUS
ScannerQueryTeardown (
    __in PCFLT_RELATED_OBJECTS FltObjects,
    __in FLT_INSTANCE_QUERY_TEARDOWN_FLAGS Flags
    );

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

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

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

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

NTSTATUS
ScannerInstanceSetup (
    __in PCFLT_RELATED_OBJECTS FltObjects,
    __in FLT_INSTANCE_SETUP_FLAGS Flags,
    __in DEVICE_TYPE VolumeDeviceType,
    __in FLT_FILESYSTEM_TYPE VolumeFilesystemType
    );

#endif /* __SCANNER_H__ */

内核.c文件

/*++

Copyright (c) 1999-2002  Microsoft Corporation

Module Name:

    scanner.c

Abstract:

    This is the main module of the scanner filter.

    This filter scans the data in a file before allowing an open to proceed.  This is similar
    to what virus checkers do.

Environment:

    Kernel mode

--*/

#include <fltKernel.h>
#include <dontuse.h>
#include <suppress.h>
#include "scanuk.h"
#include "scanner.h"

#pragma prefast(disable:__WARNING_ENCODE_MEMBER_FUNCTION_POINTER, "Not valid for kernel mode drivers")

//
//  Structure that contains all the global data structures
//  used throughout the scanner.
//

SCANNER_DATA ScannerData;

//
//  This is a static list of file name extensions files we are interested in scanning
//

const UNICODE_STRING ScannerExtensionsToScan[] =
    { RTL_CONSTANT_STRING( L"doc"),
      RTL_CONSTANT_STRING( L"txt"),
      RTL_CONSTANT_STRING( L"bat"),
      RTL_CONSTANT_STRING( L"cmd"),
      RTL_CONSTANT_STRING( L"inf"),
      /*RTL_CONSTANT_STRING( L"ini"),   Removed, to much usage*/
      {0, 0, NULL}
    };

//
//  Function prototypes
//

NTSTATUS
ScannerPortConnect (
    __in PFLT_PORT ClientPort,
    __in_opt PVOID ServerPortCookie,
    __in_bcount_opt(SizeOfContext) PVOID ConnectionContext,
    __in ULONG SizeOfContext,
    __deref_out_opt PVOID *ConnectionCookie
    );

VOID
ScannerPortDisconnect (
    __in_opt PVOID ConnectionCookie
    );

NTSTATUS
ScannerpScanFileInUserMode (
    __in PFLT_INSTANCE Instance,
    __in PFILE_OBJECT FileObject,
    __out PBOOLEAN SafeToOpen
    );

BOOLEAN
ScannerpCheckExtension (
    __in PUNICODE_STRING Extension
    );

//
//  Assign text sections for each routine.
//

#ifdef ALLOC_PRAGMA
    #pragma alloc_text(INIT, DriverEntry)
    #pragma alloc_text(PAGE, ScannerInstanceSetup)
    #pragma alloc_text(PAGE, ScannerPreCreate)
    #pragma alloc_text(PAGE, ScannerPortConnect)
    #pragma alloc_text(PAGE, ScannerPortDisconnect)
#endif

//
//  Constant FLT_REGISTRATION structure for our filter.  This
//  initializes the callback routines our filter wants to register
//  for.  This is only used to register with the filter manager
//

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

const FLT_CONTEXT_REGISTRATION ContextRegistration[] = {

    { FLT_STREAMHANDLE_CONTEXT,
      0,
      NULL,
      sizeof(SCANNER_STREAM_HANDLE_CONTEXT),
      'chBS' },

    { FLT_CONTEXT_END }
};

const FLT_REGISTRATION FilterRegistration = {

    sizeof( FLT_REGISTRATION ),         //  Size
    FLT_REGISTRATION_VERSION,           //  Version
    0,                                  //  Flags
    ContextRegistration,                //  Context Registration.
    Callbacks,                          //  Operation callbacks
    ScannerUnload,                      //  FilterUnload
    ScannerInstanceSetup,               //  InstanceSetup
    ScannerQueryTeardown,               //  InstanceQueryTeardown
    NULL,                               //  InstanceTeardownStart
    NULL,                               //  InstanceTeardownComplete
    NULL,                               //  GenerateFileName
    NULL,                               //  GenerateDestinationFileName
    NULL                                //  NormalizeNameComponent
};

////////////////////////////////////////////////////////////////////////////
//
//    Filter initialization and unload routines.
//
////////////////////////////////////////////////////////////////////////////

NTSTATUS
DriverEntry (
    __in PDRIVER_OBJECT DriverObject,
    __in PUNICODE_STRING RegistryPath
    )
/*++

Routine Description:

    This is the initialization routine for the Filter driver.  This
    registers the Filter with the filter manager and initializes all
    its global data structures.

Arguments:

    DriverObject - Pointer to driver object created by the system to
        represent this driver.

    RegistryPath - Unicode string identifying where the parameters for this
        driver are located in the registry.

Return Value:

    Returns STATUS_SUCCESS.
--*/
{
    OBJECT_ATTRIBUTES oa;
    UNICODE_STRING uniString;
    PSECURITY_DESCRIPTOR sd;
    NTSTATUS status;

    UNREFERENCED_PARAMETER( RegistryPath );

    //
    //  Register with filter manager.
    //

    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,//当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 );

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

NTSTATUS
ScannerPortConnect (
    __in PFLT_PORT ClientPort,
    __in_opt PVOID ServerPortCookie,
    __in_bcount_opt(SizeOfContext) PVOID ConnectionContext,
    __in ULONG SizeOfContext,
    __deref_out_opt PVOID *ConnectionCookie
    )
/*++

Routine Description

    This is called when user-mode connects to the server port - to establish a
    connection

Arguments

    ClientPort - This is the client connection port that will be used to
        send messages from the filter

    ServerPortCookie - The context associated with this port when the
        minifilter created this port.

    ConnectionContext - Context from entity connecting to this port (most likely
        your user mode service)

    SizeofContext - Size of ConnectionContext in bytes

    ConnectionCookie - Context to be passed to the port disconnect routine.

Return Value

    STATUS_SUCCESS - to accept the connection

--*/
{
    PAGED_CODE();

    UNREFERENCED_PARAMETER( ServerPortCookie );
    UNREFERENCED_PARAMETER( ConnectionContext );
    UNREFERENCED_PARAMETER( SizeOfContext);
    UNREFERENCED_PARAMETER( ConnectionCookie );

    ASSERT( ScannerData.ClientPort == NULL );
    ASSERT( ScannerData.UserProcess == NULL );

    //
    //  Set the user process and port.
    //
	//设置本身进程 和 R3的的通信端口 给后面判断和通信时使用
    ScannerData.UserProcess = PsGetCurrentProcess();
    ScannerData.ClientPort = ClientPort;

    DbgPrint( "!!! scanner.sys --- connected, port=0x%p\n", ClientPort );

    return STATUS_SUCCESS;
}

VOID
ScannerPortDisconnect(
     __in_opt PVOID ConnectionCookie
     )
/*++

Routine Description

    This is called when the connection is torn-down. We use it to close our
    handle to the connection

Arguments

    ConnectionCookie - Context from the port connect routine

Return value

    None

--*/
{
    UNREFERENCED_PARAMETER( ConnectionCookie );

    PAGED_CODE();

    DbgPrint( "!!! scanner.sys --- disconnected, port=0x%p\n", ScannerData.ClientPort );

    //
    //  Close our handle to the connection: note, since we limited max connections to 1,
    //  another connect will not be allowed until we return from the disconnect routine.
    //

	//关闭R3通信端口
    FltCloseClientPort( ScannerData.Filter, &ScannerData.ClientPort );

    //
    //  Reset the user-process field.
    //

	//设置R3进程为0
    ScannerData.UserProcess = NULL;
}

NTSTATUS
ScannerUnload (
    __in FLT_FILTER_UNLOAD_FLAGS Flags
    )
/*++

Routine Description:

    This is the unload routine for the Filter driver.  This unregisters the
    Filter with the filter manager and frees any allocated global data
    structures.

Arguments:

    None.

Return Value:

    Returns the final status of the deallocation routines.

--*/
{
    UNREFERENCED_PARAMETER( Flags );

    //
    //  Close the server port.
    //

    FltCloseCommunicationPort( ScannerData.ServerPort );

    //
    //  Unregister the filter
    //

    FltUnregisterFilter( ScannerData.Filter );

    return STATUS_SUCCESS;
}

NTSTATUS
ScannerInstanceSetup (
    __in PCFLT_RELATED_OBJECTS FltObjects,
    __in FLT_INSTANCE_SETUP_FLAGS Flags,
    __in DEVICE_TYPE VolumeDeviceType,
    __in FLT_FILESYSTEM_TYPE VolumeFilesystemType
    )
/*++

Routine Description:

    This routine is called by the filter manager when a new instance is created.
    We specified in the registry that we only want for manual attachments,
    so that is all we should receive here.

Arguments:

    FltObjects - Describes the instance and volume which we are being asked to
        setup.

    Flags - Flags describing the type of attachment this is.

    VolumeDeviceType - The DEVICE_TYPE for the volume to which this instance
        will attach.

    VolumeFileSystemType - The file system formatted on this volume.

Return Value:

  FLT_NOTIFY_STATUS_ATTACH              - we wish to attach to the volume
  FLT_NOTIFY_STATUS_DO_NOT_ATTACH       - no, thank you

--*/
{
    UNREFERENCED_PARAMETER( FltObjects );
    UNREFERENCED_PARAMETER( Flags );
    UNREFERENCED_PARAMETER( VolumeFilesystemType );

    PAGED_CODE();

    ASSERT( FltObjects->Filter == ScannerData.Filter );

    //
    //  Don't attach to network volumes.
    //

    if (VolumeDeviceType == FILE_DEVICE_NETWORK_FILE_SYSTEM) {

       return STATUS_FLT_DO_NOT_ATTACH;
    }

    return STATUS_SUCCESS;
}

NTSTATUS
ScannerQueryTeardown (
    __in PCFLT_RELATED_OBJECTS FltObjects,
    __in FLT_INSTANCE_QUERY_TEARDOWN_FLAGS Flags
    )
/*++

Routine Description:

    This is the instance detach routine for the filter. This
    routine is called by filter manager when a user initiates a manual instance
    detach. This is a 'query' routine: if the filter does not want to support
    manual detach, it can return a failure status

Arguments:

    FltObjects - Describes the instance and volume for which we are receiving
        this query teardown request.

    Flags - Unused

Return Value:

    STATUS_SUCCESS - we allow instance detach to happen

--*/
{
    UNREFERENCED_PARAMETER( FltObjects );
    UNREFERENCED_PARAMETER( Flags );

    return STATUS_SUCCESS;
}

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

Routine Description:

    Pre create callback.  We need to remember whether this file has been
    opened for write access.  If it has, we'll want to rescan it in cleanup.
    This scheme results in extra scans in at least two cases:
    -- if the create fails (perhaps for access denied)
    -- the file is opened for write access but never actually written to
    The assumption is that writes are more common than creates, and checking
    or setting the context in the write path would be less efficient than
    taking a good guess before the create.

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-create callback to the post-create callback.

Return Value:

   FLT_PREOP_SUCCESS_WITH_CALLBACK - If this is not our user-mode process.
   FLT_PREOP_SUCCESS_NO_CALLBACK - All other threads.

--*/
{
    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) {

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

        return FLT_PREOP_SUCCESS_NO_CALLBACK;
    }

    return FLT_PREOP_SUCCESS_WITH_CALLBACK;
}

BOOLEAN
ScannerpCheckExtension (
    __in PUNICODE_STRING Extension
    )
/*++

Routine Description:

    Checks if this file name extension is something we are interested in

Arguments

    Extension - Pointer to the file name extension

Return Value

    TRUE - Yes we are interested
    FALSE - No
--*/
{
    const UNICODE_STRING *ext;

    if (Extension->Length == 0) {

        return FALSE;
    }

    //
    //  Check if it matches any one of our static extension list
    //

    ext = ScannerExtensionsToScan;

    while (ext->Buffer != NULL) {

        if (RtlCompareUnicodeString( Extension, ext, TRUE ) == 0) {

            //
            //  A match. We are interested in this file
            //

            return TRUE;
        }
        ext++;
    }

    return FALSE;
}

//处理打开,创建时(一般这个时候提示的话就是已经被感染了)
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 );

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

//处理打开时 有写权限但 打开成功时是安全的,等它关闭的时候的我们来扫描它
//触发这个回调的条件是文件引用技术为0,这个包括内核+R3的计数,一般是在上层使用了ZwClose或者CloseHandle时调用
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,
                                        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 );
    }

    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.
    //
	//如果R3进程退出了
    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.
        //
		//如果写的长度为0 就放行
        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.
            //
			//开始获取数据缓存区 有2个缓存区,需要判断在数据在哪个buff中 判断的方法前面说过了
            if (Data->Iopb->Parameters.Write.MdlAddress != NULL) {

                buffer = MmGetSystemAddressForMdlSafe( Data->Iopb->Parameters.Write.MdlAddress,
                                                       NormalPagePriority );

                //
                //  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 {

                //
                //  Use the users buffer
                //
				//不是MDL就是USERbuff
                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!
            //
			//为发送给R3数据申请内存
            notification = ExAllocatePoolWithTag( NonPagedPool,
                                                  sizeof( SCANNER_NOTIFICATION ),
                                                  'nacS' );
            if (notification == NULL) {

                Data->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
                Data->IoStatus.Information = 0;
                returnStatus = FLT_PREOP_COMPLETE;
                leave;
            }
			//取最小啦 这里设置SCANNER_READ_BUFFER_SIZE为1024
            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.
            //
			//发送数据给R3 处理
            replyLength = sizeof( SCANNER_REPLY );

            status = FltSendMessage( ScannerData.Filter,
                                     &ScannerData.ClientPort,
                                     notification,
                                     sizeof( SCANNER_NOTIFICATION ),
                                     notification,
                                     &replyLength,
                                     NULL );//永远等待,一般40秒吧
			//为什么共用一块内存呢 你猜
            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" );

			//如果不是Flt管理发送的,这个IRP很特殊 ,必须放行,如果不是这个IRP就阻止了,因为它是不安全的
            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;
}

//////////////////////////////////////////////////////////////////////////
//  Local support routines.
//
/////////////////////////////////////////////////////////////////////////

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

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

        //
        //  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,
                                     sizeof(SCANNER_NOTIFICATION),
                                     notification,
                                     &replyLength,
                                     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;
}

r3.h文件

/*++

Copyright (c) 1999-2002  Microsoft Corporation

Module Name:

    scanuser.h

Abstract:

    Header file which contains the structures, type definitions,
    constants, global variables and function prototypes for the
    user mode part of the scanner.

Environment:

    Kernel & user mode

--*/
#ifndef __SCANUSER_H__
#define __SCANUSER_H__

#pragma pack(1)

typedef struct _SCANNER_MESSAGE {

    //
    //  Required structure header.
    //

    FILTER_MESSAGE_HEADER MessageHeader;

    //
    //  Private scanner-specific fields begin here.
    //

    SCANNER_NOTIFICATION Notification;

    //
    //  Overlapped structure: this is not really part of the message
    //  However we embed it instead of using a separately allocated overlap structure
    //

    OVERLAPPED Ovlp;

} SCANNER_MESSAGE, *PSCANNER_MESSAGE;

typedef struct _SCANNER_REPLY_MESSAGE {

    //
    //  Required structure header.
    //

    FILTER_REPLY_HEADER ReplyHeader;

    //
    //  Private scanner-specific fields begin here.
    //

    SCANNER_REPLY Reply;

} SCANNER_REPLY_MESSAGE, *PSCANNER_REPLY_MESSAGE;

#endif //  __SCANUSER_H__

r3.c文件

/*++

Copyright (c) 1999-2002  Microsoft Corporation

Module Name:

    scanUser.c

Abstract:

    This file contains the implementation for the main function of the
    user application piece of scanner.  This function is responsible for
    actually scanning file contents.

Environment:

    User mode

--*/

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <winioctl.h>
#include <string.h>
#include <crtdbg.h>
#include <assert.h>
#include <fltuser.h>
#include "../MiniFliter_Scaner/scanuk.h"
#include "scanuser.h"
#include <dontuse.h>

#pragma comment(lib,"fltlib.lib")

#define SCANNER_DEFAULT_REQUEST_COUNT       5
#define SCANNER_DEFAULT_THREAD_COUNT        2
#define SCANNER_MAX_THREAD_COUNT            64

UCHAR FoulString[] = "foul";

//
//  Context passed to worker threads
//

typedef struct _SCANNER_THREAD_CONTEXT {

    HANDLE Port;
    HANDLE Completion;

} SCANNER_THREAD_CONTEXT, *PSCANNER_THREAD_CONTEXT;

VOID
Usage (
    VOID
    )
/*++

Routine Description

    Prints usage

Arguments

    None

Return Value

    None

--*/
{

    printf( "Connects to the scanner filter and scans buffers \n" );
    printf( "Usage: scanuser [requests per thread] [number of threads(1-64)]\n" );
}

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

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

        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 = 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 ) );

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

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,
								   (LPTHREAD_START_ROUTINE)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;
}

编译平台:VS2012 + WDK8.1

测试平台:Winxp Win7 x86 Win7x64

上面是R0主动通信 下面说说R3主动通信

这个通信一般是加规则

R3主动通信使用 FilterSendMessage

//主动发请求给R0
 FilterSendMessage(
	Port,
	request,
	sizeof(REQUEST),
	reply ? reply : NULL,
	reply ? sizeof(REPLY) : 0,
	&dwRtn);

R0处理R3的主动消息

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));
		//GET InputBuffer
		//Do something
		ProbeForWrite(OutputBuffer, OutputBufferLength, sizeof(ULONG));
		//Copy Result to Outputbuffer
	}
	__except(EXCEPTION_EXECUTE_HANDLER)
	{
		return STATUS_NOT_IMPLEMENTED;
	}

	return STATUS_SUCCESS;
}

例子:来自http://bbs.pediy.com/showthread.php?t=186931

标 题: 【原创】minifilter通讯之简单示例之一

作 者: correy

时 间: 2014-04-24,14:10:34

链 接: http://bbs.pediy.com/showthread.php?t=186931

made by correy

made at 2013.11.13

email:kouleguan at hotmail dot com

homepage:http://correy.webs.com

效果如下(包括驱动打印的消息):

用户发来的信息是:test

[1124] FilterSendMessage ok!

[1124] 从内核发来的信息是:

[1124] to user client

[1124]

*/

#include <windows.h>

//这两个文件在VS中没有,在WDK中有.
//如果要用VS编译要拷贝相应的文件到相应的目录或者改变目录的设置等.
#include <fltuser.h>
#pragma comment(lib, "fltLib.lib")

int _tmain(int argc, _TCHAR* argv[])
{
    MessageBox(0,L"请附加调试器",L"调试专用",0);//如果是远程调试的话,这行特别有用.

    HANDLE port = INVALID_HANDLE_VALUE;
    HRESULT hResult = S_OK;

    hResult = FilterConnectCommunicationPort( L"\\CommunicationPort", 0, NULL, 0, NULL, &port );
    if (IS_ERROR( hResult )) {
        OutputDebugString(L"FilterConnectCommunicationPort fail!\n");
        return hResult;
    }

    wchar_t InBuffer[] = L"test";
    wchar_t OutBuffer[MAX_PATH] = {0};
    DWORD bytesReturned = 0;
    hResult = FilterSendMessage(port, InBuffer, lstrlen(InBuffer), OutBuffer, sizeof(OutBuffer), &bytesReturned);
    if (IS_ERROR( hResult )) {
        OutputDebugString(L"FilterSendMessage fail!\n");
        CloseHandle( port );
        return hResult;
    } else {
        OutputDebugString(L"FilterSendMessage ok!\n");
    }

    OutputDebugString(L"从内核发来的信息是:");
    OutputDebugString(OutBuffer);
    OutputDebugString(L"\n");

    CloseHandle( port );

 return 0;
}

驱动的代码如下:

/*
内核中没有:FltGetMessage,FltReplyMessage函数.
个人认为:MessageNotifyCallback有FltGetMessage,FltReplyMessage,FltSendMessage这三个函数的功能.
所以在MessageNotifyCallback里面调用这些函数是不对的,得到一些意想不到的效果.建议不要这样做.

FltGetMessage除了在MessageNotifyCallback里面,大多的地方都可以调用,但是用户层最好开启线程处理函数.

FltGetMessage函数调用示例代码如下:
//{
//    wchar_t SenderBuffer[] = L"SenderBuffer";
//    wchar_t ReplyBuffer[] = L"ReplyBuffer";
//    ULONG replyLength = sizeof(ReplyBuffer);
//
//    status = FltSendMessage( gFilterHandle, &g_ClientPort, SenderBuffer, sizeof(SenderBuffer), ReplyBuffer, &replyLength, NULL);
//    if (STATUS_SUCCESS == status) {
//        DbgPrint( "send message to user-mode\n");
//    } else {
//        DbgPrint( "couldn't send message to user-mode to scan file, status 0x%X\n", status );
//    }
//}
*/

#include <fltKernel.h>

PFLT_FILTER gFilterHandle;
PFLT_PORT g_ServerPort;
PFLT_PORT g_ClientPort;

NTSTATUS MessageNotifyCallback (
    IN PVOID PortCookie,
    IN PVOID InputBuffer OPTIONAL,
    IN ULONG InputBufferLength,
    OUT PVOID OutputBuffer OPTIONAL,
    IN ULONG OutputBufferLength,//用户可以接受的数据的最大长度.
    OUT PULONG ReturnOutputBufferLength)
    /*
    这里要注意:1.数据地址的对齐.
               2.文档建议使用:try/except处理.
               3.如果是64位的驱动要考虑32位的EXE发来的请求.
    */
{
    NTSTATUS status = 0;
    wchar_t buffer[] = L"to user client";//

    PAGED_CODE();

    UNREFERENCED_PARAMETER(PortCookie);

    //打印用户发来的信息
    KdPrint(("用户发来的信息是:%ls\n",InputBuffer));

    //返回用户一些信息.
    *ReturnOutputBufferLength = sizeof(buffer);
    RtlCopyMemory(OutputBuffer,buffer,* ReturnOutputBufferLength);

    /*
    minispy在这里用FilterSendMessage获取信息的,对就是FilterSendMessage.
    这里某个类型里面获取信息,这些信息是在各种操作时(IRP的MJ_)加入链表的.
    注意链表的操作一定要同步,支持多线程.
    然后用户的一个线程在不停的获取这些信息.
    */

    return status;
}

VOID DisconnectNotifyCallback (_In_opt_ PVOID ConnectionCookie)
{
    PAGED_CODE();

    UNREFERENCED_PARAMETER(ConnectionCookie);

    FltCloseClientPort(gFilterHandle, &g_ClientPort);//应该加判断,如果ConnectionCookie == 我们的值就执行这行.
}

NTSTATUS ConnectNotifyCallback (IN PFLT_PORT ClientPort, IN PVOID ServerPortCookie, IN PVOID ConnectionContext, IN ULONG SizeOfContext, OUT PVOID * ConnectionPortCookie)
{
    PAGED_CODE();

    UNREFERENCED_PARAMETER( ServerPortCookie );
    UNREFERENCED_PARAMETER( ConnectionContext );
    UNREFERENCED_PARAMETER( SizeOfContext);
    UNREFERENCED_PARAMETER( ConnectionPortCookie);

    //可以加以判断,禁止非法的连接,从而给予保护.
    g_ClientPort = ClientPort;//保存以供以后使用.

    return STATUS_SUCCESS;
}

#pragma PAGEDCODE
NTSTATUS PtInstanceQueryTeardown (__in PCFLT_RELATED_OBJECTS FltObjects,__in FLT_INSTANCE_QUERY_TEARDOWN_FLAGS Flags)
{
    return STATUS_SUCCESS;
}

#pragma PAGEDCODE//#pragma alloc_text(PAGE, PtUnload)
NTSTATUS PtUnload (__in FLT_FILTER_UNLOAD_FLAGS Flags)
{
    FltCloseCommunicationPort(g_ServerPort);//没有这一行是停止不了驱动的,查询也是永远等待中.
    FltUnregisterFilter( gFilterHandle );
    return STATUS_SUCCESS;
}

CONST FLT_REGISTRATION FilterRegistration = {
    sizeof( FLT_REGISTRATION ),         //  Size
    FLT_REGISTRATION_VERSION,           //  Version
    0,                                  //  Flags
    NULL,                               //  Context
    NULL,                               //  Operation callbacks
    PtUnload,                           //  MiniFilterUnload
    NULL,                               //  InstanceSetup
    PtInstanceQueryTeardown,            //  InstanceQueryTeardown
    NULL,                               //  InstanceTeardownStart
    NULL,                               //  InstanceTeardownComplete
    NULL,                               //  GenerateFileName
    NULL,                               //  GenerateDestinationFileName
    NULL                                //  NormalizeNameComponent
};

DRIVER_INITIALIZE DriverEntry;
#pragma alloc_text(INIT, DriverEntry)//#pragma INITCODE
NTSTATUS DriverEntry (_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath)
{
    NTSTATUS status;
    PSECURITY_DESCRIPTOR sd;
    OBJECT_ATTRIBUTES oa;
    UNICODE_STRING uniString;

    UNREFERENCED_PARAMETER(RegistryPath);

    KdBreakPoint();

    __try
    {
        status = FltRegisterFilter(DriverObject, &FilterRegistration, &gFilterHandle);
        if (!NT_SUCCESS(status)) //;
        {
            __leave;
        }

        status  = FltBuildDefaultSecurityDescriptor(&sd, FLT_PORT_ALL_ACCESS);
        if (!NT_SUCCESS( status )) {
            __leave;
        }

        RtlInitUnicodeString(&uniString, L"\\CommunicationPort");
        InitializeObjectAttributes( &oa, &uniString, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL, sd);
        status = FltCreateCommunicationPort(gFilterHandle, &g_ServerPort, &oa, NULL, ConnectNotifyCallback, DisconnectNotifyCallback, MessageNotifyCallback, 1);
        FltFreeSecurityDescriptor( sd );
        if (!NT_SUCCESS( status )) {
            __leave;
        }

        status = FltStartFiltering(gFilterHandle);//这个结果在下面判断.
    } __finally {
        if (!NT_SUCCESS( status ) )
        {
            if (NULL != g_ServerPort) {
                FltCloseCommunicationPort(g_ServerPort);
            }

            if (NULL != gFilterHandle) {
                FltUnregisterFilter(gFilterHandle);
            }
        }
    }

    return status;
}

上面的部分代码最好放到线程中去指向,原因你猜

一些参考文章:

http://www.cnblogs.com/huangyong9527/archive/2012/09/07/2674720.html minifilter 与用户态的通信

http://bbs.pediy.com/showthread.php?t=186931 //minifilter通讯之简单示例之一

http://bbs.pediy.com/showthread.php?t=186932 //minifilter通讯之二:使用FilterGetMessage获取内核拦截的目录的创建

http://blog.csdn.net/caimouse/article/details/1855142 //Windows API一日一练(60)CreateIoCompletionPort和GetQueuedCompletionStatus函数

《Windows内核安全和驱动开发》Minifliter 例子:简单的MiniFilter框架实现了记事本文件无法使用的代码

#include "fltKernel.h"
#include "ntddk.h"  

#pragma prefast(disable:__WARNING_ENCODE_MEMBER_FUNCTION_POINTER, "Not valid for kernel mode drivers")  

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING pReg);
NTSTATUS NPUnload(FLT_FILTER_UNLOAD_FLAGS Flags);  

FLT_PREOP_CALLBACK_STATUS NPPreCreate(
    __inout PFLT_CALLBACK_DATA Data,
    __in PCFLT_RELATED_OBJECTS FltObjects,
    __deref_out_opt PVOID *CompletionContext);  

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

#ifdef ALLOC_PRAGMA
    #pragma alloc_text(INIT,DriverEntry)
    #pragma alloc_text(PAGE,NPUnload)
    #pragma alloc_text(PAGE,NPPreCreate)
#endif  

PFLT_FILTER gFilterHandle;  

const FLT_OPERATION_REGISTRATION Callbacks[] =
{
    {
        IRP_MJ_CREATE,
        0,
        NPPreCreate,
        NPPostCreate
    },
    {
        IRP_MJ_OPERATION_END
    }
};  

const FLT_REGISTRATION FltRegistration =
{
    sizeof(FLT_REGISTRATION),
    FLT_REGISTRATION_VERSION,
    0,
    NULL,
    Callbacks,
    NPUnload,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL
};  

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING pReg)
{
    NTSTATUS st = STATUS_SUCCESS;
    st = FltRegisterFilter(pDriverObject,&FltRegistration,&gFilterHandle);
    if(NT_SUCCESS(st))
    {
        st = FltStartFiltering(gFilterHandle);
        if(!NT_SUCCESS(st))
        {
            FltUnregisterFilter(gFilterHandle);
        }
    }
    DbgPrint("[MiniFilter Entry]\n");
    return st;
}  

NTSTATUS NPUnload(FLT_FILTER_UNLOAD_FLAGS Flags)
{
    UNREFERENCED_PARAMETER(Flags);
    PAGED_CODE();
    DbgPrint("[MiniFilter Unload]\n");
    FltUnregisterFilter(gFilterHandle);
    return STATUS_SUCCESS;
}  

FLT_PREOP_CALLBACK_STATUS NPPreCreate(
    __inout PFLT_CALLBACK_DATA Data,
    __in PCFLT_RELATED_OBJECTS FltObjects,
    __deref_out_opt PVOID *CompletionContext)
{
    UNREFERENCED_PARAMETER(FltObjects);
    UNREFERENCED_PARAMETER(CompletionContext);
    PAGED_CODE();
    {
        UCHAR MajorFunction = 0;
        PFLT_FILE_NAME_INFORMATION nameInfo;
        MajorFunction = Data->Iopb->MajorFunction;
        if(IRP_MJ_CREATE == MajorFunction &&
            NT_SUCCESS(FltGetFileNameInformation(Data,FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT,&nameInfo)))
        {
            if(NT_SUCCESS(FltParseFileNameInformation(nameInfo)))
            {//查找notepad.exe字符串,并阻止
                if(NULL!=wcsstr(nameInfo->Name.Buffer,L"notepad.exe"))
                {
                    Data->IoStatus.Status = STATUS_ACCESS_DENIED;
                    Data->IoStatus.Information = 0;
                    FltReleaseFileNameInformation(nameInfo);
                    return FLT_PREOP_COMPLETE;
                }
            }
            FltReleaseFileNameInformation(nameInfo);
        }
        //DbgPrint("ENTER CREATE_CALLBACK\n");
    }
    return FLT_PREOP_SUCCESS_WITH_CALLBACK;
}  

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)
{
    return FLT_POSTOP_FINISHED_PROCESSING;
}
   

其他可以看TA的教程

至于沙盒

要注意的是

In and out

重定向

删除的处理

路径的处理

再次说说sandbox的巨大漏洞

在沙盒中的程序如果使用剪切功能 是对外部文件的真正操作

WDK下有例子

在index下有一些帮助文档

wdk samples index ->Windows Driver Kit samples

以及

Windows Drvier Kit -> Device and Driver Technologies->Installable File System Drivers->File System Minifilter Drivers

其它的也可以到这里找哦

时间: 2024-10-15 18:30:31

MiniFilter文件系统学习的相关文章

Unix文件系统学习笔记之二: 文件描述符、inode和打开文件表

Unix文件系统学习笔记之二: 文件描述符.inode和打开文件表 系统盘上数据的布局 文件系统无非是关于数据在磁盘上的组织以及存储空间管理的,为此,首先需要知道磁盘上数据的总体布局方式.以Unix为例,最重要的一张表如下: Unix 进程管理中和用户文件.io 最相关的数据结构:usr 数据结构 The procstructure does not record information related to file access.  However the userstructure con

FAT32文件系统学习(3) —— 数据区(DATA区)

FAT32文件系统学习(3) —— 数据区(DATA区) 今天继续学习FAT32文件系统的数据区部分(Data区).其实这一篇应该是最有意思的,我们可以通过在U盘内放入一些文件,然后在程序中读取出来:反过来也可以用程序在U盘内写入一下数据,然后在windows下可以看到写入的文件.这些笔者都会在这篇文章中演示.同时,在写这篇文章的时候笔者也发现了许多意想不到的规律. 1.本文目录 1.读取根目录 2.短文件名目录项 3.长文件名目录项 4.U盘写入文件夹 5.参考文献 2.读取根目录 两张FAT

FAT32文件系统学习(2) —— FAT表

1.题外话 在继续本文学习FAT32文件系统之前,先来插入一点别的话题.我们都知道U盘有一个属性是容量,就拿笔者的U盘为例,笔者手上的U盘是金士顿的DataTraveler G3 4GB的一个U盘.电脑上显示的容量如图1所示为3.75GB.那么这个3.75GB是怎么计算出来的呢? 图 1 系统显示U盘属性 我们先来回顾一下上一篇BPB参数当中的Sectors(扇区总数)这个参数,这一参数代表了这个U盘在出厂时的总扇区数,笔者手上这个是7884672个,可以从图2中看到.其中每个扇区为512 B,

FAT32文件系统学习(1) —— BPB的理解

FAT 32 文件系统学习 1.本文的目标 本文将通过实际读取一个FAT32格式的U盘来简单了解和学习FAT32文件系统的格式.虽然目前windwos操作系统的主流文件系统格式是NTFS,但是FAT32由于其兼容性原因,还是有一定的学习价值.为了能做出一个窗体程序提供直观的感觉,本文的代码采用c#编写,对应的c++代码也会附上. 2.本文目录 1.本文的目标 2.什么是FAT32 3.引导区 2.什么是FAT32 FAT32是Windwos系统硬盘格式分区的一种.这种格式采用32位的文件分配表,

linux ext2 文件系统学习

Linux  ext2文件系统理解 硬盘组成: 硬盘由多个圆形硬盘片组成.按照硬盘片能够容纳的数据量分为单盘和多盘.硬盘的数据读取主要靠机械手臂上的磁头,在机械手臂上有多个磁头.机械手臂不动硬盘旋转一周划过的路径就是磁道.由于在一块硬盘上有多个硬盘片重叠放置,所以在硬盘旋转的时候在不同硬盘片上画出了多个相同的磁道,这些相同的磁道组成了柱面.柱面是分区的最小单位.由圆形向外画直线又可以将磁道划分为扇区,扇区是数据存储的最小单位,一个扇区的大小约为512字节. 磁盘容量 = 柱面 * 磁头 * 扇区

文件系统学习

占位:文件系统学习

[Linux] linux文件系统学习

linux系统支持很多种文件系统. 1. 如何确认当前系统挂载了哪些文件系统? 使用mount命令可以查看当前系统上已经挂载了哪些文件系统, [email protected]:~$ mount/dev/sda1 on / type ext4 (rw,errors=remount-ro) proc on /proc type proc (rw,noexec,nosuid,nodev) sysfs on /sys type sysfs (rw,noexec,nosuid,nodev) none o

Linux 文件系统学习

Linux 文件系统学习 文件信息显示 ls -al:用于显示文件的所有信息. 依次是:文件权限类型/链接数/文件所有者/文件所属群组/文件大小/文件最后修改时间/文件名,在 Linux 下,文件夹也是一个文件(类型为 d). 文件类型由十个字符组成. 分别为:文件类型/文件所有者权限/文件所属群组权限/其他人权限. 权限由三个字符组成. 分别为:可读/可写/可执行. 文件类型:d(目录)/ -(文件)/ l(链接)/ b(存储设备)/ c(串行端口设备) 文件权限更改 chgrp:改变文件所属

linux文件系统学习总结

linux最优秀的特点在于它是多用户多任务的环境,而且对于linux来讲一切皆文件,提到文件这个概念就免不了提文件相关的权限与属性的概念,那相关文件的属性记录在硬盘的哪个地方呢?这就需要了解linux的文件系统是如何记录文件,如何读取文件. 大家都知道硬盘作为存储介质,如果要使用硬盘存储数据需要对硬盘进行分区,格式化之后才可以存储数据.那为什么要对硬盘进行分区呢?因为我们必须要告诉操作系统:"这块硬盘可以访问的区域是有A柱面到B柱面",只有这样,操作系统才能控制硬盘磁头去A~B范围内的