# Minifilter驱动程序与用户层程序通信
# 背景
通常 NT 驱动程序与用户层间的通信,可以由用户层调用 CreateFile 函数打开驱动设备并获取设备句柄,然后调用 DeviceIoControl 函数实现用户层数据和内核层数据的交互。
那么,对于 Minifilter,它是一个 WDM 驱动,它并不像 NT 驱动那样使用常用的方式通信,而是有自己一套函数专门用于数据通信交互。现在,我就把程序的实现过程和原理整理成文档,分享给大家。
# 实现过程
## 用户层程序的实现过程
### 导入库文件
我们先来介绍下用户层上的程序的实现过程。首先,我们需要包含头文件 fltUser.h 以及库文件 fltLib.lib,这些文件在 VS 中并没有,它们存在于 WDK 中。我们可以设置程序的目录包含路径以及库文件包含路径,也可以将 WDK 中这两个文件拷贝到当前目录中来。我们选择后一种方法,将下面目录下的文件拷贝到当前目录中:
- C:\Program Files (x86)\Windows Kits\8.1\Include\um\fltUser.h
- C:\Program Files (x86)\Windows Kits\8.1\Lib\winv6.3\um\x86\fltLib.lib
- C:\Program Files (x86)\Windows Kits\8.1\Lib\winv6.3\um\x64\fltLib.lib
那么,我们在程序中声明头文件以及导入库文件的代码为:
```c++
# include "flt\\fltUser.h"
# ifdef _WIN32
#pragma comment(lib, "flt\\lib\\x86\\fltLib.lib")
# else
#pragma comment(lib, "flt\\lib\\x64\\fltLib.lib")
# endif
```
### 调用函数实现交互
用户层上实现于 Minifilter 内核层的数据交互方法,和用户层与 NT 驱动程序的交互方法很相似,虽然不是 CreateFile 打开对象获取句柄,在调用 DeviceIoControl 交互数据。具体的实现步骤如下:
- 首先,调用 FilterConnectCommunicationPort 函数打开通信端口,获取端口的句柄
- 然后,调用 FilterSendMessage 函数交互数据,向内核程序传入输入、输出缓冲区
- 当交互结束,通信句柄不再使用的时候,调用 CloseHandle 函数关闭句柄
综合上面 3 个步骤来看,是不是和 NT 驱动程序的交互方式很相似呢?我们通过类比记忆就好。其中,Minifilter 是通过端口的方式来实现数据交互的。具体的实现代码如下所示:
```c++
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hPort = NULL;
char szInputBuf[MAX_PATH] = "From User Test!";
char szOutputBuf[MAX_PATH] = { 0 };
DWORD dwInputLen = 1 + ::lstrlen(szInputBuf);
DWORD dwOutputLen = MAX_PATH;
DWORD dwRet = 0;
HRESULT hRet = NULL;
// 打开并连接端口, 获取端口句柄. (类似CreateFile)
hRet = ::FilterConnectCommunicationPort(PORT_NAME, 0, NULL, 0, NULL, &hPort);
if (IS_ERROR(hRet))
{
::MessageBox(NULL, "FilterConnectCommunicationPort", NULL, MB_OK);
return 1;
}
// 向端口发送数据. (类似 DeviceIoControl)
hRet = ::FilterSendMessage(hPort, szInputBuf, dwInputLen, szOutputBuf, dwOutputLen, &dwRet); // 类似DeviceIoControl
if (IS_ERROR(hRet))
{
::MessageBox(NULL, "FilterSendMessage", NULL, MB_OK);
return 2;
}
// 显示数据
printf("InputBuffer:0x%x\n", szInputBuf);
printf("OutputBuffer:0x%x\n", szOutputBuf);
system("pause");
return 0;
}
```
## 内核层程序的实现过程
从上面用户层程序的实现过程来看,和通常的交互方式来看,没有什么大区别,只是调用的函数变了而已。但是,对于内核层,却有很大的改变。
我们知道,VS2013 里面有向导可以直接创建一个 Minifilter 驱动,可以生成代码框架和 inf 文件,这简化了很多工作。但是,VS2013 开发化境并没有帮我们生成与用户层通信部分的代码,所以,需要我们手动对代码进行更改,实现与用户层的数据通信。具体的步骤如下:
1.首先,在内核程序的顶头声明 2 个全局变量,保存通信用的服务器端口以及客户端端口;并且声明 3 个回调函数:建立连接回调函数、数据通信回调函数、断开连接回调函数。
```c++
// 端口名称
# define PORT_NAME L"\\CommPort"
// 服务器端口
PFLT_PORT g_ServerPort;
// 客户端端口
PFLT_PORT g_ClientPort;
// 建立连接回调函数
NTSTATUS ConnectNotifyCallback(
IN PFLT_PORT ClientPort,
IN PVOID ServerPortCookies,
IN PVOID ConnectionContext,
IN ULONG SizeOfContext,
OUT PVOID *ConnectionPortCokkie);
// 数据通信回调函数
NTSTATUS MessageNotifyCallback(
IN PVOID PortCookie,
IN PVOID InputBuffer OPTIONAL,
IN ULONG InputBufferLength,
OUT PVOID OutputBuffer,
IN ULONG OutputBufferLength,
OUT PULONG ReturnOutputBufferLength);
// 断开连接回调函数
VOID DisconnectNotifyCallback(_In_opt_ PVOID ConnectionCookie);
```
2.然后,我们来到 DriverEntry 入口点函数,进行修改:
- 首先,调用 FltRegisterFilter 注册过滤器
- 然后,在使用 FltCreateCommunicationPort 函数创建通信端口之前,需要调用 FltBuildDefaultSecurityDescriptor 函数创建一个默认的安全描述符。其中,FLT_PORT_ALL_ACCESS 表示程序拥有连接到端口、访问端口等所有权限。其中,Minifilter 通常在调用 FltCreateCommunicationPort 函数之前会调用 FltBuildDefaultSecurityDescriptor 函数;在调用完 FltCreateCommunicationPort 函数后,会调用 FltFreeSecurityDescriptor 函数
- 接着,调用 FltCreateCommunicationPort 创建通信服务器端口,使得Minifilter 驱动程序可以接收来自用户层程序的连接请求。可以通过该函数设置端口名称、建立连接回调函数、数据通信回调函数、断开连接回调函数、最大连接数等,同时可以获取服务器端口句柄
- 然后,调用 FltFreeSecurityDescriptor 函数释放安全描述符
- 最后,调用 FltStartFiltering 函数开始启动过滤注册的 Minifilter 驱动程序
```c++
NTSTATUS DriverEntry (
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{
NTSTATUS status;
UNREFERENCED_PARAMETER( RegistryPath );
PT_DBG_PRINT( PTDBG_TRACE_ROUTINES,
("Minifilter_Communicate_Test!DriverEntry: Entered\n") );
//
// Register with FltMgr to tell it our callback routines
//
status = FltRegisterFilter( DriverObject,
&FilterRegistration,
&gFilterHandle );
FLT_ASSERT( NT_SUCCESS( status ) );
if (NT_SUCCESS( status )) {
PSECURITY_DESCRIPTOR lpSD = NULL;
// 创建安全描述, 注意:要创建这个安全描述,否则不能成功通信
status = FltBuildDefaultSecurityDescriptor(&lpSD, FLT_PORT_ALL_ACCESS);
if (!NT_SUCCESS(status))
{
KdPrint(("FltBuildDefaultSecurityDescriptor Error[0x%X]", status));
return status;
}
// 创建于用户层交互的端口
UNICODE_STRING ustrCommPort;
OBJECT_ATTRIBUTES objectAttributes;
RtlInitUnicodeString(&ustrCommPort, PORT_NAME);
InitializeObjectAttributes(&objectAttributes, &ustrCommPort, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, lpSD);
status = FltCreateCommunicationPort(gFilterHandle, &g_ServerPort, &objectAttributes,
NULL, ConnectNotifyCallback, DisconnectNotifyCallback, MessageNotifyCallback, 1);
if (!NT_SUCCESS(status))
{
KdPrint(("FltCreateCommunicationPort Error[0x%X]", status));
return status;
}
// 释放安全描述
FltFreeSecurityDescriptor(lpSD);
//
// Start filtering i/o
//
status = FltStartFiltering( gFilterHandle );
if (!NT_SUCCESS( status )) {
FltUnregisterFilter( gFilterHandle );
}
}
return status;
}
```
其中,建立连接回调函数的代码为:
```c++
NTSTATUS ConnectNotifyCallback(
IN PFLT_PORT ClientPort,
IN PVOID ServerPortCookies,
IN PVOID ConnectionContext,
IN ULONG SizeOfContext,
OUT PVOID *ConnectionPortCokkie)
{
PAGED_CODE();
UNREFERENCED_PARAMETER(ServerPortCookies);
UNREFERENCED_PARAMETER(ConnectionContext);
UNREFEREN