Minifilter驱动程序与用户层程序通信

DemonGan

发布日期: 2019-02-15 17:40:50 浏览量: 1388
评分:
star star star star star star star star star_border star_border
*转载请注明来自write-bug.com

背景

通常 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

那么,我们在程序中声明头文件以及导入库文件的代码为:

  1. #include "flt\\fltUser.h"
  2. #ifdef _WIN32
  3. #pragma comment(lib, "flt\\lib\\x86\\fltLib.lib")
  4. #else
  5. #pragma comment(lib, "flt\\lib\\x64\\fltLib.lib")
  6. #endif

调用函数实现交互

用户层上实现于 Minifilter 内核层的数据交互方法,和用户层与 NT 驱动程序的交互方法很相似,虽然不是 CreateFile 打开对象获取句柄,在调用 DeviceIoControl 交互数据。具体的实现步骤如下:

  • 首先,调用 FilterConnectCommunicationPort 函数打开通信端口,获取端口的句柄

  • 然后,调用 FilterSendMessage 函数交互数据,向内核程序传入输入、输出缓冲区

  • 当交互结束,通信句柄不再使用的时候,调用 CloseHandle 函数关闭句柄

综合上面 3 个步骤来看,是不是和 NT 驱动程序的交互方式很相似呢?我们通过类比记忆就好。其中,Minifilter 是通过端口的方式来实现数据交互的。具体的实现代码如下所示:

  1. int _tmain(int argc, _TCHAR* argv[])
  2. {
  3. HANDLE hPort = NULL;
  4. char szInputBuf[MAX_PATH] = "From User Test!";
  5. char szOutputBuf[MAX_PATH] = { 0 };
  6. DWORD dwInputLen = 1 + ::lstrlen(szInputBuf);
  7. DWORD dwOutputLen = MAX_PATH;
  8. DWORD dwRet = 0;
  9. HRESULT hRet = NULL;
  10. // 打开并连接端口, 获取端口句柄. (类似CreateFile)
  11. hRet = ::FilterConnectCommunicationPort(PORT_NAME, 0, NULL, 0, NULL, &hPort);
  12. if (IS_ERROR(hRet))
  13. {
  14. ::MessageBox(NULL, "FilterConnectCommunicationPort", NULL, MB_OK);
  15. return 1;
  16. }
  17. // 向端口发送数据. (类似 DeviceIoControl)
  18. hRet = ::FilterSendMessage(hPort, szInputBuf, dwInputLen, szOutputBuf, dwOutputLen, &dwRet); // 类似DeviceIoControl
  19. if (IS_ERROR(hRet))
  20. {
  21. ::MessageBox(NULL, "FilterSendMessage", NULL, MB_OK);
  22. return 2;
  23. }
  24. // 显示数据
  25. printf("InputBuffer:0x%x\n", szInputBuf);
  26. printf("OutputBuffer:0x%x\n", szOutputBuf);
  27. system("pause");
  28. return 0;
  29. }

内核层程序的实现过程

从上面用户层程序的实现过程来看,和通常的交互方式来看,没有什么大区别,只是调用的函数变了而已。但是,对于内核层,却有很大的改变。

我们知道,VS2013 里面有向导可以直接创建一个 Minifilter 驱动,可以生成代码框架和 inf 文件,这简化了很多工作。但是,VS2013 开发化境并没有帮我们生成与用户层通信部分的代码,所以,需要我们手动对代码进行更改,实现与用户层的数据通信。具体的步骤如下:

