0%

使用MiniFilter与应用层通讯(1)

前言

通常情况下驱动想要主动与应用层通讯,都是借助于内核同步对象(比如事件、信号量等)来实现,
但是使用内核同步对象对 IRQL 有限制,某些特殊环境下是 APC_LEVEL 级别,这就产生了一些问题。

内核同步对象

我们以内核同步对象中的 事件(Event) 为例,可以在MSDN文档中查看 ZwSetEventKeSetEvent
的相关说明,其中 ZwSetEvent 只能运行在 PASSIVE_LEVEL 级别下,而 KeSetEvent 则是说明如下

KeSetEvent

可以知道 KeSetEvent 适应的范围更广,由于 KeSetEvent 操作的不是句柄,把内核中的事件跟应用层
关联起来的方法,就是使用 IoCreateNotificationEventIoCreateSynchronizationEvent
创建跟应用层 相同名称 的事件,可以获取到对应的句柄和内核对象。

注意:先从应用层创建,再从内核层打开,两者具有相同权限。如果先从内核层创建,再从应用层打开,
应用层只能 WaitForSingleObject 而没有修改的权限。

1
2
3
4
5
6
7
8
9
PKEVENT IoCreateNotificationEvent(
_In_ PUNICODE_STRING EventName,
_Out_ PHANDLE EventHandle
); // 创建或打开手动复位的命名事件

PKEVENT IoCreateSynchronizationEvent(
_In_ PUNICODE_STRING EventName,
_Out_ PHANDLE EventHandle
); // 创建或打开自动复位的命名事件

MiniFilter通讯端口

MiniFilter跟应用层通讯,可以在 APC_LEVEL 级别下进行,某些情况下可以选择此通讯方式。
本例子以WDK源码中 src -> filesys -> miniFilter -> minispy 工程作为基础。
创建驱动与应用层通讯的端口,需要安全描述符,如下为相关创建和释放的函数

1
2
3
4
NTSTATUS FltBuildDefaultSecurityDescriptor(
_Out_ PSECURITY_DESCRIPTOR *SecurityDescriptor,
_In_ ACCESS_MASK DesiredAccess
); // 创建描述权限的安全描述符
1
2
3
VOID FltFreeSecurityDescriptor(
_In_ PSECURITY_DESCRIPTOR SecurityDescriptor
); // 释放安全描述符内存

创建好安全描述符后,就可以使用描述符创建和关闭通讯端口

1
2
3
4
5
6
7
8
9
10
NTSTATUS FltCreateCommunicationPort(
_In_ PFLT_FILTER Filter,
_Out_ PFLT_PORT *ServerPort,
_In_ POBJECT_ATTRIBUTES ObjectAttributes,
_In_opt_ PVOID ServerPortCookie,
_In_ PFLT_CONNECT_NOTIFY ConnectNotifyCallback,
_In_ PFLT_DISCONNECT_NOTIFY DisconnectNotifyCallback,
_In_opt_ PFLT_MESSAGE_NOTIFY MessageNotifyCallback,
_In_ LONG MaxConnections
); // 创建通讯端口
1
2
3
VOID FltCloseCommunicationPort(
_In_ PFLT_PORT ServerPort
); // 关闭通讯端口
1
2
3
4
VOID FltCloseClientPort(
_In_ PFLT_FILTER Filter,
_Out_ PFLT_PORT *ClientPort
); // 关闭客户端通讯

当应用层连接驱动的通讯端口时,就会触发 ConnectNotifyCallback 回调,在这里我们要保存
ClientPort 信息,给驱动主动往应用层发送数据时用。当应用层关闭与驱动连接的通讯时,就会
触发 DisconnectNotifyCallback 回调,这里我们要使用 FltCloseClientPort 关闭 ClientPort
当应用层主动向驱动发送数据时,就会触发 MessageNotifyCallback 回调,我们在这里进行数据操作。

1
2
3
4
// 全局变量
PFLT_FILTER FilterHandle = NULL; // 过滤器句柄
PFLT_PORT FilterServerPort = NULL; // 驱动端口
PFLT_PORT FilterClientPort = NULL; // 应用端口

