基于Minifilter实现文件监控和文件防删除

Redundant

发布日期: 2021-10-07 09:05:52 浏览量: 231
评分:
star star star star star star star star star star_border
*转载请注明来自write-bug.com

背景

Minifilter即File System Minifilter Drivers,是Windows为了简化第三方开发人员开发文件过滤驱动而提供的一套框架,这个框架依赖于一个称之为Filter Manager(后面简写为FltMgr)的传统文件系统过滤驱动。

使用 Minifilter 其实很简单,主要步骤就 4 个:

  1. 设置你要过滤的 IRP。
  2. 使用 FltRegisterFilter 注册过滤器。
  3. 使用 FltStartFiltering 开启过滤器。
  4. 在驱动卸载历程(DriverUnload)里,使用 FltUnregisterFilter 卸载过滤器。

但是,VS2013 里面有向导可以直接创建一个 Minifilter 驱动,可以生成代码框架和 inf 文件,这简化了很多工作。

现在,我就使用 VS2013 创建 Minifilter 驱动项目,实现文件监控及文件防删除。把实现过程和原理整理成文档,分享给大家。

函数介绍

FltGetFileNameInformation 函数

返回文件或目录的名称信息。

函数声明

  1. NTSTATUS FltGetFileNameInformation(
  2. _In_ PFLT_CALLBACK_DATA CallbackData,
  3. _In_ FLT_FILE_NAME_OPTIONS NameOptions,
  4. _Out_ PFLT_FILE_NAME_INFORMATION *FileNameInformation
  5. );

参数

  • CallbackData [in]
    指向I / O操作(FLT_CALLBACK_DATA)的回调数据结构的指针。 此参数是必需的,不能为NULL。
  • NameOptions [in]
    FLT_FILE_NAME_OPTIONS值包含指定要返回的名称信息的格式的标志,以及Filter Manager要使用的查询方法。其中,FLT_FILE_NAME_NORMALIZED 表示FileNameInformation 参数接收包含该文件的归一化名称的结构的地址。FLT_FILE_NAME_QUERY_DEFAULT 表示如果文件系统查询文件名目前不安全,FltGetFileNameInformation不会执行任何操作。 否则,将查询过滤器管理器的名称缓存以获取文件名信息。 如果在缓存中找不到名称,则会查询文件系统并缓存结果。
  • FileNameInformation [out]
    指向调用者分配变量的指针,该变量接收包含文件名信息的系统分配的FLT_FILE_NAME_INFORMATION结构的地址。 FltGetFileNameInformation从分页池分配此结构。 此参数是必需的,不能为NULL。

返回值

  • 成功,则返回 STATUS_SUCCESS;否则,返回其它的 NTSTATUS 错误码。

备注

  • 要解析FltGetFileNameInformation返回的FLT_FILE_NAME_INFORMATION结构的内容,请调用FltParseFileNameInformation。

FltParseFileNameInformation 函数

解析FLT_FILE_NAME_INFORMATION结构的内容。

函数声明

  1. NTSTATUS FltParseFileNameInformation(
  2. _Inout_ PFLT_FILE_NAME_INFORMATION FileNameInformation
  3. );

参数

  • FileNameInformation [in,out]
  • 指向FltGetDestinationFileNameInformation,FltGetFileNameInformation,FltGetFileNameInformationUnsafe或FltGetTunneledName的先前调用返回的FLT_FILE_NAME_INFORMATION结构。 此参数是必需的,不能为NULL。

返回值

  • 成功,则返回 STATUS_SUCCESS;否则,返回其它的 NTSTATUS 错误码。

实现过程