1.首先,在内核程序的顶头声明 2 个全局变量,保存通信用的服务器端口以及客户端端口;并且声明 3 个回调函数:建立连接回调函数、数据通信回调函数、断开连接回调函数。

  1. // 端口名称
  2. #define PORT_NAME L"\\CommPort"
  3. // 服务器端口
  4. PFLT_PORT g_ServerPort;
  5. // 客户端端口
  6. PFLT_PORT g_ClientPort;
  7. // 建立连接回调函数
  8. NTSTATUS ConnectNotifyCallback(
  9. IN PFLT_PORT ClientPort,
  10. IN PVOID ServerPortCookies,
  11. IN PVOID ConnectionContext,
  12. IN ULONG SizeOfContext,
  13. OUT PVOID *ConnectionPortCokkie);
  14. // 数据通信回调函数
  15. NTSTATUS MessageNotifyCallback(
  16. IN PVOID PortCookie,
  17. IN PVOID InputBuffer OPTIONAL,
  18. IN ULONG InputBufferLength,
  19. OUT PVOID OutputBuffer,
  20. IN ULONG OutputBufferLength,
  21. OUT PULONG ReturnOutputBufferLength);
  22. // 断开连接回调函数
  23. 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 驱动程序

  1. NTSTATUS DriverEntry (
  2. _In_ PDRIVER_OBJECT DriverObject,
  3. _In_ PUNICODE_STRING RegistryPath
  4. )
  5. {
  6. NTSTATUS status;
  7. UNREFERENCED_PARAMETER( RegistryPath );
  8. PT_DBG_PRINT( PTDBG_TRACE_ROUTINES,
  9. ("Minifilter_Communicate_Test!DriverEntry: Entered\n") );
  10. //
  11. // Register with FltMgr to tell it our callback routines
  12. //
  13. status = FltRegisterFilter( DriverObject,
  14. &FilterRegistration,
  15. &gFilterHandle );
  16. FLT_ASSERT( NT_SUCCESS( status ) );
  17. if (NT_SUCCESS( status )) {
  18. PSECURITY_DESCRIPTOR lpSD = NULL;
  19. // 创建安全描述, 注意:要创建这个安全描述,否则不能成功通信
  20. status = FltBuildDefaultSecurityDescriptor(&lpSD, FLT_PORT_ALL_ACCESS);
  21. if (!NT_SUCCESS(status))
  22. {
  23. KdPrint(("FltBuildDefaultSecurityDescriptor Error[0x%X]", status));
  24. return status;
  25. }
  26. // 创建于用户层交互的端口
  27. UNICODE_STRING ustrCommPort;
  28. OBJECT_ATTRIBUTES objectAttributes;
  29. RtlInitUnicodeString(&ustrCommPort, PORT_NAME);
  30. InitializeObjectAttributes(&objectAttributes, &ustrCommPort, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, lpSD);
  31. status = FltCreateCommunicationPort(gFilterHandle, &g_ServerPort, &objectAttributes,
  32. NULL, ConnectNotifyCallback, DisconnectNotifyCallback, MessageNotifyCallback, 1);
  33. if (!NT_SUCCESS(status))
  34. {
  35. KdPrint(("FltCreateCommunicationPort Error[0x%X]", status));
  36. return status;
  37. }
  38. // 释放安全描述
  39. FltFreeSecurityDescriptor(lpSD);
  40. //
  41. // Start filtering i/o
  42. //
  43. status = FltStartFiltering( gFilterHandle );
  44. if (!NT_SUCCESS( status )) {
  45. FltUnregisterFilter( gFilterHandle );
  46. }
  47. }
  48. return status;
  49. }

其中,建立连接回调函数的代码为:

  1. NTSTATUS ConnectNotifyCallback(
  2. IN PFLT_PORT ClientPort,
  3. IN PVOID ServerPortCookies,
  4. IN PVOID ConnectionContext,
  5. IN ULONG SizeOfContext,
  6. OUT PVOID *ConnectionPortCokkie)
  7. {
  8. PAGED_CODE();
  9. UNREFERENCED_PARAMETER(ServerPortCookies);
  10. UNREFERENCED_PARAMETER(ConnectionContext);
  11. UNREFERENCED_PARAMETER(SizeOfContext);
  12. UNREFERENCED_PARAMETER(ConnectionPortCokkie);
  13. // 可以加以判断,禁止非法的连接,从而给予保护
  14. g_ClientPort = ClientPort; // 保存以供以后使用
  15. return STATUS_SUCCESS;
  16. }

只要有连接连接到端口上,就会调用此函数。我们可以在该回调函数中获取客户端的端口句柄。这个客户端端口句柄要保存下来,这样,我们的驱动程序才可以和建立连接的用户层程序使用该客户端句柄进行数据通信。