如下为连接驱动通讯端口时 ConnectNotifyCallback 回调函数的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
NTSTATUS ConnectCallback(
IN PFLT_PORT ClientPort,
IN PVOID ServerPortCookie,
IN PVOID ConnectionContext,
IN ULONG SizeOfContext,
OUT PVOID *ConnectionPortCookie)
{
UNREFERENCED_PARAMETER(ServerPortCookie);
UNREFERENCED_PARAMETER(ConnectionContext);
UNREFERENCED_PARAMETER(SizeOfContext);
UNREFERENCED_PARAMETER(ConnectionPortCookie);
FilterClientPort = ClientPort;
return STATUS_SUCCESS;
}

如下为关闭与驱动连接的通讯时 DisconnectNotifyCallback 回调函数的处理

1
2
3
4
5
VOID DisconnectCallback(IN PVOID ConnectionCookie)
{
UNREFERENCED_PARAMETER(ConnectionCookie);
FltCloseClientPort(FilterHandle, &FilterClientPort);
}

如下为主动向驱动发送数据时 MessageNotifyCallback 回调函数的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
NTSTATUS MessageCallback(
IN PVOID PortCookie,
IN PVOID InputBuffer OPTIONAL,
IN ULONG InputBufferLength,
OUT PVOID OutputBuffer OPTIONAL,
IN ULONG OutputBufferLength,
OUT PULONG ReturnOutputBufferLength)
{
UCHAR Command = 0;
UNREFERENCED_PARAMETER(PortCookie);
// 检查输入缓冲区
if ((InputBuffer == NULL) || (InputBufferLength != 1))
return STATUS_INVALID_PARAMETER;
// 检查输出缓冲区
if ((OutputBuffer == NULL) || (OutputBufferLength != 1))
return STATUS_INVALID_PARAMETER;
// 检查实际大小指针
if ((ReturnOutputBufferLength == NULL))
return STATUS_INVALID_PARAMETER;
// 对缓冲区的操作要放到try/except中
try {
Command = *((PUCHAR)InputBuffer);
} except(EXCEPTION_EXECUTE_HANDLER) {
return GetExceptionCode();
}
// 把传入的数据加1
Command++;
// 对缓冲区的操作要放到try/except中
try {
*((PUCHAR)OutputBuffer) = Command;
} except(EXCEPTION_EXECUTE_HANDLER) {
return GetExceptionCode();
}
// 实际数据大小
*ReturnOutputBufferLength = 1;
return STATUS_SUCCESS;
}

如下为驱动入口函数注册通讯端口的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
NTSTATUS Status = STATUS_SUCCESS;
PSECURITY_DESCRIPTOR pDescriptor = NULL;
OBJECT_ATTRIBUTES Attributes = { 370030 };
UNICODE_STRING PortName = RTL_CONSTANT_STRING(L"MyCommPort");
UNREFERENCED_PARAMETER(RegistryPath);
// 注册过滤器
Status = FltRegisterFilter(DriverObject, &Registration, &FilterHandle);
if (!NT_SUCCESS(Status)) return Status;
// 创建安全描述符
Status = FltBuildDefaultSecurityDescriptor(&pDescriptor, FLT_PORT_ALL_ACCESS);
if (!NT_SUCCESS(Status))
{
FltUnregisterFilter(FilterHandle);
return Status;
}
// 初始化通讯端口属性
InitializeObjectAttributes(&Attributes, &PortName,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, pDescriptor);
// 创建与应用层通讯端口
Status = FltCreateCommunicationPort(FilterHandle, &FilterServerPort,
&Attributes, NULL, ConnectCallback, DisconnectCallback, MessageCallback, 1);
// 释放安全描述符内存
FltFreeSecurityDescriptor(pDescriptor);
if (!NT_SUCCESS(Status))
{
FltUnregisterFilter(FilterHandle);
return Status;
}
// 启动过滤器
Status = FltStartFiltering(FilterHandle);
if (!NT_SUCCESS(Status))
{
FltCloseCommunicationPort(FilterServerPort);
FltUnregisterFilter(FilterHandle);
return Status;
}
return STATUS_SUCCESS;
}

应用层向驱动通讯通讯时,连接通讯端口和发送数据的函数定义如下