当我们创建好 Minifilter 驱动项的时候,首先,就开始设置要过滤的 IRP。VS2013 开发环境已经为我们生成好这部分代码了,我们只需要手动修改下,添加我们需要 IRP 就好,就是在 FLT_OPERATION_REGISTRATION 中设置 IRP_MJ_CREATE、IRP_MJ_READ、IRP_MJ_WRITE、IRP_MJ_SET_INFORMATION 等 4 个 IRP 消息。每当有文件创建、读取、写入、属性修改等操作的时候,就会触发相应的操作前和操作后的回调函数。由于,我们要实现文件防删除功能,所以,我们对操作前的回调函数进行操作。

  1. CONST FLT_OPERATION_REGISTRATION Callbacks[] =
  2. {
  3. { IRP_MJ_CREATE,
  4. 0,
  5. FileMonitor_MiniFilterPreOperation,
  6. FileMonitor_MiniFilterPostOperation },
  7. { IRP_MJ_READ,
  8. 0,
  9. FileMonitor_MiniFilterPreOperation,
  10. FileMonitor_MiniFilterPostOperation },
  11. { IRP_MJ_WRITE,
  12. 0,
  13. FileMonitor_MiniFilterPreOperation,
  14. FileMonitor_MiniFilterPostOperation },
  15. { IRP_MJ_SET_INFORMATION,
  16. 0,
  17. FileMonitor_MiniFilterPreOperation,
  18. FileMonitor_MiniFilterPostOperation },
  19. #if 0 // TODO - List all of the requests to filter.
  20. …(略)
  21. #endif // TODO
  22. { IRP_MJ_OPERATION_END }
  23. };

对于使用 FltRegisterFilter 注册过滤器以及使用 FltStartFiltering 开启过滤器的代码,VS2013 开发环境也为我们实现好了,不需要我们做这部分操作。接下来,我们只需对操作前和操作后的回调函数进行处理就好。本文演示的程序,只对操作前的回调函数进行处理。

回调函数中的第一个参数 PFLT_CALLBACK_DATA Data 中,就存储着文件的消息类型以及文件的信息。我们可以从 Data->Iopb->MajorFunction 获取消息类型,调用 FltGetFileNameInformation 函数及其 FltParseFileNameInformation 函数从 Data 中获取文件路径信息。

我们可以根据文件的信息类型以及文件路径来判断是否是我们要保护的文件,若是要保护的文件,则直接返回 FLT_PREOP_COMPLETE,结束文件操作,实现拒绝相应的操作的效果。