其中,断开连接回调函数的代码为:

  1. VOID DisconnectNotifyCallback(_In_opt_ PVOID ConnectionCookie)
  2. {
  3. PAGED_CODE();
  4. UNREFERENCED_PARAMETER(ConnectionCookie);
  5. // 应该加判断,如果ConnectionCookie == 我们的值就执行这行
  6. FltCloseClientPort(gFilterHandle, &g_ClientPort);
  7. }

每当有连接断开的时候,就会调用该函数。我们需要在此调用 FltCloseClientPort 函数,关闭客户端端口。

其中,数据交互回调函数的代码为:

  1. NTSTATUS MessageNotifyCallback(
  2. IN PVOID PortCookie,
  3. IN PVOID InputBuffer OPTIONAL,
  4. IN ULONG InputBufferLength,
  5. OUT PVOID OutputBuffer,
  6. IN ULONG OutputBufferLength,
  7. OUT PULONG ReturnOutputBufferLength)
  8. {
  9. /*
  10. 这里要注意:
  11. 1.数据地址的对齐.
  12. 2.文档建议使用:try/except处理.
  13. 3.如果是64位的驱动要考虑32位的EXE发来的请求.
  14. */
  15. NTSTATUS status = STATUS_SUCCESS;
  16. PAGED_CODE();
  17. UNREFERENCED_PARAMETER(PortCookie);
  18. /*
  19. 这里输入、输出的地址均是用户空间的地址!!!
  20. */
  21. // 显示用户传输来的数据
  22. KdPrint(("[InputBuffer][0x%X]%s\n", InputBuffer, (PCHAR)InputBuffer));
  23. KdPrint(("[OutputBuffer][0x%X]\n", OutputBuffer));
  24. // 返回内核数据到用户空间
  25. CHAR szText[] = "From Kernel Data!";
  26. RtlCopyMemory(OutputBuffer, szText, sizeof(szText));
  27. *ReturnOutputBufferLength = sizeof(szText);
  28. return status;
  29. }

每当有数据交互的时候,就会调用此回调函数。我们可以从输入缓冲区中获取来自用户层程序传入的数据。然后对输出缓冲区进行设置,将内核数据输出到用户层中。这个函数和 NT 驱动程序中的 IRP_MJ_DEVICE_CONTRL 消息对应的操作函数类似。

3.当驱动卸载的时候,要在卸载函数中调用

  1. // 没有这一行是停止不了驱动的,查询也是永远等待中
  2. FltCloseCommunicationPort(g_ServerPort);

否则,停止不了驱动的,查询也是永远等待中。

程序测试

在 Win7 32 位系统下,驱动程序正常执行:

在 Win10 64 位系统下,驱动程序正常执行:

总结

Minifilter 的通讯结构不难理解,注意和 NT 驱动程序的驱动结构进行类比理解就好。

要注意该程序的加载,并不像 NT 驱动那样,调用加载程序来加载。WDM驱动,采用 inf 文件的安装方式,但是,一定要注意:MiniFilter生成后,一定要修改 inf中的 Instance1.Altitude = “370030”,即将注释去掉即可。因为每一个 Minifilter 驱动都必须指定一个 Altitude。每一个发组都有自己的一个 Altitude 区间,Altitude 值越高,代表在设备栈里面的位置也越高,也就是越先收到应用层发过来的IRP。

inf 文件安装驱动方式:

  • 选中inf文件,鼠标右键,选择“安装”

  • 安装完毕后,以管理员权限打开cmd,输入“net start 服务名”启动服务

  • 停止服务则使用命令“net stop 服务名”即可

同时要注意,程序在判断文件路径的时候,要使用 ExAllocatePool 申请非分页内存,不要直接使用变量,因为使用 FltGetFileNameInformation 获取的路径信息是存储在分页内存中,直接在回调函数中使用会导致蓝屏情况。

参考

参考自《Windows黑客编程技术详解》一书

上传的附件 cloud_download Minifilter_Communicate_Test.7z ( 159.11kb, 2次下载 )

发送私信

这一切都不是我的,但总有一天,会是我的

73
文章数
67
评论数
最近文章
eject