1
2
3
4
5
6
7
8
HRESULT FilterConnectCommunicationPort(
_In_ LPCWSTR lpPortName,
_In_ DWORD dwOptions,
_In_opt_ LPCVOID lpContext,
_In_ WORD dwSizeOfContext,
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
_Out_ HANDLE *hPort
); // 连接驱动的通讯端口
1
2
3
4
5
6
7
8
HRESULT FilterSendMessage(
_In_ HANDLE hPort,
_In_opt_ LPVOID lpInBuffer,
_In_ DWORD dwInBufferSize,
_Out_ LPVOID lpOutBuffer,
_In_ DWORD dwOutBufferSize,
_Out_ LPDWORD lpBytesReturned
); // 发送数据给驱动

实际使用的示例代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
BOOL CommunicationToDriverTest()
{
// 连接驱动的通讯端口
HANDLE Port = NULL;
HRESULT Result = FilterConnectCommunicationPort(
L"MyCommPort", 0, NULL, 0, NULL, &Port);
if (Result != S_OK) return FALSE;
// 发送和接收数据
DWORD dwRet = 0;
UCHAR InBuffer = 55;
UCHAR OutBuffer = 0;
Result = FilterSendMessage(
Port, &InBuffer, sizeof(UCHAR), &OutBuffer, sizeof(UCHAR), &dwRet);
CloseHandle(Port); // 关闭句柄
if (Result != S_OK) return FALSE;
return TRUE;
}

驱动向应用层通讯

驱动向应用层通讯的工作方式,与Windows的消息循环模式类似,需要先在应用层创建消息循环体,
然后驱动再发送给应用层。并且应用层在读取和回复消息时,需要增加一个消息头结构体,如下所示

1
2
3
4
typedef struct _FILTER_MESSAGE_HEADER {
ULONG ReplyLength; // 驱动可接收的缓冲区大小
ULONGLONG MessageId; // 驱动生成的通讯消息ID
} FILTER_MESSAGE_HEADER, *PFILTER_MESSAGE_HEADER;
1
2
3
4
typedef struct _FILTER_REPLY_HEADER {
NTSTATUS Status; // 回复给驱动的结果
ULONGLONG MessageId; // 回复目标通讯消息ID
} FILTER_REPLY_HEADER, *PFILTER_REPLY_HEADER;

我们这里做如下示例定义

1
2
3
4
typedef struct _MY_GET_MSG {
FILTER_MESSAGE_HEADER Header; // 附加的头
ULONG ProcessID; // 实际通讯的数据
} MY_GET_MSG, *PMY_GET_MSG;
1
2
3
4
typedef struct _MY_REPLAY_MSG {
FILTER_REPLY_HEADER Header; // 附加的头
ULONG ProcessID; // 实际通讯的数据
} MY_REPLAY_MSG, *PMY_REPLAY_MSG;

如下为实际使用时的示例代码,需要注意的是,编译器有个 结构体自动对齐 的功能,以如上定义的
结构体为例,尽管我们通讯的 ProcessIDULONG 类型,但是整个结构体会对齐到 ULONGLONG 大小,
如果我们使用整个结构体的大小,就会超出驱动中能接收的 sizeof(ULONG) 大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
BOOL GetDriverCommunicationTest()
{
// 连接驱动的通讯端口
HANDLE Port = NULL;
HRESULT Result = FilterConnectCommunicationPort(
L"MyCommPort", 0, NULL, 0, NULL, &Port);
if (Result != S_OK) return FALSE;
// 创建消息循环
MY_GET_MSG stGetMsg = { 0 };
MY_REPLAY_MSG stReplayMsg = { 0 };
while (1)
{
// 等待读取驱动的信息
memset(&stGetMsg, 0, sizeof(MY_GET_MSG));
Result = FilterGetMessage(
Port, &stGetMsg.Header, sizeof(MY_GET_MSG), NULL);
if (Result != S_OK) break;
// 把读取到的信息加1
stReplayMsg.Header.Status = STATUS_SUCCESS;
stReplayMsg.Header.MessageId = stGetMsg.Header.MessageId;
stReplayMsg.ProcessID = stGetMsg.ProcessID + 1;
// 回复给驱动的信息(由于结构体的自动对齐情况,
// 可能sizeof(MY_REPLAY_MSG)中超出原本sizeof(ULONG)的大小)
Result = FilterReplyMessage(
Port, &stReplayMsg.Header,
sizeof(FILTER_REPLY_HEADER) + sizeof(ULONG));
if (Result != S_OK) break;
}
CloseHandle(Port); // 关闭句柄
if (Result != S_OK) return FALSE;
return TRUE;
}