编码实现

  1. /*************************************************************************
  2. MiniFilter callback routines.
  3. *************************************************************************/
  4. FLT_PREOP_CALLBACK_STATUS
  5. Minifilter_FileMonitor_TestPreOperation (
  6. _Inout_ PFLT_CALLBACK_DATA Data,
  7. _In_ PCFLT_RELATED_OBJECTS FltObjects,
  8. _Flt_CompletionContext_Outptr_ PVOID *CompletionContext
  9. )
  10. {
  11. NTSTATUS status;
  12. UNREFERENCED_PARAMETER( FltObjects );
  13. UNREFERENCED_PARAMETER( CompletionContext );
  14. PT_DBG_PRINT( PTDBG_TRACE_ROUTINES,
  15. ("Minifilter_FileMonitor_Test!Minifilter_FileMonitor_TestPreOperation: Entered\n") );
  16. /*
  17. 要进行监控的话,通常在PreXXX里处理,而要进行监视的话,则通常在PostXXX里
  18. 处理(当然监视在PreXXX里处理也行).
  19. 下面对监控文件的读写、删除、重命名、改属性的操作,并且禁止对指定文件520.exe
  20. 做任何操作。
  21. 原理是:在传入的参数里获取文件名,并打印出来,如果发现是被保护的文件,就返回操作。
  22. */
  23. // 获取文件路径
  24. UCHAR MajorFunction = Data->Iopb->MajorFunction;
  25. PFLT_FILE_NAME_INFORMATION lpNameInfo = NULL;
  26. status = FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &lpNameInfo);
  27. if (NT_SUCCESS(status))
  28. {
  29. status = FltParseFileNameInformation(lpNameInfo);
  30. if (NT_SUCCESS(status))
  31. {
  32. // CREATE
  33. if (IRP_MJ_CREATE == MajorFunction)
  34. {
  35. if (IsProtectionFile(lpNameInfo))
  36. {
  37. KdPrint(("[IRP_MJ_CREATE]%wZ", &lpNameInfo->Name));
  38. return FLT_PREOP_COMPLETE;
  39. // return FLT_PREOP_DISALLOW_FASTIO;
  40. }
  41. }
  42. // 读取
  43. else if (IRP_MJ_READ == MajorFunction)
  44. {
  45. if (IsProtectionFile(lpNameInfo))
  46. {
  47. KdPrint(("[IRP_MJ_READ]%wZ", &lpNameInfo->Name));
  48. return FLT_PREOP_COMPLETE;
  49. // return FLT_PREOP_DISALLOW_FASTIO;
  50. }
  51. }
  52. // 文件写入
  53. else if (IRP_MJ_WRITE == MajorFunction)
  54. {
  55. if (IsProtectionFile(lpNameInfo))
  56. {
  57. KdPrint(("[IRP_MJ_WRITE]%wZ", &lpNameInfo->Name));
  58. return FLT_PREOP_COMPLETE;
  59. // return FLT_PREOP_DISALLOW_FASTIO;
  60. }
  61. }
  62. // 修改文件信息
  63. else if (IRP_MJ_SET_INFORMATION == MajorFunction)
  64. {
  65. if (IsProtectionFile(lpNameInfo))
  66. {
  67. KdPrint(("[IRP_MJ_SET_INFORMATION]%wZ", &lpNameInfo->Name));
  68. return FLT_PREOP_COMPLETE;
  69. // return FLT_PREOP_DISALLOW_FASTIO;
  70. }
  71. }
  72. }
  73. }
  74. return FLT_PREOP_SUCCESS_WITH_CALLBACK;
  75. }
  1. // 判断是否是保护文件
  2. BOOLEAN IsProtectionFile(PFLT_FILE_NAME_INFORMATION lpNameInfo)
  3. {
  4. BOOLEAN bProtect = FALSE;
  5. PWCHAR lpszProtectionFileName, lpszFileName;
  6. // 申请内存
  7. lpszProtectionFileName = (PWCHAR)ExAllocatePool(NonPagedPool, 256);
  8. lpszFileName = (PWCHAR)ExAllocatePool(NonPagedPool, 512);
  9. // 初始化内存
  10. RtlZeroMemory(lpszProtectionFileName, 256);
  11. RtlZeroMemory(lpszFileName, 512);
  12. // 复制数据
  13. RtlCopyMemory(lpszFileName, lpNameInfo->Name.Buffer, (sizeof(WCHAR) + lpNameInfo->Name.Length));
  14. RtlCopyMemory(lpszProtectionFileName, L"520.exe", (sizeof(WCHAR) + wcslen(L"520.exe")));
  15. // 判断
  16. if (NULL != wcsstr(lpszFileName, lpszProtectionFileName))
  17. {
  18. bProtect = TRUE;
  19. }
  20. // 释放内存
  21. ExFreePool(lpszProtectionFileName);
  22. ExFreePool(lpszFileName);
  23. return bProtect;
  24. }

程序测试

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

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

总结

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

inf 文件安装驱动方式:

  1. 选中inf文件,鼠标右键,选择“安装”;
  2. 安装完毕后,以管理员权限打开cmd,输入“net start 服务名”启动服务;
  3. 停止服务则使用命令“net stop 服务名”即可。

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

上传的附件 cloud_download Minifilter_FileMonitor_Test.zip ( 26.79kb, 3次下载 )

发送私信

成长就像走夜路一样,既没有灯也没啥人,但正因为黎明很美,所以要酷酷的走下去

10
文章数
5
评论数
最近文章
eject