Minifilter

Minifilter简介

MiniFilter是微软为我们开发的一个新的驱动,称为过滤管理器.(Filter Manager或者 fltmgr).这个驱动主要作用就是如果有文件操作可以通知我们.

Minifilterlegacy filter的区别:

1.比sfilter加载顺序更易控制,因为Minifilter可以设置Altitude

对于Minifilter来说,Altitude 是一个非常重要的概念,因为 Minifilter 结构允许多个过滤驱动共存,而 Altitude 数值用于规定每个驱动的层级位置,高度越低的驱动越靠近文件系统核心,优先处理文件操作。

而Sfilter要自己手动Attach,如果多个过滤设备的话,加载顺序难免会较难控制

2.Minifilter具有可卸载能力
legacy filter卸载可能会蓝屏,原因是可能存在仍在处理的IRP请求

3.CallBack模型,只需要处理必要操作的能力

提供了Pre和Post两个类型的回调

4.兼容性更好

5.名字更好处理:提供了函数一键获取文件路径

6.同样遵循IRQL,锁等内核机制

总体框架

1731294907375

以上是用Filter管理器简化后的I/O栈和minifilter驱动

流程是这样的

首先由用户模式发送的文件I/O发送给 I/O 管理器,由I/O管理器去将用户层发来的数据和命令封装为IRP

原来的模式是,I/O管理器将这个IRP一次发给文件系统驱动,再发给磁盘系统驱动,最后发给硬件

现在多了一个FilterManager,Filter Manager 位于 I/O 管理器文件系统驱动 之间的中间层。它是一个内核组件,用于管理文件系统的 Minifilter 驱动。Filter Manager 通过拦截并协调所有 I/O 请求,为所有文件系统 I/O 操作提供了一个必经之路,以便调用注册的 Minifilter 驱动。

现在I/O管理器发IRP的时候,要先发给Filter Manager,如果注册了Minifilter,这个Filter Manager会把IRP封装为 CALLBACK_DATA这个结构,然后发给Minifilter进行处理

minifilter传递顺序是 A->B->C,任何一层都可以决定是否将IRP继续往下发或者到此为止。

1731296572086

稍微复杂点:

这幅图就告诉我们,可以既安装Legacy Filter,也可以安装Minifilter,两者不冲突,另外, Windows 系统将 Legacy Filter 固定在 Frame 1 和 Frame 0 之间是为了确保系统的兼容性和稳定性。 如果希望在更低的层次拦截请求,建议使用 Minifilter 驱动程序代替 Legacy Filter Driver。Minifilter 驱动通过 Altitude 值进行排序,可以放置在 Frame 0 以达到较低的优先级。

Altitude值:20000–429999

每一个minifilter驱动必须有一个叫做altitude的唯一标识符,每一个minifilter驱动的altitude定义了它加载时在I/O栈中相对于其他minifilter驱动的位置,值越小,栈中的位置就越低

FSFilter Anti-Virus 320000-329999 此组包括在文件I/O期间探测并杀毒的过滤驱动

FSFilter Encryption 140000-149999此组包括在文件I/O期间加密和解密数据的过滤驱动

320000-329999140000-149999 之间的范围可能属于其他用途,或者被其他特定类型的驱动(如备份、文件同步、系统监控等)使用。

它们是由 微软 预设的 常用用途范围

Minifilter架构

FLT_REGISTRATION 结构体

FLT_REGISTRATION 结构体是 Minifilter 驱动 注册过程中的关键数据结构,用于定义和管理 Minifilter 驱动的属性和行为。它在 Windows 操作系统中扮演着至关重要的角色,负责告诉操作系统如何加载和配置 Minifilter 驱动。通过 FLT_REGISTRATION,Minifilter 驱动能够注册其处理的文件系统操作、过滤的路径、需要的操作模式等信息。

结构体定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typedef struct _FLT_REGISTRATION {
USHORT Size; // 结构体大小
USHORT Version; // 结构体版本
FLT_REGISTRATION_FLAGS Flags; // 驱动注册标志
const FLT_CONTEXT_REGISTRATION *ContextRegistration; // 上下文注册
const FLT_OPERATION_REGISTRATION *OperationRegistration; // 操作注册
PFLT_FILTER_UNLOAD_CALLBACK FilterUnloadCallback; // 卸载回调
PFLT_INSTANCE_SETUP_CALLBACK InstanceSetupCallback; // 实例创建回调
PFLT_INSTANCE_QUERY_TEARDOWN_CALLBACK InstanceQueryTeardownCallback; // 实例销毁查询回调
PFLT_INSTANCE_TEARDOWN_CALLBACK InstanceTeardownStartCallback; // 实例销毁开始回调
PFLT_INSTANCE_TEARDOWN_CALLBACK InstanceTeardownCompleteCallback; // 实例销毁完成回调
PFLT_GENERATE_FILE_NAME GenerateFileNameCallback; // 文件名生成回调
PFLT_NORMALIZE_NAME_COMPONENT NormalizeNameComponentCallback; // 名称规范化回调
PFLT_NORMALIZE_CONTEXT_CLEANUP NormalizeContextCleanupCallback; // 上下文清理回调
PFLT_TRANSACTION_NOTIFICATION_CALLBACK TransactionNotificationCallback; // 事务通知回调
PFLT_NORMALIZE_NAME_COMPONENT_EX NormalizeNameComponentExCallback; // 扩展的名称规范化回调
PFLT_SECTION_CONFLICT_NOTIFICATION_CALLBACK SectionNotificationCallback; // 分区冲突通知回调
} FLT_REGISTRATION, *PFLT_REGISTRATION;

字段详细解释

  1. Size

    • 类型:USHORT
    • 说明:该字段指定结构体的大小,通常设置为 sizeof(FLT_REGISTRATION)。操作系统使用该字段来确保正确解析结构体。
  2. Version

    • 类型:USHORT
    • 说明:此字段指定 FLT_REGISTRATION 结构体的版本,通常为 1。
  3. Flags

    • 类型:FLT_REGISTRATION_FLAGS
    • 说明:此字段包含注册标志,用于定义 Minifilter 驱动的行为。例如,FLT_REGISTRATION_FLAG_SUPPORTS_DYNAMIC_FILTERING 标志表明该驱动支持动态启用或禁用过滤操作。
  4. ContextRegistration

    • 类型:const FLT_CONTEXT_REGISTRATION*
    • 说明:该字段用于注册上下文类型。上下文允许 Minifilter 驱动在操作期间存储与文件或文件系统实例相关的特定数据。此字段指向 FLT_CONTEXT_REGISTRATION 结构体,定义了上下文类型及其生命周期。
  5. OperationRegistration

    • 类型:const FLT_OPERATION_REGISTRATION*
    • 说明: 是 FLT_REGISTRATION 结构体中的一个关键字段,它定义了 Minifilter 驱动支持的所有文件操作及其对应的回调函数。这个字段包含 FLT_OPERATION_REGISTRATION 数组,列出驱动可以处理的所有操作,如文件创建、读取、写入等。
  6. FilterUnloadCallback

    • 类型:PFLT_FILTER_UNLOAD_CALLBACK
    • 说明:指向回调函数的指针,负责在 Minifilter 驱动卸载时执行清理操作。当驱动被卸载时,该回调会被调用。
  7. InstanceSetupCallback

    • 类型:PFLT_INSTANCE_SETUP_CALLBACK
    • 说明:在加载 Minifilter 驱动时,操作系统会遍历系统中的所有卷设备,并为每个卷设备创建一个实例(实例与卷设备绑定)。每当为卷设备创建一个新实例时,InstanceSetupCallback 就会被调用。此回调函数用于初始化该实例,设置与实例相关的上下文或资源,记录下卷设备的名称,扇区大小等信息
  8. InstanceQueryTeardownCallback

    • 类型:PFLT_INSTANCE_QUERY_TEARDOWN_CALLBACK
    • 说明:指向回调函数的指针,负责在 Minifilter 驱动实例销毁前进行查询操作。该回调允许在销毁实例之前进行清理或状态检查。
  9. InstanceTeardownStartCallback

    • 类型:PFLT_INSTANCE_TEARDOWN_CALLBACK
    • 说明:指向回调函数的指针,负责在 Minifilter 驱动实例开始销毁时执行清理工作。当驱动实例准备销毁时,该回调会被调用。
  10. InstanceTeardownCompleteCallback

    • 类型:PFLT_INSTANCE_TEARDOWN_CALLBACK
    • 说明:指向回调函数的指针,负责在 Minifilter 驱动实例销毁完成后执行清理工作。此回调会在实例完全销毁后调用。
  11. GenerateFileNameCallback

    • 类型:PFLT_GENERATE_FILE_NAME
    • 说明:指向回调函数的指针,负责生成文件路径名。此回调可以用于将文件路径从内部格式转换为外部格式或进行其他路径转换。
  12. NormalizeNameComponentCallback

    • 类型:PFLT_NORMALIZE_NAME_COMPONENT
    • 说明:指向回调函数的指针,用于处理文件路径组件的规范化。它可以确保文件路径按照标准的格式进行处理。
  13. NormalizeContextCleanupCallback

    • 类型:PFLT_NORMALIZE_CONTEXT_CLEANUP
    • 说明:指向回调函数的指针,负责清理与文件路径规范化过程相关的上下文数据。
  14. TransactionNotificationCallback

    • 类型:PFLT_TRANSACTION_NOTIFICATION_CALLBACK
    • 说明:指向回调函数的指针,用于处理文件 I/O 操作中的事务通知。该回调函数可用于在文件操作发生时执行某些事务控制操作。
  15. NormalizeNameComponentExCallback

    • 类型:PFLT_NORMALIZE_NAME_COMPONENT_EX
    • 说明:指向回调函数的指针,扩展的名称组件规范化回调,通常用于处理更复杂的路径和组件规范化操作。
  16. SectionNotificationCallback

    • 类型:PFLT_SECTION_CONFLICT_NOTIFICATION_CALLBACK
    • 说明:指向回调函数的指针,用于处理文件系统中的分区冲突。这种情况通常出现在文件 I/O 操作跨越了文件系统或分区的边界时。

注册监控回调示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const FLT_OPERATION_REGISTRATION fileMonitorCallBacks[]=
{
{
IRP_MJ_CREATE,
FLTFL_OPERATION_REGISTRATION_SKIP_PAGING_IO,
HOOK_PreNtCreateFile,
HOOK_PostNtCreateFile,
},
{
IRP_MJ_CLEANUP,
0,
HOOK_PreNtCleanup,
0
},
{
IRP_MJ_WRITE,
0,
HOOK_PreNtWriteFile,
0
}
}

这里要说一下为什么IRP_MJ_CREATE这里,flag要设置为FLTFL_OPERATION_REGISTRATION_SKIP_PAGING_IO
微软介绍为:

微筛选器为读取或写入操作设置此标志,以指定不应为分页 I/O 操作调用其操作前和操作后回调例程。 此标志仅适用于基于 IRP 的 I/O 操作。 对于不基于 IRP 的 I/O 操作,将忽略它。

首先我们要先介绍下什么是IRP_PAGING_IO
这是由系统MM发起的

IRP_PAGING_IO主要是由系统缓存管理器发起的IO操作,所以对于系统的操作,我们一般不去管

通常IO请求的过程是

IRP_NOCACHEIRP_CACHE 是指 I/O 请求是否通过缓存管理器进行数据缓存操作:

情况1:不开启缓存管理器,App -> IO -> FSD -> DISK

情况2: 开启缓存管理器 , App(应用程序) -> IO( I/O 管理器) -> FSD(文件系统驱动) -> CC(缓存管理) -> MM(内存管理)( -> FSD -> DISK(磁盘))

即应用程序发起的读操作会首先经过 I/O 管理器,然后由文件系统驱动尝试直接从缓存中读取。缓存未命中时(比如就读写一个字节,那么不会立刻被写入),请求会通过内存管理器加载数据至缓存,最终从磁盘获取数据。

IRP_PAGING_IO:在情况2中,MM会发起一个IRP并标记为 IRP_XXX_PAGING_IO,因为是由MM发起的,是系统干的活,所以我们一般去忽略

minifilter里面没有IRP了,变为PFLT_CALLBACK_DATA,这个就是从IRP来的

Pre回调:

1
2
3
4
5
6
7
8
PFLT_PRE_OPERATION_CALLBACK PfltPreOperationCallback;

FLT_PREOP_CALLBACK_STATUS PfltPreOperationCallback(
[in, out] PFLT_CALLBACK_DATA Data,
[in] PCFLT_RELATED_OBJECTS FltObjects,
[out] PVOID *CompletionContext
)
{...}

**PFLT_CALLBACK_DATA Data**(输入/输出):

  • Data 是指向 FLT_CALLBACK_DATA 结构体的指针,其中包含了关于即将执行的 I/O 请求的详细信息,包括操作的类型、缓冲区、目标对象等。
  • 该结构体中的字段可以帮助你获取操作类型(如读取、写入)、请求缓冲区、偏移量等信息。
  • 常用字段:
    • **Iopb**:包含请求参数(如偏移量、目标文件、操作缓冲区、长度等)。
    • **IoStatus**:可以在回调中设置操作的完成状态,比如可以设置为失败状态来阻止操作。
  • PreOperation 回调中,开发者可以使用此结构来修改请求的参数,记录日志,或阻止操作。

**PCFLT_RELATED_OBJECTS FltObjects**(输入):

  • FltObjects 是指向 FLT_RELATED_OBJECTS 结构体的指针,包含了与当前 I/O 操作有关的对象(例如文件、实例、卷等)。
  • 常用字段包括:
    • **Instance**:当前过滤器的实例,可以用来判断在哪个卷上处理该请求。
    • **FileObject**:与当前操作相关的文件对象,表示目标文件。
    • **Filter**:当前操作所在的过滤器对象。
  • FltObjects 提供了当前操作的上下文信息,便于判断目标文件、卷或过滤器实例的信息,并对特定的文件或卷执行条件处理。

**PVOID \*CompletionContext**(输出):

  • CompletionContext 是一个指向指针的输出参数,用于在 PreOperationPostOperation 之间传递自定义的上下文信息。
  • 你可以在 PreOperation 中分配一个上下文结构,将其地址赋给 CompletionContext,后续会将这个上下文指针传递给 PostOperation 回调,以便在操作完成时执行进一步处理或清理工作。
  • 若不需要传递上下文,设置为 NULL 即可。

返回值:

FLT_PREOP_SUCCESS_WITH_CALLBACK`

表示预操作处理成功,并且希望在该操作完成后调用 PostOperation 回调。

适用于希望在操作结束后执行进一步处理的情况。

会往下传递

在返回 FLT_PREOP_SUCCESS_WITH_CALLBACK 后,操作会继续往下传递,传递到文件系统驱动等下层组件后再执行实际的 I/O 操作。等到这个操作完成,并且返回到 minifilter 驱动时,才会调用 PostOperation 回调。因此,PostOperation 回调是在 I/O 操作传递到下层并执行完成之后才会执行的。

FLT_PREOP_SUCCESS_NO_CALLBACK

表示预操作处理成功,但不需要调用 PostOperation 回调。

适用于不需要进一步处理操作完成情况的场景,返回该值可以减少性能开销。

会往下传递

**FLT_PREOP_PENDING **

表示该操作尚未完成,需要异步处理。

返回此值时,开发者需自行完成 I/O 操作,并调用 FltCompletePendedPreOperation 来通知过滤器框架操作已完成。

适用于异步处理或复杂操作场景。

FLT_PREOP_DISALLOW_FASTIO

表示不允许此操作通过快速 I/O (Fast I/O) 通道执行,操作将转至标准 I/O 路径。

适用于需要绕过快速路径以保证一致性或进行特定处理的场景。

FLT_PREOP_COMPLETE

表示立即完成此操作,不再传递给下层文件系统。

可用于阻止操作,或自行完成处理(例如返回一个特定错误状态)。

IoStatus.StatusIoStatus.Information 应在 FLT_CALLBACK_DATA 中设置,以指示操作的结果。

一般我们在Pre做 SandBox,主防,加解密,杀毒引擎等

Pre函数一般通常在 PASSIVE LEVEL(低级别)APC LEVEL 执行。大部分情况下,它是在 PASSIVE LEVEL 下调用的

Post回调:

1
2
3
4
5
6
7
8
9
PFLT_POST_OPERATION_CALLBACK PfltPostOperationCallback;

FLT_POSTOP_CALLBACK_STATUS PfltPostOperationCallback(
[in, out] PFLT_CALLBACK_DATA Data,
[in] PCFLT_RELATED_OBJECTS FltObjects,
[in, optional] PVOID CompletionContext,
[in] FLT_POST_OPERATION_FLAGS Flags
)
{...}

参数说明

  1. **PFLT_CALLBACK_DATA Data**(输入/输出):

    • Data 是指向 FLT_CALLBACK_DATA 结构体的指针,包含了有关正在处理的 I/O 操作的详细信息,例如请求的类型、目标对象、缓冲区信息等。

    • FLT_CALLBACK_DATA
      
      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
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79

      中常用字段包括:

      - **`Iopb`**:请求参数块,包含当前操作的详细信息,如操作的文件对象、文件偏移量、缓冲区地址、长度等。
      - **`IoStatus`**:用于表示操作的状态,如成功、失败等。
      - **`RequestorMode`**:指定了请求的模式,用户模式或内核模式。

      - 通过这个结构体,开发者可以读取、修改 I/O 请求的数据或状态。

      2. **`PCFLT_RELATED_OBJECTS FltObjects`**(输入):

      - `FltObjects` 是指向 `FLT_RELATED_OBJECTS` 结构体的指针,该结构体包含了与当前 I/O 操作相关的对象指针。
      - 其中包括:
      - **`Instance`**:当前过滤器的实例,表示当前操作在哪个卷上执行。
      - **`FileObject`**:文件对象指针,代表当前操作的目标文件。
      - **`Filter`**:当前调用的过滤器对象。
      - **`Volume`**:表示此 I/O 操作所在的卷对象。
      - 通过这些对象,开发者可以获取到有关当前操作目标的上下文,从而实现对卷、文件的操作和判断。

      3. **`PVOID CompletionContext`**(可选):

      - `CompletionContext` 用于在前后操作回调之间传递自定义的上下文信息。通常由前置回调函数 (`PreOperation`) 提供,用来在后置回调函数中完成某些清理工作或共享信息。
      - 该值的类型是 `PVOID`,因此可以指向任何类型的数据结构,比如某种操作的状态信息、标志位等。

      4. **`FLT_POST_OPERATION_FLAGS Flags`**(输入):

      - `Flags` 是 `FLT_POST_OPERATION_FLAGS` 枚举,指示了操作完成时的一些附加信息或标志。
      - 主要标志:
      - **`FLTFL_POST_OPERATION_DRAINING`**:表示驱动栈正在关闭,回调可能在清理过程中被调用。
      - **`FLTFL_POST_OPERATION_SYNCHRONIZE`**:指示后置回调是同步的,即与原始操作同一线程执行。





      #### **返回值:**

      **FLT_POSTOP_FINISHED_PROCESSING**

      表示后操作处理已完成,不需要进一步处理。

      适用于无需延迟操作或后续异步处理的情况。

      微筛选器驱动程序已完成 I/O 操作的处理,并将操作的控制权返回给筛选器管理器。



      **FLT_POSTOP_MORE_PROCESSING_REQUIRED**

      表示需要进一步的处理,通常用于异步操作。

      在 `PostOperation` 回调函数中返回 `FLT_POSTOP_MORE_PROCESSING_REQUIRED` 后,Minifilter 框架会等待 minifilter 驱动通过调用特定的 API 来指示何时完成进一步的处理。通常情况下,你需要在另一个上下文中(例如在工作线程或其他延迟执行的环境中)完成进一步的处理,最后通过调用 `FltCompletePendedPostOperation` 来通知框架该操作处理已结束。

      ```c++
      FLT_POSTOP_CALLBACK_STATUS PostOperationCallback(
      PFLT_CALLBACK_DATA Data,
      PCFLT_RELATED_OBJECTS FltObjects,
      PVOID CompletionContext,
      FLT_POST_OPERATION_FLAGS Flags
      ) {
      // 判断是否需要延迟处理
      if (需要延迟处理) {
      // 启动异步处理(例如将工作项加入系统队列)
      FltQueueDeferredIoWorkItem(...);
      // 返回 MORE_PROCESSING_REQUIRED,表明稍后会完成操作
      return FLT_POSTOP_MORE_PROCESSING_REQUIRED;
      }

      // 立即完成操作的情况
      return FLT_POSTOP_FINISHED_PROCESSING;
      }

      // 在工作线程中完成延迟处理
      VOID DeferredPostOperationWorkItemRoutine(PFLT_CALLBACK_DATA Data) {
      // 进行延迟处理...

      // 处理完成后调用 FltCompletePendedPostOperation 完成操作
      FltCompletePendedPostOperation(Data);
      }

判断Data是什么操作的宏:

FLT_IS_IRP_OPERATION:
判断操作是否为 I/O 请求包(IRP)。
IRP 操作涉及标准文件系统操作,例如文件创建、读写、关闭等。

FLT_IS_FASTIO_OPERATION:
判断操作是否为快速 I/O(Fast I/O)请求。
Fast I/O 是一种优化方式,绕过常规 IRP 通道执行文件操作,适用于较简单的读写操作。

``FLT_IS_FS_FILTER_OPERATION`:
判断操作是否为文件系统过滤操作(FS Filter)。
FS Filter 操作通常用于文件系统驱动间的特殊通信,处理缓存、加密、压缩等请求。

Minifilter的启动

简化代码如下

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
NTSTATUS initFileMonitor(PDRIVER_OBJECT DriverObject)
{
return FltRegisterFilter(
DriverObject,
&fileMonitorRegistration,
&g_pFilter
);
}


NTSTATUS startFileMonitor(PDRIVER_OBJECT DriverObject)
{
if (g_pFilter)
{
return FltStartFiltering(g_pFilter);
}
return STATUS_INSUFFICIENT_RESOURCES;//STATUS_INSUFFICIENT_RESOURCES 是一个 NTSTATUS 错误代码,表示系统资源不足,导致无法完成请求的操作。
}


NTSTATUS stopFileMonitor(PDRIVER_OBJECT DriverObject)
{
if (g_pFilter)
{
FltUnregisterFilter(g_pFilter);
g_pFilter = NULL;
}
}

Nullfilter安装

inf文件安装

首先我们要知道什么叫inf文件

在Windows文件系统的Minifilter驱动程序中,.inf文件是安装信息文件,它用来描述驱动的安装方式,包括注册表设置、文件拷贝、设备驱动安装等。总之就是.inf文件会告诉系统如何安装和配置Minifilter驱动。它可以包含以下信息:

  1. 服务注册:定义服务的名称、类型、启动方式等。
  2. 拷贝文件:指定驱动文件(.sys文件)和其他必要的文件应该复制到什么位置。
  3. 注册表设置:配置与驱动相关的注册表键值,如用于Minifilter驱动的实例化和加载的配置信息。
  4. 安装顺序:定义在系统加载时如何初始化和加载Minifilter。

.inf文件的核心作用就是简化和自动化Minifilter驱动的安装过程,避免手动配置注册表或移动文件。

我们不必自己写,了解下原理拿别人的稍微改改即可

首先我们来分析一下nullfilter的inf文件

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
;;;
;;; NullFilter
;;;
;;;
;;; Copyright (c) 1999 - 2002, Microsoft Corporation
;;;

[Version]
Signature = "$Windows NT$" ;标识安装文件格式适用于 Windows NT。
Class = "ActivityMonitor" ;指定驱动的类别名称为 "ActivityMonitor",用于表示这是一个监控类的文件系统驱动。
ClassGuid = {b86dff51-a31e-4bac-b3cf-e8cfe75c9fc2} ;为该类指定唯一的 GUID 标识符。
Provider = %ProviderString% ;提供商的字符串信息,此处定义为 TODO-Set-Provider。
DriverVer = 06/16/2007,1.0.0.0;驱动的版本信息,包含日期和版本号。
CatalogFile = nullfilter.cat;安装时需要的 .cat 文件(nullfilter.cat),用于驱动签名和验证。
PnpLockdown = 1;指定即插即用设备的限制级别。


;定义源文件和安装盘的信息,nullfilter.sys 是主驱动文件。
[SourceDisksFiles]
nullfilter.sys = 1,,

[SourceDisksNames]
1 = %DiskId1%,,,

[DestinationDirs] ;12 代表 system32\drivers,13 代表驱动程序存储目录。
NullFilterDownlevel.CopyDriverFiles = 12 ;%windir%\system32\drivers
NullFilterDownlevel.DelDriverFiles = 12 ;%windir%\system32\drivers
NullFilter.DriverFiles = 13 ;driver store

;;
;; Default install sections
;;

[DefaultInstall.NTamd64.10.0...25952]
OptionDesc = %ServiceDescription%
CopyFiles = NullFilter.DriverFiles

[DefaultInstall.NTamd64.10.0...25952.Services]
AddService = %ServiceName%,,NullFilter.Service ;

;
; Support sections
;

[NullFilter.Service]
DisplayName = %ServiceName%
Description = %ServiceDescription%
ServiceBinary = %13%\%DriverName%.sys ;%windir%\system32\drivers\ 指定驱动文件的位置。
Dependencies = "FltMgr" ;表示依赖的服务,此处是 "FltMgr"(文件系统过滤管理器)。
ServiceType = 2 ;SERVICE_FILE_SYSTEM_DRIVER 表示文件系统驱动。
StartType = 3 ;SERVICE_DEMAND_START 表示按需启动(即手动启动)。
ErrorControl = 1 ;SERVICE_ERROR_NORMAL
LoadOrderGroup = "FSFilter Activity Monitor" ;设置加载顺序组为 "FSFilter Activity Monitor"。
AddReg = NullFilter.AddRegistry ;指定与服务相关的注册表键(由 NullFilter.AddRegistry 定义)

[NullFilter.AddRegistry] ;配置该驱动实例的注册表信息。
HKR,"Parameters","SupportedFeatures",0x00010001,0x3 ;设置驱动支持的功能。
HKR,"Parameters\Instances","DefaultInstance",0x00000000,%DefaultInstance% ;设置默认实例名称。
HKR,"Parameters\Instances\"%Instance1.Name%,"Altitude",0x00000000,%Instance1.Altitude% ;配置实例的 "Altitude" 和 "Flags" 属性,用于控制Minifilter驱动的加载顺序和自动附加行为。
HKR,"Parameters\Instances\"%Instance1.Name%,"Flags",0x00010001,%Instance1.Flags%

[NullFilter.DriverFiles]
%DriverName%.sys ;定义复制到目标系统的驱动文件(nullfilter.sys)。

;;
;; Downlevel default install sections
;;
;下层支持安装部分(Downlevel Sections)
;这些部分用于向旧版 Windows 提供兼容支持,定义了下层系统的驱动文件复制、服务配置等。


[DefaultInstall.NTamd64]
OptionDesc = %ServiceDescription%
CopyFiles = NullFilterDownlevel.CopyDriverFiles

[DefaultInstall.NTamd64.Services]
AddService = %ServiceName%,,NullFilterDownlevel.Service

; 下层支持卸载部分

[DefaultUninstall.NTamd64]
LegacyUninstall = 1
DelFiles = NullFilterDownlevel.DelDriverFiles

[DefaultUninstall.NTamd64.Services]
DelService = %ServiceName%,0x200 ;确保在删除之前停止服务

;
; Downlevel support sections
;

[NullFilterDownlevel.Service]
DisplayName = %ServiceName%
Description = %ServiceDescription%
ServiceBinary = %12%\%DriverName%.sys ;%windir%\system32\drivers\
Dependencies = "FltMgr"
ServiceType = 2 ;SERVICE_FILE_SYSTEM_DRIVER
StartType = 3 ;SERVICE_DEMAND_START
ErrorControl = 1 ;SERVICE_ERROR_NORMAL
LoadOrderGroup = "FSFilter Activity Monitor"
AddReg = NullFilterDownlevel.AddRegistry

[NullFilterDownlevel.AddRegistry]
HKR,,"SupportedFeatures",0x00010001,0x3
HKR,"Instances","DefaultInstance",0x00000000,%DefaultInstance%
HKR,"Instances\"%Instance1.Name%,"Altitude",0x00000000,%Instance1.Altitude%
HKR,"Instances\"%Instance1.Name%,"Flags",0x00010001,%Instance1.Flags%

[NullFilterDownlevel.CopyDriverFiles]
%DriverName%.sys

[NullFilterDownlevel.DelDriverFiles]
%DriverName%.sys

;;
;; String Section
;;

[Strings] ;该部分定义了 .inf 文件中使用的字符串变量。
ProviderString = "TODO-Set-Provider"
ServiceDescription = "NullFilter mini-filter driver"
ServiceName = "NullFilter"
DriverName = "NullFilter"
DiskId1 = "NullFilter Device Installation Disk"

;Instances specific information.
DefaultInstance = "Null Instance"
Instance1.Name = "Null Instance"
Instance1.Altitude = "370020"
Instance1.Flags = 0x1 ; Suppress automatic attachments

以上有一个很坑的点,将所有 NT$ARCH$ 替换为 NTamd64(表示 x64 架构)。从Github脱下来的代码都是ARCH的,这一点要自己手动改改。

[Version]中的classGUID查看以下链接

为供应商提供的系统定义的设备安装程序类 - Windows drivers | Microsoft Learn

如果是自己写的minifilter,直接在[String]这里改一改就好了

必须修改的有

**ServiceName**: 每个 Minifilter 在系统中需要唯一的服务名称,这样才能在服务控制器中分别管理它们。

**DriverName**不同的 Minifilter 需要有各自唯一的驱动文件名,以防止文件名冲突。

Instance1.Name 和 **DefaultInstance**: 每个 Minifilter 实例在系统中也需要唯一的名称。

**Instance1.Altitude**: Minifilter 驱动的 Altitude 值必须是唯一的,以确定它在文件过滤栈中的相对位置。

然后把编译好的驱动,和Inf文件放在同一个文件夹

1731458444190

在inf文件右键,即可安装

1731458517564

具体有没有成功,可以看/Windows/System32/Driver目录里面有没有我们的nullfilter.sys,这里可以看到已经进入目录了

1731458618230

然后再看看注册表

.inf 文件中没有明确指定 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\ 这一完整路径,因为它是安装服务的默认注册表路径。Windows 系统会自动将所有在 [Service][AddService] 部分指定的服务,注册到 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\ 下。

1731460139496

都做好之后,就是安装这个nullfilter了

用管理员权限打开cmd,安装的命令是

1
net start nullfilter

卸载的命令是:

1
net stop nullfilter

1731460577174

可执行程序安装

后续补….

参数数据的获取

从PFLT_CALLBACK_DATA获取

PFLT_CALLBACK_DATA这个结构我们能拿到什么?

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
41
#include <fltKernel.h>

PFLT_CALLBACK_DATA Data; // 假设 Data 是从回调函数中传入的参数
PEPROCESS processObject = nullptr;

// 获取当前进程对象
if (Data->Thread)
{
processObject = IoThreadToProcess(Data->Thread);
} else
{
processObject = PsGetCurrentProcess();
}

// 获取当前进程的 PID
ULONG pid = HandleToUlong(PsGetProcessId(processObject));

// 设置 I/O 状态
Data->IoStatus.Status = STATUS_ACCESS_DENIED;//(给上层调用者或发起 I/O 操作的进程看的 )
Data->IoStatus.Information = 0;

// 获取 FltObjects 中的信息
PFLT_VOLUME Volume = FltObjects->Volume;//卷设备
PFLT_INSTANCE Instance = FltObjects->Instance;//实例对象,指向当前过滤器的实例
PFILE_OBJECT FileObject = FltObjects->FileObject;//文件对象
PDEVICE_OBJECT DeviceObject = FileObject->DeviceObject;//设备对象

// 获取读取操作相关信息
PMDL pReadMdl = Data->Iopb->Parameters.Read.MdlAddress;//MDL 地址
PVOID pReadBuffer = Data->Iopb->Parameters.Read.ReadBuffer;//读取缓冲区地址
ULONG uReadLength = Data->Iopb->Parameters.Read.Length;//读取缓冲区长度

// 获取创建操作的安全上下文
ACCESS_MASK DesiredAccess = Data->Iopb->Parameters.Create.SecurityContext->DesiredAccess;//访问权限,通过 DesiredAccess 字段可以获取或设置当前创建请求的所需访问权限。

// 获取查询操作相关信息
PVOID pQueryBuffer = Data->Iopb->Parameters.DirectoryControl.QueryDirectory.DirectoryBuffer;//查询目录的缓冲区
ULONG uQueryBufferSize = Data->Iopb->Parameters.DirectoryControl.QueryDirectory.Length;//查询目录的缓冲区长度

// 返回 FLT_PREOP_COMPLETE 表示操作完成,给Minifilter管理器看的
return FLT_PREOP_COMPLETE;

当前进程信息 , I/O 状态信息 , 卷、实例、文件对象及设备对象 , 读取操作相关信息 , 安全上下文 , 查询操作相关信息 , 返回状态

获取操作的文件路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 在postcreate里获取文件路径
PFLT_FILE_NAME_INFORMATION pNameInfo = NULL;

ntStatus = FltGetFileNameInformation(
Data,
FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT,
&pNameInfo
);
FltParseFileNameInformation(pNameInfo);

pNameInfo->Name;//这里可以提取出信息
pNameInfo->Volume;

FltReleaseFileNameInformation(pNameInfo);

// 重命名路径的获取
PFILE_RENAME_INFORMATION pFileRenameInformation = (PFILE_RENAME_INFORMATION)Data->Iopb->Parameters.SetFileInformation.InfoBuffer;

FltGetDestinationFileNameInformation // 重命名路径获取

FltGetFileNameInformation:此函数通过 Data(即 PFLT_CALLBACK_DATA 类型)来提取文件名信息,并将信息存储在 pNameInfo 指针指向的结构中。参数 FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT 指示返回标准化的路径。 FltParseFileNameInformation:进一步解析文件名信息,将提取的内容填入 pNameInfoNameVolume 字段。

FltReleaseFileNameInformation:在使用完 pNameInfo 后,必须调用此函数来释放分配的内存,避免内存泄漏。

重命名路径的获取:当文件被重命名时,需要获取新的文件名信息。代码通过 PFILE_RENAME_INFORMATION 类型的指针 pFileRenameInformation 来访问新文件名信息。

Minifilter上下文

上下文的介绍和分类

Context上下文:其实就是附着在某个对象上的一段内存,内存缓存相关数据,这段数据由自己定义

举个例子,对象就是一个人,上下文就是对应这个人的口袋,里面有钱包,手机啥的,这是一个人几乎必须要带在身上的,而不是需要的时候再冲回家拿

Stream Context :

这是绑定在FILE CONTROL BLOCK的上下文,文件和FCB是一对一的关系,FCB主要用于文件系统内部, 存储文件的物理信息、文件属性、大小、路径等 。用于处理与磁盘存储相关的操作,像文件的读写、位置定位等。

FCB 主要与文件系统相关,存储文件的磁盘元数据,关注文件的物理存储和属性,管理文件的全局状态。它与文件系统的底层操作紧密相关。

Stream Handle Context

绑定在FO(File Object)的上下文,一个文件可以对应多个FO,属于一对多的关系。File Object存储文件的访问信息、当前读写位置、文件句柄、权限等

FO 是文件在内存中的表示,关注文件在进程中的使用和访问。每个打开的文件在每个进程中都会有一个 FO,它存储了文件的状态、读写偏移、句柄等。

Instance Context

与 Minifilter 实例相关的上下文,允许您存储有关 Minifilter 驱动程序实例的全局状态信息。

Volume Context

在一个与磁盘卷相关的过滤操作中,Volume Context 可以用于存储与该卷的 I/O 操作、缓存状态、挂载信息等相关的上下文。

用于与磁盘卷相关的上下文,通常在与卷的操作(例如卷的挂载或卸载)相关时使用。

FltAllocateContext 用于分配一个新的上下文对象。 这是一块内存,存着某个对象的具体信息

1
2
3
4
5
6
7
8
9
PFLT_CONTEXT streamContext;
NTSTATUS status;
status = FltAllocateContext(
gFilterHandle, // minifilter 驱动程序的过滤器句柄
FLT_STREAM_CONTEXT, // 上下文类型
sizeof(MY_STREAM_CONTEXT), // 上下文大小
NonPagedPool, // 内存池类型
&streamContext // 返回的上下文对象指针
);

注册上下文的过程:

1.定义上下文类型:

可以通过 FLT_CONTEXT_REGISTRATION 数组来定义您的上下文类型,并将其传递给 FltRegisterContext。还可以创建上下文回调函数 (可选)

1
2
3
4
5
6
7
8
9
10
11
FLT_CONTEXT_REGISTRATION ContextRegistration[] = {
{
sizeof(FLT_CONTEXT_REGISTRATION), // 结构大小
FLT_STREAM_CONTEXT, // 上下文类型
StreamContextCleanup, // 可选的清理回调
StreamContextAllocate, // 可选的分配回调
0, // 标志
NULL // 可选的时间戳回调
},
{0} // 终结符,表示注册表结束
};

这里的Cleanup是用来清理申请到的指针,不是用来清理自己上下文的

2.注册上下文

1
2
3
4
5
6
7
8
9
NTSTATUS Status;
Status = FltRegisterContext(
Filter, // Minifilter 驱动程序的过滤器实例
&ContextRegistration[0], // 上下文注册表
&gStreamContextRegHandle // 保存注册句柄
);
if (!NT_SUCCESS(Status)) {
// 错误处理
}

分配和释放上下文

1
2
3
4
5
6
7
8
9
10
11
12
13
// 分配上下文
PFLT_CONTEXT pContext = NULL;
Status = FltAllocateContext(
Filter, // Minifilter 驱动程序的过滤器实例
FLT_STREAM_CONTEXT, // 上下文类型
&pContext // 返回的上下文指针
);
if (NT_SUCCESS(Status)) {
// 使用上下文
}

// 释放上下文
FltReleaseContext(pContext);

Context的使用示例

代码:

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
typedef struct _INSTANCE_CONTEXT {
...
} INSTANCE_CONTEXT, *PINSTANCE_CONTEXT;

PINSTANCE_CONTEXT pContext = NULL;

// 分配与设置
ntStatus = FltGetInstanceContext(FltObjects->Instance, &pContext);//首先尝试获取上下文
if (NT_SUCCESS(ntStatus) == FALSE) {
ntStatus = FltAllocateContext(g_Filter, FLT_INSTANCE_CONTEXT,
sizeof(INSTANCE_CONTEXT),
PagedPool, &pContext);//如果获取失败,则重新分配
if (NT_SUCCESS(Status) == FALSE) {
return ntStatus;
}
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 pContext2 = NULL;
Status = FltGetInstanceContext(FltObjects->Instance, &pContext2);//现在就可以从这个Instance访问到上下文了
pContext2->xxx = xxx;

拿到上下文记得要Release掉

R3和R0的通讯

R3给R0发信息

驱动代码:

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#include <fltKernel.h>
#include <dontuse.h>

// 定义全局通信端口指针
PFLT_PORT g_ServerPort = NULL; // 用于保存服务器端的端口
PFLT_PORT g_ClientPort = NULL; // 用于保存客户端连接的端口

// 消息处理回调函数,接收来自用户模式的消息
NTSTATUS
MinifilterMessageCallback(
PVOID ConnectionCookie,
PVOID InputBuffer,
ULONG InputBufferSize,
PVOID OutputBuffer,
ULONG OutputBufferSize,
PULONG ReturnOutputBufferLength
) {
UNREFERENCED_PARAMETER(ConnectionCookie);

// 输出接收到消息的调试信息
DbgPrint("Received message from user mode\n");

// 检查输出缓冲区是否足够大,如果足够则返回消息
if (OutputBuffer && OutputBufferSize >= sizeof("Hello from Kernel")) {
RtlCopyMemory(OutputBuffer, "Hello from Kernel", sizeof("Hello from Kernel"));
*ReturnOutputBufferLength = sizeof("Hello from Kernel");
} else {
return STATUS_BUFFER_TOO_SMALL;
}

return STATUS_SUCCESS;
}

// 客户端连接回调函数,保存客户端的连接端口
VOID
MinifilterConnectCallback(
PVOID ConnectionCookie,
PFLT_PORT ClientPort
) {
UNREFERENCED_PARAMETER(ConnectionCookie);

// 保存客户端端口,供后续通信使用
g_ClientPort = ClientPort;
DbgPrint("User mode connected\n");
}

// 客户端断开连接回调函数,关闭客户端端口
VOID
MinifilterDisconnectCallback(
PVOID ConnectionCookie
) {
UNREFERENCED_PARAMETER(ConnectionCookie);

// 检查客户端端口是否存在,如果存在则关闭它
if (g_ClientPort) {
FltCloseClientPort(g_FilterHandle, &g_ClientPort);
g_ClientPort = NULL;
}
DbgPrint("User mode disconnected\n");
}

// 驱动入口函数,初始化过滤器并创建通信端口
NTSTATUS
DriverEntry(
PDRIVER_OBJECT DriverObject,
PUNICODE_STRING RegistryPath
) {
UNICODE_STRING portName = RTL_CONSTANT_STRING(L"\\MinifilterPort"); // 定义通信端口名称
FLT_REGISTRATION FilterRegistration = { sizeof(FLT_REGISTRATION), FLT_REGISTRATION_VERSION }; // 定义过滤器注册结构

// 注册过滤器
NTSTATUS status = FltRegisterFilter(DriverObject, &FilterRegistration, &g_FilterHandle);
if (!NT_SUCCESS(status)) {
return status;
}

// 创建通信端口,并指定连接、断开连接和消息处理的回调函数
status = FltCreateCommunicationPort(
g_FilterHandle,
&g_ServerPort,
&portName,
NULL,
MinifilterConnectCallback, // 客户端连接回调
MinifilterDisconnectCallback, // 客户端断开回调
MinifilterMessageCallback, // 消息处理回调
1
);

// 如果通信端口创建成功,则启动过滤器
if (NT_SUCCESS(status)) {
FltStartFiltering(g_FilterHandle);
} else {
// 如果创建通信端口失败,则注销过滤器
FltUnregisterFilter(g_FilterHandle);
}

return status;
}

// 驱动卸载函数,清理通信端口和过滤器
VOID
DriverUnload(
PDRIVER_OBJECT DriverObject
) {
// 关闭通信端口
if (g_ServerPort) {
FltCloseCommunicationPort(g_ServerPort);
}
// 注销过滤器
FltUnregisterFilter(g_FilterHandle);
}

全局变量

  • g_ServerPort:保存服务器端通信端口的句柄。
  • g_ClientPort:保存客户端连接的端口句柄。

MinifilterMessageCallback

  • 此回调函数处理从用户模式发送的消息。
  • 当用户模式程序发送消息到此端口时,MinifilterMessageCallback 会被调用。
  • 该函数检查输出缓冲区是否足够大,如果足够大,就将 “Hello from Kernel” 这条消息复制到输出缓冲区中返回给用户模式程序。

MinifilterConnectCallback

  • 此回调函数在用户模式程序连接到通信端口时被调用。
  • g_ClientPort 被设置为当前的客户端端口句柄,以便后续通信使用。
  • 打印调试信息表示连接已建立。

MinifilterDisconnectCallback

  • 此回调函数在用户模式程序断开连接时被调用。
  • 如果 g_ClientPort 存在(即有一个连接),则关闭该连接端口并将其指针设置为 NULL。
  • 打印调试信息表示连接已断开。

DriverEntry

  • 驱动的入口函数,初始化 Minifilter 并创建通信端口。
  • 通过调用 FltRegisterFilter 注册过滤器。
  • 使用 FltCreateCommunicationPort 创建通信端口,并提供连接、断开连接和消息处理的回调函数。
  • 如果通信端口创建成功,则调用 FltStartFiltering 启动过滤器。
  • 如果通信端口创建失败,则注销过滤器。

三环代码:

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
41
42
43
44
45
46
47
48
#include <windows.h>
#include <fltUser.h>
#include <iostream>

int main() {
HANDLE hPort = NULL; // 通信端口句柄,用于与内核模式的 Minifilter 驱动通信

// 连接到 Minifilter 驱动程序创建的通信端口
HRESULT hr = FilterConnectCommunicationPort(
L"\\MinifilterPort", // 通信端口的名称,需与内核中创建的端口名称一致
0,
NULL,
0,
NULL,
&hPort // 返回的通信端口句柄,用于后续的通信
);

// 检查连接是否成功
if (FAILED(hr)) {
std::cerr << "Failed to connect to port. Error: " << hr << std::endl;
return 1;
}

// 设置输出缓冲区,用于接收来自内核模式的响应
char outputBuffer[100] = {0}; // 用于存储从内核接收到的消息
DWORD bytesReturned; // 返回的字节数

// 向内核模式发送消息,并接收内核的响应
hr = FilterSendMessage(
hPort, // 通信端口句柄
"Hello from User Mode", // 发送到内核的消息
(DWORD)strlen("Hello from User Mode") + 1, // 消息长度,包括终止符
outputBuffer, // 输出缓冲区,用于存储内核的响应
sizeof(outputBuffer), // 输出缓冲区大小
&bytesReturned // 实际返回的字节数
);

// 检查消息是否成功发送并接收到响应
if (SUCCEEDED(hr)) {
std::cout << "Received from kernel: " << outputBuffer << std::endl;
} else {
std::cerr << "Failed to send message. Error: " << hr << std::endl;
}

// 关闭通信端口
CloseHandle(hPort);
return 0;
}

R0给R3发信息

三环代码:

用户模式程序首先需要使用 FilterCreateCommunicationPort 创建一个通信端口,并等待来自内核模式的消息。通过 FilterGetMessage 来获取内核模式发送的消息。

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <windows.h>
#include <fltUser.h>
#include <iostream>

#define BUFFER_SIZE 256

int main() {
HRESULT hr;
HANDLE hPort;
HANDLE hCompletionPort;
DWORD bytesReturned;
ULONG_PTR completionKey;
LPOVERLAPPED pOvlp;
char receiveBuffer[BUFFER_SIZE] = { 0 };

// 连接到内核模式创建的通信端口
hr = FilterConnectCommunicationPort(
L"\\MinifilterPort",
0,
NULL,
0,
NULL,
&hPort
);

if (FAILED(hr)) {
std::cerr << "Failed to connect to port. Error: " << hr << std::endl;
return 1;
}

// 创建一个I/O完成端口
hCompletionPort = CreateIoCompletionPort(hPort, NULL, 0, 1);
if (hCompletionPort == NULL) {
std::cerr << "Failed to create I/O completion port. Error: " << GetLastError() << std::endl;
CloseHandle(hPort);
return 1;
}

// 等待来自内核模式的消息
std::cout << "Waiting for messages from kernel..." << std::endl;

while (true) {
// 阻塞等待并接收消息
if (GetQueuedCompletionStatus(hCompletionPort, &bytesReturned, &completionKey, &pOvlp, INFINITE)) {
// 接收消息成功
std::cout << "Received message from kernel: " << receiveBuffer << std::endl;

// 处理完消息后,继续循环接收
} else {
std::cerr << "Failed to receive message. Error: " << GetLastError() << std::endl;
break;
}
}

// 关闭端口句柄和完成端口句柄
CloseHandle(hPort);
CloseHandle(hCompletionPort);

return 0;
}

内核代码:

内核模式 Minifilter 代码使用 FltSendMessage 函数向用户模式发送消息。请注意,用户模式程序需要先建立通信端口,内核模式才可以发送消息。

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#include <fltKernel.h>
#include <dontuse.h>

PFLT_PORT g_ServerPort = NULL;
PFLT_PORT g_ClientPort = NULL;

// 用户模式连接回调函数
NTSTATUS
MinifilterConnectCallback(
PVOID ConnectionCookie,
PFLT_PORT ClientPort
) {
UNREFERENCED_PARAMETER(ConnectionCookie);

g_ClientPort = ClientPort;
DbgPrint("User mode connected\n");
return STATUS_SUCCESS;
}

// 用户模式断开连接回调函数
VOID
MinifilterDisconnectCallback(
PVOID ConnectionCookie
) {
UNREFERENCED_PARAMETER(ConnectionCookie);

if (g_ClientPort) {
FltCloseClientPort(g_FilterHandle, &g_ClientPort);
g_ClientPort = NULL;
}
DbgPrint("User mode disconnected\n");
}

// 向用户模式发送消息的函数
VOID
SendMessageToUserMode(
const char* message
) {
if (g_ClientPort) {
ULONG bytesWritten;
NTSTATUS status = FltSendMessage(
g_FilterHandle,
&g_ClientPort,
(PVOID)message,
(ULONG)strlen(message) + 1,
NULL,
0,
&bytesWritten
);

if (NT_SUCCESS(status)) {
DbgPrint("Message sent to user mode: %s\n", message);
} else {
DbgPrint("Failed to send message. Status: 0x%X\n", status);
}
} else {
DbgPrint("No user mode client connected.\n");
}
}

// 驱动入口
NTSTATUS
DriverEntry(
PDRIVER_OBJECT DriverObject,
PUNICODE_STRING RegistryPath
) {
UNICODE_STRING portName = RTL_CONSTANT_STRING(L"\\MinifilterPort");
FLT_REGISTRATION FilterRegistration = { sizeof(FLT_REGISTRATION), FLT_REGISTRATION_VERSION };

NTSTATUS status = FltRegisterFilter(DriverObject, &FilterRegistration, &g_FilterHandle);
if (!NT_SUCCESS(status)) {
return status;
}

// 创建通信端口
status = FltCreateCommunicationPort(
g_FilterHandle,
&g_ServerPort,
&portName,
NULL,
MinifilterConnectCallback,
MinifilterDisconnectCallback,
NULL,
1
);

if (NT_SUCCESS(status)) {
FltStartFiltering(g_FilterHandle);
} else {
FltUnregisterFilter(g_FilterHandle);
}

// 向用户模式发送测试消息
SendMessageToUserMode("Hello from Kernel");

return status;
}

// 驱动卸载
VOID
DriverUnload(
PDRIVER_OBJECT DriverObject
) {
if (g_ServerPort) {
FltCloseCommunicationPort(g_ServerPort);
}
FltUnregisterFilter(g_FilterHandle);
}

FltSendMessage是一个同步的API

FltSendMessage 发送消息给用户模式进程时,用户模式进程需要通过 FilterGetMessage 来接收这个消息并进行处理。如果用户模式进程没有及时接收到消息或没有回复(例如,用户模式进程被挂起、退出、或处理速度较慢),FltSendMessage 就会等待用户模式的响应,直到超时设定的时间到达。

同步:不拿到数据不返回
同步也分阻塞和非阻塞,阻塞就是进入休眠,直到被唤醒,非阻塞就是轮询

scanner分析

微软的官方示例MInifilter-Scanner 是实现了一个文件扫描器的功能,实时监控文件并检测文件中是否含有预设特征。

一共三次扫描,具体分别是在 创建后扫描,写之前扫描,写关闭扫描

驱动代码分析

函数作用预览

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
//获取IoOpenDriverRegistryKey这个API的地址
PFN_IoOpenDriverRegistryKey
ScannerGetIoOpenDriverRegistryKey (
VOID
);



//功能:打开服务的注册表参数键。
//目的:根据传入的驱动对象和注册表路径打开服务参数子键。首先尝试使用 IoOpenDriverRegistryKey API 打开参数键。如果失败,使用 ZwOpenKey 直接打开指定路径的注册表键。(这里是Scanner把要扫描的后缀填到了注册表里面了)
NTSTATUS
ScannerOpenServiceParametersKey (
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING ServiceRegistryPath,
_Out_ PHANDLE ServiceParametersKey
);



/*
功能:从注册表读取文件扩展名并初始化文件扫描扩展名数组。
目的:此函数读取注册表中的 Extensions 键,解析它包含的文件扩展名,并将其存储在 ScannedExtensions 数组中。返回扫描扩展名数组的初始化状态。
*/
NTSTATUS
ScannerInitializeScannedExtensions(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
);


/*
功能:释放扫描扩展名数组。
目的:清理并释放在 ScannerInitializeScannedExtensions 中分配的内存,确保没有内存泄漏。
*/
VOID
ScannerFreeExtensions(
);



/*
功能:为 UNICODE_STRING 分配内存。
目的:为给定的 UNICODE_STRING 分配内存,并初始化其内容。如果分配失败,则返回错误代码。
*/
NTSTATUS
ScannerAllocateUnicodeString (
_Inout_ PUNICODE_STRING String
);



/*
功能:释放 UNICODE_STRING 分配的内存。
目的:释放通过 ScannerAllocateUnicodeString 分配的内存,确保没有内存泄漏。
*/
VOID
ScannerFreeUnicodeString (
_Inout_ PUNICODE_STRING String
);


/*
功能:处理用户模式连接到服务器端口的请求。
目的:当用户模式应用程序连接到驱动的服务器端口时,记录客户端端口和进程信息。还可以进行一些调试输出。
*/
NTSTATUS
ScannerPortConnect (
_In_ PFLT_PORT ClientPort,
_In_opt_ PVOID ServerPortCookie,
_In_reads_bytes_opt_(SizeOfContext) PVOID ConnectionContext,
_In_ ULONG SizeOfContext,
_Outptr_result_maybenull_ PVOID *ConnectionCookie
);



/*
功能:处理用户模式断开连接的请求。
目的:当连接断开时,关闭客户端端口,并重置与用户进程相关的信息。
*/
VOID
ScannerPortDisconnect (
_In_opt_ PVOID ConnectionCookie
);



/*
ScannerpScanFileInUserMode 函数的主要作用是将文件的内容传输到用户模式并请求用户模式扫描该文件,确定文件是否安全可打开(例如是否包含恶意内容或不当语言)。如果扫描成功且文件安全,它会将 SafeToOpen 设置为 FALSE,表示文件可以安全打开,否则它将保留为 TRUE。
*/
NTSTATUS
ScannerpScanFileInUserMode (
_In_ PFLT_INSTANCE Instance,
_In_ PFILE_OBJECT FileObject,
_Out_ PBOOLEAN SafeToOpen
);



/*
ScannerpCheckExtension 这个函数的作用是检查给定的文件扩展名是否在驱动程序关注的扩展名列表中。如果该扩展名在已配置的列表中,则返回 TRUE,表示这个文件类型是驱动程序感兴趣的文件;否则,返回 FALSE,表示不感兴趣。
*/
BOOLEAN
ScannerpCheckExtension (
_In_ PUNICODE_STRING Extension
);

具体分析

以下分析的都写在注释上

ScannerInitializeScannedExtensions函数

这里的参数_In_ PUNICODE_STRING RegistryPath是来自DriverEntry的RegisterPath,注册表路径一般默认就是HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\xxYourDriver

我们可以从inf文件中读取到注册表的内容

1731572063730

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
NTSTATUS
ScannerInitializeScannedExtensions(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{
NTSTATUS status;
HANDLE driverRegKey = NULL;
UNICODE_STRING valueName;
PKEY_VALUE_PARTIAL_INFORMATION valueBuffer = NULL;
ULONG valueLength = 0;
PWCHAR ch;
SIZE_T length;
ULONG count;
PUNICODE_STRING ext;

PAGED_CODE();//如果 IRQL >APC_LEVEL,PAGED_CODE 宏会导致系统出现 ASSERT。

ScannedExtensions = NULL;
ScannedExtensionCount = 0;

//
// Open service parameters key to query values from.
//

status = ScannerOpenServiceParametersKey( DriverObject,
RegistryPath,
&driverRegKey ); //这里是打开服务的注册表参数键。

if (!NT_SUCCESS( status )) {

driverRegKey = NULL;
goto ScannerInitializeScannedExtensionsCleanup;
}



RtlInitUnicodeString( &valueName, L"Extensions" );

status = ZwQueryValueKey( driverRegKey,
&valueName,
KeyValuePartialInformation,
NULL,
0,
&valueLength ); //获取到注册表"Extensions"的长度

if (status!=STATUS_BUFFER_TOO_SMALL && status!=STATUS_BUFFER_OVERFLOW) {

status = STATUS_INVALID_PARAMETER;
goto ScannerInitializeScannedExtensionsCleanup;
}




valueBuffer = ExAllocatePoolZero( NonPagedPool,
valueLength,
SCANNER_REG_TAG );

if (valueBuffer == NULL) {

status = STATUS_INSUFFICIENT_RESOURCES;
goto ScannerInitializeScannedExtensionsCleanup;
}

status = ZwQueryValueKey( driverRegKey,
&valueName,
KeyValuePartialInformation,
valueBuffer,
valueLength,
&valueLength ); //提取出注册表的项

if (!NT_SUCCESS( status )) {

goto ScannerInitializeScannedExtensionsCleanup;
}

ch = (PWCHAR)(valueBuffer->Data);

count = 0;


//计算有多少项
while (*ch != '\0') {

ch = ch + wcslen( ch ) + 1;
count++;
}

ScannedExtensions = ExAllocatePoolZero( PagedPool,
count * sizeof(UNICODE_STRING),
SCANNER_STRING_TAG );

if (ScannedExtensions == NULL) {
goto ScannerInitializeScannedExtensionsCleanup;
}

ch = (PWCHAR)((PKEY_VALUE_PARTIAL_INFORMATION)valueBuffer->Data);
ext = ScannedExtensions;

while (ScannedExtensionCount < count) { //将提取出来的项存到数组里,例如.doc,.exe等等后缀

length = wcslen( ch ) * sizeof(WCHAR);

ext->MaximumLength = (USHORT) length;

status = ScannerAllocateUnicodeString( ext );

if (!NT_SUCCESS( status )) {
goto ScannerInitializeScannedExtensionsCleanup;
}

ext->Length = (USHORT)length;

RtlCopyMemory( ext->Buffer, ch, length );

ch = ch + length/sizeof(WCHAR) + 1;

ScannedExtensionCount++;

ext++;

}

ScannerInitializeScannedExtensionsCleanup:

//
// Note that this function leaks the global buffers.
// On failure DriverEntry will clean up the globals
// so we don't have to do that here.
//

if (valueBuffer != NULL) {

ExFreePoolWithTag( valueBuffer, SCANNER_REG_TAG );
valueBuffer = NULL;
}

if (driverRegKey != NULL) {

ZwClose( driverRegKey );
}

if (!NT_SUCCESS( status )) {

ScannerFreeExtensions();
}

return status;
}

DriverEntry

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
NTSTATUS
DriverEntry (
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{
OBJECT_ATTRIBUTES oa;
UNICODE_STRING uniString;
PSECURITY_DESCRIPTOR sd;
NTSTATUS status;

ExInitializeDriverRuntime( DrvRtPoolNxOptIn ); //动态选择启用默认非分页池分配不可执行(Non-Executable, NX)

//注册minifilter,拿到minifilter的句柄(ScannerData.Filter)
status = FltRegisterFilter( DriverObject,
&FilterRegistration,//存着回调函数
&ScannerData.Filter );


if (!NT_SUCCESS( status )) {

return status;
}


status = ScannerInitializeScannedExtensions( DriverObject, RegistryPath );//从注册表中获取要扫描的扩展

if (!NT_SUCCESS( status )) {

status = STATUS_SUCCESS;

ScannedExtensions = &ScannedExtensionDefault;
ScannedExtensionCount = 1;
}

//初始化一个端口名
RtlInitUnicodeString( &uniString, ScannerPortName );



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,//客户端连接调用的回调
ScannerPortDisconnect,//客户端断开连接的回调
NULL,//这里是客户端发消息的回调,但是这个例子没注册
1 );


FltFreeSecurityDescriptor( sd ); //在所有情况下释放安全描述符。调用 FltCreateCommunicationPort() 后,就不需要它。

if (NT_SUCCESS( status )) {

status = FltStartFiltering( ScannerData.Filter );//开启minifilter

if (NT_SUCCESS( status )) {

return STATUS_SUCCESS;
}

FltCloseCommunicationPort( ScannerData.ServerPort );
}
}

ScannerFreeExtensions(); //释放扫描扩展名数组。

FltUnregisterFilter( ScannerData.Filter );

return status;
}

建立连接回调ScannerPortConnect

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
NTSTATUS
ScannerPortConnect (
_In_ PFLT_PORT ClientPort,
_In_opt_ PVOID ServerPortCookie,
_In_reads_bytes_opt_(SizeOfContext) PVOID ConnectionContext,
_In_ ULONG SizeOfContext,
_Outptr_result_maybenull_ PVOID *ConnectionCookie
)
{
PAGED_CODE();

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

//FLT_ASSERT是一个宏,用于在调试过程中检查条件是否为真。如果条件为假(即表达式计算结果为FALSE),则FLT_ASSERT会触发一个断言失败,这通常会导致调试器中断执行
FLT_ASSERT( ScannerData.ClientPort == NULL );
FLT_ASSERT( ScannerData.UserProcess == NULL );


//设置用户进程和端口。后续校验用
ScannerData.UserProcess = PsGetCurrentProcess();
ScannerData.ClientPort = ClientPort;

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

return STATUS_SUCCESS;
}

断开连接回调ScannerPortDisconnect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
VOID
ScannerPortDisconnect(
_In_opt_ PVOID ConnectionCookie
)
{
UNREFERENCED_PARAMETER( ConnectionCookie );

PAGED_CODE();

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

//关闭连接的句柄:注意,由于我们将最大连接数限制为1,所以在我们从断开例程返回之前,不允许进行另一个连接。
FltCloseClientPort( ScannerData.Filter, &ScannerData.ClientPort );


ScannerData.UserProcess = NULL;
}

接下来分析Minifilter里面具体注册的IRP回调

IRP_MJ_CREATE的Pre回调:

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
FLT_PREOP_CALLBACK_STATUS
ScannerPreCreate (
_Inout_ PFLT_CALLBACK_DATA Data,
_In_ PCFLT_RELATED_OBJECTS FltObjects,
_Flt_CompletionContext_Outptr_ PVOID *CompletionContext
)
{
UNREFERENCED_PARAMETER( FltObjects );
UNREFERENCED_PARAMETER( CompletionContext = NULL );

PAGED_CODE();

if (IoThreadToProcess( Data->Thread ) == ScannerData.UserProcess) //去对比当前发送Create的这个线程是否与我们注册的相关,我们信任与自己取得连接的三环程序
{

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

//这里也许可以多一些验证,比如md5,数字签名啥的,要保证与minifilter连接的是真正我们自己的程序


return FLT_PREOP_SUCCESS_NO_CALLBACK;//表示预操作处理成功,但不需要调用 `PostOperation` 回调,即信任自己的程序
}

return FLT_PREOP_SUCCESS_WITH_CALLBACK; //如果不是自己的进程,则启用Post
}

IRP_MJ_CREATE的Post回调:

前面提到过了,在IRP_MJ_CREATE的Post回调我们会开始我们的第一次扫描验证,让我们看看怎么个

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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
)
/*
例程说明:

Post create 回调。 在创建文件进入文件系统之前,我们无法扫描文件,否则文件系统将无法准备好为我们读取文件。

参数:

数据 - 描述操作参数的结构。

FltObject - 描述受此操作影响的对象的结构。

CompletionContext - 从 pre-create 回调传递的操作上下文。

Flags (标志) - 说明我们为何获得此操作后回调的标志。

返回值:

FLT_POSTOP_FINISHED_PROCESSING - 确定以打开文件,或者我们希望拒绝对此文件的访问,因此撤消打开的

--*/

{
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 );//FltParseFileNameInformation 函数在Windows文件系统过滤驱动开发中用于解析文件名信息。


//检查扩展是否与我们感兴趣的扩展列表匹配
scanFile = ScannerpCheckExtension( &nameInfo->Extension );


//已经不用nameInfo,可以删
FltReleaseFileNameInformation( nameInfo );

if (!scanFile) {

//我们不感兴趣就返回
return FLT_POSTOP_FINISHED_PROCESSING;
}

(VOID) ScannerpScanFileInUserMode( FltObjects->Instance,
FltObjects->FileObject,
&safeToOpen ); //这个函数是将文件发送至三环进行扫描,结果存放在safeToOpen

if (!safeToOpen) {

//如果不安全做以下事情:

DbgPrint( "!!! scanner.sys -- foul language detected in postcreate !!!\n" );

DbgPrint( "!!! scanner.sys -- undoing create \n" );

FltCancelFileOpen( FltObjects->Instance, FltObjects->FileObject );//微筛选器驱动程序可以使用 FltCancelFileOpen 例程关闭新打开或创建的文件。

Data->IoStatus.Status = STATUS_ACCESS_DENIED;
Data->IoStatus.Information = 0;

returnStatus = FLT_POSTOP_FINISHED_PROCESSING;

}
else if (FltObjects->FileObject->WriteAccess) //如果是写打开,要创建标志,方便在写关闭的时候再次扫描
{

//如果是这一步扫描出来是安全的,如果是以Write的权限打开,我们做好标记,在写入的时候我们还会再次扫描检测
status = FltAllocateContext( ScannerData.Filter,
FLT_STREAMHANDLE_CONTEXT, //绑定在文件对象上的上下文
sizeof(SCANNER_STREAM_HANDLE_CONTEXT),
PagedPool,
&scannerContext );//引用计数初始化为 1

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

FltReleaseContext( scannerContext );
}
}

return returnStatus;
}

将扫描数据发送给三环的函数分析

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
NTSTATUS
ScannerpScanFileInUserMode (
_In_ PFLT_INSTANCE Instance,
_In_ PFILE_OBJECT FileObject,
_Out_ PBOOLEAN SafeToOpen
)
/*
描述:

该例程用于向用户模式发送请求,扫描指定的文件,并告诉调用者是否可以打开该文件。

需要注意的是,如果扫描失败,我们将SafeToOpen设置为TRUE。扫描可能失败,可能是因为服务尚未启动,也可能是因为这是针对目录的创建/清理操作,而没有数据可供读取和扫描。

如果服务未运行时我们尝试创建它,就会出现启动问题——我们该如何加载服务的可执行文件呢?

论点:

实例 - 本卷扫描器的过滤器实例的句柄。

文件对象 - 待扫描的文件。

“SafeToOpen”设置为“FALSE”,如果文件扫描成功,并且其中包含粗俗语言。

返回值:

操作的状态,希望是STATUS_SUCCESS。 常见的失败状态可能是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;

//如果port是空,说明还没有进行连接
if (ScannerData.ClientPort == NULL) {

return STATUS_SUCCESS;
}

try {


status = FltGetVolumeFromInstance( Instance, &volume ); //获取卷对象

if (!NT_SUCCESS( status )) {

leave;
}


//确定扇区大小。非缓存I/O只能在扇区大小偏移量处完成,并且长度是扇区大小的倍数。一种更有效的方法是调用一次,并在实例设置例程中记住扇区大小,并设置一个实例上下文,我们可以缓存它。
//如果你尝试进行非缓存 I/O 读取,并且文件的读取偏移量或者读取的长度不是扇区大小的倍数,操作系统可能会返回错误或者无法成功执行该操作。
status = FltGetVolumeProperties( volume,
&volumeProps,
sizeof( volumeProps ),
&length );


if (NT_ERROR( status )) {

leave;
}

length = max( SCANNER_READ_BUFFER_SIZE, volumeProps.SectorSize );//如果扫描缓冲区的大小 SCANNER_READ_BUFFER_SIZE 小于扇区大小,你就需要使用扇区大小来进行读取;如果缓冲区较大,就使用缓冲区的大小


buffer = FltAllocatePoolAlignedWithTag( Instance,
NonPagedPool,
length,
'nacS' );

if (NULL == buffer) {

status = STATUS_INSUFFICIENT_RESOURCES;
leave;
}

notification = ExAllocatePoolZero( NonPagedPool,
sizeof( SCANNER_NOTIFICATION ),
'nacS' );

if(NULL == notification) {

status = STATUS_INSUFFICIENT_RESOURCES;
leave;
}


//读取文件的开头,并传递给三环
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;


//这里只拷贝min( notification->BytesToScan, SCANNER_READ_BUFFER_SIZE )
RtlCopyMemory( &notification->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 );//如果填写了期望收到恢复(reply),最后一个参数NULL代表无限等待

if (STATUS_SUCCESS == status) {

*SafeToOpen = ((PSCANNER_REPLY) notification)->SafeToOpen;

} else {

//发送不了信息
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;
}

接下来是关于IRP_MJ_WRITE的pre,这将会是进行第二次安全扫描

之所以是Pre而不是Post,是因为如果是Post就代表已经写入了,所以我们需要在Pre,也就是写之前进行对写入数据检测

写拦截,假如我们去拦截可疑特征码:“foul”,但是有个局限性,如果攻击者分开写,依次写入’f’,’o’,’u’,’l’,这下去拦截写就不太好

所以我们要去拦截写关闭,在cleanup的时候进行拦截,对文件进行扫描,具体来说,在Create的时候,可以知道是读还是写,如果是写,那么在Cleanup就会去扫描

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
FLT_PREOP_CALLBACK_STATUS
ScannerPreWrite (
_Inout_ PFLT_CALLBACK_DATA Data,
_In_ PCFLT_RELATED_OBJECTS FltObjects,
_Flt_CompletionContext_Outptr_ PVOID *CompletionContext
)
{
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 (ScannerData.ClientPort == NULL) { //如果 ScannerData.ClientPort 为空,表示当前没有有效的客户端端口用于与用户模式进行通信。


return FLT_PREOP_SUCCESS_NO_CALLBACK;//表示预操作处理成功,但不需要调用 `PostOperation` 回调。
}


//使用 FltGetStreamHandleContext 获取与文件相关的上下文。如果获取失败,则不再关注该文件,返回 FLT_PREOP_SUCCESS_NO_CALLBACK。为什么拿不到上下文就返回呢?因为在CreatePost我们为我们感兴趣的设置了上下文(特定后缀)
status = FltGetStreamHandleContext( FltObjects->Instance,
FltObjects->FileObject,
&context );

if (!NT_SUCCESS( status )) {
return FLT_PREOP_SUCCESS_NO_CALLBACK;
}


try {
//如果写操作的长度不为 0,函数会进一步处理写入的数据。
if (Data->Iopb->Parameters.Write.Length != 0) {

//以下是区别各种IO的


//如果写操作使用了 MDL(Memory Descriptor List),则通MmGetSystemAddressForMdlSafe 获取 MDL 映射的系统地址,读取写入的内容。
//如果没有 MDL,则直接使用用户提供的缓冲区 WriteBuffer。
if (Data->Iopb->Parameters.Write.MdlAddress != NULL) {
//这里是DirectIO
buffer = MmGetSystemAddressForMdlSafe(
Data->Iopb->Parameters.Write.MdlAddress,
NormalPagePriority | MdlMappingNoExecute
);



if (buffer == NULL) {

Data->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
Data->IoStatus.Information = 0;
returnStatus = FLT_PREOP_COMPLETE;
leave;
}

}
else {
buffer = Data->Iopb->Parameters.Write.WriteBuffer;
}

//分配一个 SCANNER_NOTIFICATION 结构来存储待扫描的数据。然后通过 RtlCopyMemory 将缓冲区中的数据复制到通知结构中。
notification = ExAllocatePoolZero( NonPagedPool,
sizeof( SCANNER_NOTIFICATION ),
'nacS' );
if (notification == NULL) {

Data->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
Data->IoStatus.Information = 0;
returnStatus = FLT_PREOP_COMPLETE;
leave;
}

//min 这里的作用是确保 扫描的字节数 (BytesToScan) 不会超过 扫描缓冲区的大小 (SCANNER_READ_BUFFER_SIZE),也不会超过 写入数据的实际长度 (Data->Iopb->Parameters.Write.Length)。
notification->BytesToScan = min( Data->Iopb->Parameters.Write.Length, SCANNER_READ_BUFFER_SIZE );

try {

RtlCopyMemory( &notification->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.
//

replyLength = sizeof( SCANNER_REPLY );


//调用 FltSendMessage 将 notification 发送到用户模式(通过 ClientPort)。用户模式应用程序负责扫描缓冲区中的内容,返回一个 SCANNER_REPLY。
status = FltSendMessage( ScannerData.Filter,
&ScannerData.ClientPort,
notification,
sizeof( SCANNER_NOTIFICATION ),
notification,
&replyLength,
NULL );

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

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

最后,还有一次检查,就是在IRP_MJ_CLEANUP这个IRP请求

关于什么时候会发送IRP_MJ_CLEANUP:

  1. 文件句柄关闭: 用户程序调用 CloseHandle 或在文件描述符生命周期结束时,Windows 内核会生成一个 IRP_MJ_CLEANUP 请求。这通常发生在文件对象不再需要时,句柄关闭或文件流结束时。
  2. 关闭文件对象时的清理 , 当文件系统或文件对象的句柄引用计数降到零时,操作系统会调用驱动程序的 Cleanup 例程来清理资源。
  3. 关闭时的流清理:在文件 I/O 操作(如 ReadWrite)完成后,文件句柄被关闭时,IRP_MJ_CLEANUP 用于指示文件的清理。
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
41
42
43
44
45
46
47
48
49
50
51
52
53
FLT_PREOP_CALLBACK_STATUS
ScannerPreCleanup (
_Inout_ PFLT_CALLBACK_DATA Data,
_In_ PCFLT_RELATED_OBJECTS FltObjects,
_Flt_CompletionContext_Outptr_ PVOID *CompletionContext
)
/*++
程序描述:
预清理回调。如果这个文件是为了写访问而打开的,我们想要现在重新扫描。

参数:
Data-描述操作参数的结构。
FltObject -描述受此影响的对象的结构操作。
CompletionContext -输出参数,可以用来传递一个上下文从这个清理前回调到清理后回调。


返回值:
总是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 )) {

//如果标记过RescanRequired(ScannerPostCreate中标记)
if (context->RescanRequired) {

(VOID) ScannerpScanFileInUserMode( FltObjects->Instance,
FltObjects->FileObject,
&safe ); //把内容读出来,发给R3扫描

if (!safe) {

DbgPrint( "!!! scanner.sys -- foul language detected in precleanup !!!\n" );
}
}

FltReleaseContext( context );
}


return FLT_PREOP_SUCCESS_NO_CALLBACK;
}

用户层代码分析

scanuser.c 代码实现了一个用户模式程序,用于与过滤驱动进行通信并扫描文件内容。它通过与内核模式的过滤器进行交互来扫描数据,并将扫描结果返回给过滤器。

完整代码如下

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
#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 "scanuk.h"
#include "scanuser.h"
#include <dontuse.h>


//定义了默认的请求数、线程数和最大线程数。程序将根据命令行参数或默认值来确定线程数和每个线程处理的请求数。
#define SCANNER_DEFAULT_REQUEST_COUNT 5
#define SCANNER_DEFAULT_THREAD_COUNT 2
#define SCANNER_MAX_THREAD_COUNT 64



//这是扫描程序要查找的目标字符串(不合适的内容)。程序将在扫描的缓冲区中搜索这个字符串。
UCHAR FoulString[] = "foul";



/*
SCANNER_THREAD_CONTEXT 结构体存储了与扫描工作线程相关的两个重要句柄:
Port:与过滤驱动的通信端口句柄。
Completion:I/O 完成端口句柄,用于多线程 I/O 操作。
*/
typedef struct _SCANNER_THREAD_CONTEXT {

HANDLE Port;
HANDLE Completion;

} SCANNER_THREAD_CONTEXT, *PSCANNER_THREAD_CONTEXT;



//打印程序的使用说明,包括如何传递参数以指定每个线程处理的请求数量以及线程数。
VOID
Usage (
VOID
)
{

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


//ScanBuffer 函数接收一个缓冲区并扫描其中是否包含 FoulString。
BOOL
ScanBuffer (
_In_reads_bytes_(BufferSize) PUCHAR Buffer,
_In_ ULONG BufferSize
)
{
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" );
return TRUE;
}
}

return FALSE;
}



/*
ScannerWorker 是工作线程的主函数:
它从完成端口获取消息,表示需要扫描的缓冲区数据。
使用 ScanBuffer 函数扫描缓冲区,检查是否包含目标字符串 FoulString。
如果发现目标字符串,则设置 SafeToOpen 为 FALSE,表示文件不安全;否则为 TRUE。
然后,通过 FilterReplyMessage 将结果回复给过滤驱动。
*/
DWORD
ScannerWorker(
_In_ PSCANNER_THREAD_CONTEXT Context
)
{
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)



result = GetQueuedCompletionStatus( Context->Completion, &outSize, &key, &pOvlp, INFINITE );//GetQueuedCompletionStatus等待从完成端口中获取一个异步I/O操作的结果(这里是消息)。&pOvlp:指向消息的OVERLAPPED结构体。



//message指向实际的消息内容,minifliter把SCANNER_NOTIFICATION封装在SCANNER_MESSAGE里面了,需要提取出来
message = CONTAINING_RECORD( pOvlp, SCANNER_MESSAGE, Ovlp );

if (!result) {

//
// An error occured.
//

hr = HRESULT_FROM_WIN32( GetLastError() );
break;
}

printf( "Received message, size %Id\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 );
}
}

return hr;
}


int _cdecl
main (
_In_ int argc,
_In_reads_(argc) char *argv[]
)
{
DWORD requestCount = SCANNER_DEFAULT_REQUEST_COUNT;
DWORD threadCount = SCANNER_DEFAULT_THREAD_COUNT;
HANDLE threads[SCANNER_MAX_THREAD_COUNT] = { NULL };
SCANNER_THREAD_CONTEXT context;
HANDLE port, completion;
PSCANNER_MESSAGE messages;
DWORD threadId;
HRESULT hr;

//
// 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.
//
//创建一个 I/O 完成端口,用于管理 I/O 操作的完成通知。
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;

//
// Allocate messages.
//

messages = calloc(((size_t) threadCount) * requestCount, sizeof(SCANNER_MESSAGE));

if (messages == NULL) {

hr = ERROR_NOT_ENOUGH_MEMORY;
goto main_cleanup;
}

//
// Create specified number of threads.
//

for (DWORD 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 (DWORD j = 0; j < requestCount; j++) {

PSCANNER_MESSAGE msg = &(messages[i * requestCount + j]);

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 )) {
goto main_cleanup;
}
}
}

hr = S_OK;

main_cleanup:

for (INT i = 0; threads[i] != NULL; ++i) {
WaitForSingleObjectEx(threads[i], INFINITE, FALSE);
}

printf( "Scanner: All done. Result = 0x%08x\n", hr );

CloseHandle( port );
CloseHandle( completion );

free(messages);

return hr;
}

这里注意一下就是说:

驱动发送的数据是SCANNER_NOTIFICATION结构体

1
2
3
4
5
6
7
8
9
10
//调用 FltSendMessage 将 notification 发送到用户模式(通过 ClientPort)。用户模式应用程序负责扫描缓冲区中的内容,返回一个 SCANNER_REPLY。
status = FltSendMessage(
ScannerData.Filter,
&ScannerData.ClientPort,
notification,
sizeof( SCANNER_NOTIFICATION ),
notification,
&replyLength,
NULL
);

但是在三环是这样提取的 , message指向实际的消息内容,minifliter把SCANNER_NOTIFICATION封装在SCANNER_MESSAGE里面了,需要提取出来。

1
2
3
4
5
6
7
8
9
result = GetQueuedCompletionStatus(
Context->Completion,
&outSize,
&key,
&pOvlp,
INFINITE
);

message = CONTAINING_RECORD( pOvlp, SCANNER_MESSAGE, Ovlp );

注册表回调整体框架

在64位系统下,我们不用Hook去监控注册表了,我们选择用回调

利用CmRegisterCallbackEx我们可以注册监控注册表的回调例程,实现一系列Pre Post操作

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
41
42
43
44
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) {
CmRegisterCallbackEx(
MyRegCallback,
&uAltitude,
DriverObject,
NULL,
&g_RegCookie,
NULL
);
return STATUS_SUCCESS;
}

NTSTATUS MyRegCallback(
_In_ PVOID CallbackContext,
_In_opt PVOID Argument1, // REG_NOTIFY_CLASS
_In_opt PVOID Argument2 // KEY_INFORMATION
) {
switch ((REG_NOTIFY_CLASS)Argument1) {
case RegNtPreDeleteKey:
return HOOK_PreNtDeleteKey((PREG_DELETE_KEY_INFORMATION)Argument2);

case RegNtPreRenameKey:
return HOOK_PreNtRenameKey((PREG_RENAME_KEY_INFORMATION)Argument2);

case RegNtPreCreateKeyEx: // pre 操作
return HOOK_PreNtCreateKeyEx((PREG_CREATE_KEY_INFORMATION)Argument2);

case RegNtPostCreateKeyEx: // post 操作
return HOOK_PostNtCreateKeyEx((PREG_POST_OPERATION_INFORMATION)Argument2);

default:
return STATUS_SUCCESS;
}
}

NTSTATUS HOOK_PreNtDeleteKey(PREG_DELETE_KEY_INFORMATION Data) {
NTSTATUS status = STATUS_SUCCESS;
UNICODE_STRING keyName;
UNICODE_STRING uTarget;

// Custom logic here...

return status;
}

SandBox工作原理

路径的概念

沙盒根目录:\device\harddiskvolume1\sandbox
想操作路径:\device\harddiskvolume2\doc\hi.txt

当我们想要把一个应用程序放入沙盒中以后,如果我们想要操作\device\harddiskvolume2\doc\hi.txt,沙盒会自动将这个重定向,转变为操作\device\harddiskvolume1\sandbox\device\harddiskvolume2\doc\hi.txt,这样恶意程序就无法改动原来想要改动的目标,进而攻击失败

而且一旦中毒,我们也只需要删除掉 sandbox 这个文件夹即可,如果写得好,病毒是不会突破沙盒的

文件重定向

Windows的I/O管理器提供了一个方便的方法来重定向一个文件对象,通常会使用文件过滤驱动(在文件打开和文件创建的操作中)实现该方法,操作方法如下:

1.在IRP_MJ_CREATE的分发函数中,获得FILE_OBJECTFileName属性

2.用目标文件的完整路径替换掉原有的文件名字

3.设置IoStatus的status字段为STATUS_REPARSE,然后设置Information字段为IO_REPARSE

4.完成该IRP请求

5.返回STATUS_REPARSE

I/O管理器在接收到该返回以后,便会触发另一个文件打开操作,并发送一个IRP_MJ_CREATE的请 求

拦截IRP_MJ_CREATE示例代码,注意这是Minifilter

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
NTSTATUS SbRedirectFile(
IN PFLT_CALLBACK_DATA Data,
IN PCFLT_RELATED_OBJECTS FltObjects,
IN PUNICODE_STRING pUstrDstFileName
)
{
PFILE_OBJECT pFileObject;

pFileObject = Data->Iopb->TargetFileObject;
if (pFileObject == NULL)
return STATUS_INVALID_PARAMETER;

if (pFileObject->FileName.Length > 0 && pFileObject->FileName.Buffer != NULL)
{
ExFreePool(pFileObject->FileName.Buffer);
pFileObject->FileName.Buffer = NULL;
}

pFileObject->FileName = *pUstrDstFileName;
pFileObject->RelatedFileObject = NULL;

Data->IoStatus.Status = STATUS_REPARSE;
Data->IoStatus.Information = IO_REPARSE;

FltSetCallbackDataDirty(Data);

return STATUS_REPARSE;
}

主要逻辑

  1. 检查目标文件对象是否为空

    • Data->Iopb->TargetFileObject 获取目标文件对象。
    • 如果为空,则直接返回 STATUS_INVALID_PARAMETER
  2. 释放旧的文件名内存

    • 如果目标文件对象的 FileName 字段存在有效的内存缓冲区,则释放该内存并清空指针。
  3. 设置新的文件名

    • 将目标文件对象的 FileName 字段设置为新的文件名(pUstrDstFileName)。
    • RelatedFileObject 字段清空。
  4. 标记为重解析操作

    • 修改 IoStatus.StatusSTATUS_REPARSE,表示当前请求需要重新解析。
    • 设置 IoStatus.InformationIO_REPARSE,进一步说明是重解析请求。
  5. 通知 MiniFilter 处理的数据已被修改

    • 调用 FltSetCallbackDataDirty,告诉过滤管理器 Data 数据结构已经被修改,需要重新处理。否则修改是无效的

      来自微软官方文档:( 微筛选器驱动程序的预操作或操作后 回调例程可以修改回调数据的内容结构。 如果已更改,则它必须调用 FltSetCallbackDataDirty ,除非它已更改回调数据结构的 IoStatus 字段的内容。 )

  6. 返回重解析状态

    • 返回 STATUS_REPARSE,让系统重新解析文件路径。

CreatePre需要做的事

一.首先判断该进程是否需要SANDBOX

二.获取被沙盒的文件操作的文件名(通过PID,文件名等判断是否该进程需要在沙盒运行)在这里需要获取两个文件名,一个是沙盒内部的,一个是沙盒外部的

三.判断沙盒里是否含有该文件的.del文件,如果有,则按照是否来自沙盒内的请求和是否改变文件进行处理

分以下情形:

1.如果说是要在沙箱外面操作创建或改变文件,那么就重定向到内部

2.如果说是要在沙箱里面操作创建或改变文件,那么就删除该标志文件,交给文件系统去创建

3.只读操作该文件,含有删除标志,访问失败

四:如果沙盒里面存在该文件,且请求来自沙盒,就交给文件系统进行处理。请求来自外面,重解析到里面

五:如果操作不改变文件,但是沙盒里面没有这个文件,并且请求来自沙盒,则重定向到外面,让它访问也无妨;如果沙盒有这个文件,那就交给文件系统去处理,我们不必处理

六:如果操作改变文件,且沙盒里面没有这个文件,那么就准备路径,如果沙盒外面有这个文件,就拷贝进来

DeleteFile需要做的事

删除文件时会发送 IRP_MJ_SET_INFORMATION 这个 IRP。

所以我们要监控这个IRP

一:首先要获取一下文件的路径

二:判断是来自沙盒的外面还是里面

三:如果是外面的路径,就转为里面的路径,也就是不删除外面的文件,而是在里面设置一个删除的标志

四:如果是里面的路径,就先获取外面的路径,
如果外面这个路径不存在,就直接删除里面的文件,
如果外面存在,就在里面建立一个删除的标志,并删除。

QueryDir需要做的事

查询文件夹发起的IRP请求是:IRP_MJ_DIRECTORY_CONTROL

为了确保删除掉的文件不会再被恶意程序检测到,我们要做的是

一:首先确定是否需要沙盒

二:获取文件名(沙盒内和沙盒外都要)

三:在沙箱中去掉含删除标志的文件,在外面的路径去掉含删除文件的标志,然后合并这两个部分

关于IRP_MJ_WRITE是否要重定向

其实是不需要重定向的,因为前面在CreatePre的时候已经重定向过了

对示例SandBox分析

分析项目: minifilter/Sandbox at master · haidragon/minifilter

分析写在注释上

首先是入口函数DriverEntry

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
//驱动程序入口,完成各种初始化工作,创建设备对象
NTSTATUS DriverEntry (
IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING pRegistryPath)
{
NTSTATUS ntStatus;
PDEVICE_OBJECT pDevObj;
UNICODE_STRING uDevName;
UNICODE_STRING uLinkName;
PROCESS_LIST_ENTRY *pPROCESS_LIST_ENTRYEntry;
DbgPrint("Driver Load begin!\n");

ExInitializeFastMutex(&g_PROCESS_LIST_ENTRYListLock);
InitializeListHead(&g_PROCESS_LIST_ENTRYList);

pPROCESS_LIST_ENTRYEntry = ExAllocatePoolWithTag(NonPagedPool, sizeof(PROCESS_LIST_ENTRY), 'XBBS');
if (pPROCESS_LIST_ENTRYEntry == NULL)
{
return STATUS_INSUFFICIENT_RESOURCES;
}

RtlZeroMemory(pPROCESS_LIST_ENTRYEntry, sizeof(PROCESS_LIST_ENTRY));

wcscpy(pPROCESS_LIST_ENTRYEntry->NameBuffer, L"notepad.exe");
InsertHeadList(&g_PROCESS_LIST_ENTRYList, &pPROCESS_LIST_ENTRYEntry->Entry);


pPROCESS_LIST_ENTRYEntry = ExAllocatePoolWithTag(NonPagedPool, sizeof(PROCESS_LIST_ENTRY), 'XBBS');
if (pPROCESS_LIST_ENTRYEntry == NULL)
{
return STATUS_INSUFFICIENT_RESOURCES;
}

RtlZeroMemory(pPROCESS_LIST_ENTRYEntry, sizeof(PROCESS_LIST_ENTRY));

wcscpy(pPROCESS_LIST_ENTRYEntry->NameBuffer, L"iexplore.exe");
InsertHeadList(&g_PROCESS_LIST_ENTRYList, &pPROCESS_LIST_ENTRYEntry->Entry);


ntStatus = initFileMonitor(pDriverObject);//注册过滤器

if(! NT_SUCCESS(ntStatus))
return ntStatus;

ntStatus = initIPC( );//建立minifilter内核和用户通讯

if(! NT_SUCCESS(ntStatus))
{
stopMiniMonitor( );
return ntStatus;
}

//Sandbox一些赋值操作
InitSb();

ntStatus = startMiniMonitor( );////启动过滤器

if(! NT_SUCCESS(ntStatus))
{
stopMiniMonitor( );
closeIPC( );
return STATUS_SUCCESS;
}

//初始化各个例程
pDriverObject->MajorFunction[IRP_MJ_CREATE] =
DispatchCreate;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] =
DispatchClose;
pDriverObject->MajorFunction[IRP_MJ_WRITE] =
DispatchWrite;
pDriverObject->MajorFunction[IRP_MJ_READ] =
DispatchRead;
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] =
DispatchControl;
pDriverObject->DriverUnload =
DriverUnload;

RtlInitUnicodeString(&uDevName, DEVICE_NAME);
//创建驱动设备
ntStatus = IoCreateDevice( pDriverObject,
0,//sizeof(DEVICE_EXTENSION)
&uDevName,
FILE_DEVICE_SANDBOX,
0, TRUE,
&pDevObj );
if (!NT_SUCCESS(ntStatus))
{
DbgPrint("IoCreateDevice Failed:%x\n", ntStatus);
return ntStatus;
}

pDevObj->Flags |= DO_BUFFERED_IO;
RtlInitUnicodeString(&uLinkName, LINK_NAME);
//创建符号链接
ntStatus = IoCreateSymbolicLink( &uLinkName,
&uDevName );
if (!NT_SUCCESS(ntStatus))
{
//STATUS_INSUFFICIENT_RESOURCES 资源不足
//STATUS_OBJECT_NAME_EXISTS 指定对象名存在
//STATUS_OBJECT_NAME_COLLISION 对象名有冲突
DbgPrint("IoCreateSymbolicLink Failed:%x\n", ntStatus);
IoDeleteDevice( pDevObj );
return ntStatus;
}
DbgPrint("Driver Load success!\n");
return ntStatus;
}

DriverEntry主要就是注册了Minifilter

然后是minifilter的连接,断连,通讯三个回调,但是由于过于简单,这里就不分析了

CreatePre

接下来就是重头戏,也就是CreatePre的分析,写在注释上了

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
//功能: 根据沙箱名称和注册表标志,获取沙箱路径。
NTSTATUS sbPreCreateFile(
IN OUT PFLT_CALLBACK_DATA Data,
IN PCFLT_RELATED_OBJECTS FltObjects
)
{

NTSTATUS ntStatus = STATUS_SUCCESS;
ULONG ulDisposition = 0;
BOOLEAN bNeedFree = FALSE;
BOOLEAN bDir = FALSE;
BOOLEAN bIsRename = FALSE;
BOOLEAN bIsHardLink = TRUE;
BOOLEAN bCreateFile = FALSE;
PWCHAR pFileName = NULL;
PEPROCESS pEprocess = PsGetCurrentProcess();
PACCESS_STATE accessState;
PFILE_OBJECT OutFileObject = NULL;
PFLT_INSTANCE pOutVolumeInstance = NULL;
UNICODE_STRING ustrDstFile = { 0 };
UNICODE_STRING ustrSrcFile = {0};
UNICODE_STRING ustrDeledName = {0};
PUNICODE_STRING pInName = NULL;
PUNICODE_STRING pOutName = NULL;
BOOLEAN bReparsed = FALSE;
BOOLEAN bReqInSandbox = FALSE;
ACCESS_MASK AccessMask = 0;
PFLT_FILE_NAME_INFORMATION pNameInfo = NULL;

Data->IoStatus.Status = STATUS_UNSUCCESSFUL;
accessState = Data->Iopb->Parameters.Create.SecurityContext->AccessState;

__try
{
if((ExGetPreviousMode() == KernelMode) ||
(KeGetCurrentIrql() > APC_LEVEL) ||
(pEprocess == NULL) ||
(pEprocess == g_pProcessObject))
{

return STATUS_SUCCESS;
}




if (!SbShouldBeSandBoxed(PsGetProcessId(pEprocess)))//看是否应该在沙盒里面进行处理,如果不需要,直接返回,不需要下面的步骤,我们只关心特定进程
{
return STATUS_SUCCESS;
}

if(g_SbVolInstance == NULL)
{
g_SbVolInstance = SbGetVolumeInstance(gp_Filter, &g_ustrVolumeDeviceName); //获取卷设备对象

if(g_SbVolInstance == NULL)
{

ntStatus = STATUS_SUCCESS;
__leave;
}
}

if(FltObjects->FileObject->Flags & FO_VOLUME_OPEN)
{
ntStatus = STATUS_SUCCESS;
__leave;
}


//AccessMask 是一个权限掩码,用于表示对文件或对象的访问权限需求。
AccessMask = Data->Iopb->Parameters.Create.SecurityContext->DesiredAccess;

bIsRename = (AccessMask == (SYNCHRONIZE | FILE_READ_ATTRIBUTES | DELETE));
bIsHardLink = (AccessMask == (SYNCHRONIZE | FILE_WRITE_DATA));


//--------------------------------------------------------------------------------------------

//获取操作文件的具体信息
if(!bIsRename && !bIsHardLink)
{
ntStatus = FltGetFileNameInformation(Data,
FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT,
&pNameInfo);

if(NT_SUCCESS(ntStatus))
{
FltParseFileNameInformation(pNameInfo);
}
}

if ( bIsRename || bIsHardLink || !NT_SUCCESS(ntStatus ))
{
ntStatus = SbGetFileNameInformation(FltObjects->Volume,
FltObjects->Instance,
FltObjects->FileObject,
FALSE,
&pNameInfo);
if (NT_SUCCESS(ntStatus))
{
bNeedFree = TRUE;
}
else
{
Data->IoStatus.Status = ntStatus;
Data->IoStatus.Information = 0;
__leave;
}
}
//----------------------------------------------------------------------------------------------------------




/*pNameInfo->Name 是文件对象的完整路径名,例如 \Device\HarddiskVolume1\test.txt。
pNameInfo->Volume 是文件所在卷的设备路径,例如 \Device\HarddiskVolume1
如果请求目标是整个卷,意味着这是一个对卷级别的操作(例如挂载、卸载、卷属性查询等)。
对于这种请求,文件过滤器通常不需要干预,因为它们不涉及具体的文件操作。*/
if(!RtlCompareUnicodeString(&pNameInfo->Name, &pNameInfo->Volume, TRUE))
{
ntStatus = STATUS_SUCCESS;
__leave;
}


//提取后缀,如果后缀是.rem,说明是想创建或者操作这个已经被删除的文件,就返回找不到句柄,直接退出try块
if(pNameInfo->Name.Length >= sizeof(WCHAR)*DEL_LENGTH &&
!_wcsnicmp(pNameInfo->Name.Buffer+pNameInfo->Name.Length/sizeof(WCHAR)-DEL_LENGTH,
DEL_MARK, DEL_LENGTH))
{
Data->IoStatus.Status = STATUS_OBJECT_NAME_NOT_FOUND;
Data->IoStatus.Information = 0;
ntStatus = STATUS_OBJECT_NAME_NOT_FOUND;
__leave;
}


//-------------------------------------------------------------------------------------------------------------------------------
//获取沙盒内,沙盒外各自的路径
if(!RtlPrefixUnicodeString(&g_SandboxPath, &pNameInfo->Name, TRUE)) //RtlPrefixUnicodeString比较两个 Unicode 字符串,以确定一个字符串是否为另一个字符串的前缀。
{
//进入if代表是来自外面的请求
ntStatus = SbConvertToSbName(&g_SandboxPath, &pNameInfo->Name, &ustrDstFile, NULL);//转换为内部请求
if(!NT_SUCCESS(ntStatus))
{
Data->IoStatus.Status = ntStatus;
Data->IoStatus.Information = 0;
__leave;
}

pInName = &ustrDstFile;
pOutName = &pNameInfo->Name;
pOutVolumeInstance = FltObjects->Instance;
}
else
{
//来自内部请求,转化一下,获得外部路径
UNICODE_STRING ustrVolName = {0};

ustrVolName.Buffer = (PWCHAR)MyNew(BYTE, sizeof(WCHAR)*MAX_PATH);

if(ustrVolName.Buffer == NULL)
{
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
Data->IoStatus.Status = ntStatus;
Data->IoStatus.Information = 0;
__leave;
}

ustrVolName.MaximumLength = sizeof(WCHAR)*MAX_PATH;

//将沙盒的内部路径转为外部路径
ntStatus = SbConvertInSbNameToOutName(gp_Filter,
&pNameInfo->Name,
&g_SandboxPath,
&ustrSrcFile,
&ustrVolName);
if(!NT_SUCCESS(ntStatus))
{
MyDelete(ustrVolName.Buffer);
Data->IoStatus.Status = ntStatus;
Data->IoStatus.Information = 0;
__leave;
}

ustrDstFile.Buffer = MyNew(WCHAR, pNameInfo->Name.Length/sizeof(WCHAR));
if(ustrDstFile.Buffer == NULL)
{
MyDelete(ustrVolName.Buffer);
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
Data->IoStatus.Status = ntStatus;
Data->IoStatus.Information = 0;
__leave;
}

ustrDstFile.Length = 0;
ustrDstFile.MaximumLength = pNameInfo->Name.Length;

RtlCopyUnicodeString(&ustrDstFile, &pNameInfo->Name);
pInName = &ustrDstFile;//存沙箱内路径
pOutName = &ustrSrcFile;//存沙箱外路径
pOutVolumeInstance = SbGetVolumeInstance(gp_Filter, &ustrVolName);//获取卷实例
MyDelete(ustrVolName.Buffer);
if (pOutVolumeInstance == NULL)
{
ntStatus = STATUS_UNSUCCESSFUL;
Data->IoStatus.Status = ntStatus;
Data->IoStatus.Information = 0;
__leave;
}
bReqInSandbox = TRUE;
}
//-------------------------------------------------------------------------------------------------------------------------------
//这里是判断这个IRP_MJ_CREATE想要干啥,是创建还是删除等等,顺便把如果删除的话,文件名后缀加一个.rem
ustrDeledName.MaximumLength = pInName->Length + DEL_LENGTH*sizeof(WCHAR);
ustrDeledName.Buffer = MyNew(WCHAR, ustrDeledName.MaximumLength/sizeof(WCHAR));
if(ustrDeledName.Buffer == NULL)
{
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
Data->IoStatus.Status = ntStatus;
Data->IoStatus.Information = 0;
__leave;
}

ulDisposition = (Data->Iopb->Parameters.Create.Options >> 24) & 0x000000ff;
bCreateFile = (BOOLEAN)((ulDisposition == FILE_CREATE) ||
(ulDisposition == FILE_OPEN_IF) ||
(ulDisposition == FILE_OVERWRITE_IF) ||
(ulDisposition == FILE_SUPERSEDE));

RtlCopyUnicodeString(&ustrDeledName, pInName);
RtlAppendUnicodeToString(&ustrDeledName, DEL_MARK);

//-----------------------------------------------------------------------------------------------------
if(SbFileExist(gp_Filter, g_SbVolInstance, &ustrDeledName))//判断后缀是.rem的文件是否存在
{
if(bCreateFile ||SbOperWillModifyFile(AccessMask)) //如果是创建或者要改变该文件
{
if(!SbFileExist(gp_Filter,g_SbVolInstance, pInName)) //内部不存在该文件
{

//如果内部没有这个文件,那么造出一个路径出来
ntStatus = SbPrepareSandboxPath(
gp_Filter,
g_SbVolInstance,
&g_SandboxPath,
pInName,
AccessMask);

if(! NT_SUCCESS(ntStatus))
{
Data->IoStatus.Status = ntStatus;
Data->IoStatus.Information = 0;

__leave;
}
if (! bReqInSandbox)
{
//来自外面请求,重定向创建该文件
ntStatus = SbRedirectFile(Data,
FltObjects,
pInName);
if(NT_SUCCESS(ntStatus))
{
ntStatus = STATUS_SB_TRY_REPARSE;
__leave;
}
}
else
{
if(!SBDeleteOneFile(g_SbVolInstance, gp_Filter, NULL, &ustrDeledName)) //来自内部,删除.rem的标志,相当于创建了
{
ntStatus = STATUS_UNSUCCESSFUL;
Data->IoStatus.Status = ntStatus;
Data->IoStatus.Information = 0;
__leave;
}

ntStatus = STATUS_SUCCESS; //让文件系统执行
__leave;
}
}
}
else //表明访问了一个打了删除标志的文件,而且不是创建或者修改,可能是只读取 或 查询文件属性 的操作
{

Data->IoStatus.Status = STATUS_OBJECT_NAME_NOT_FOUND;
Data->IoStatus.Information = 0;
ntStatus = STATUS_OBJECT_NAME_NOT_FOUND;
__leave;
}
}
else //如果文件不存在
{
if(bReqInSandbox) //来自于内部请求
{
if(!SbFileExist(FltObjects->Filter, pOutVolumeInstance, pOutName) )//外面不存在,直接交给文件系统创建
{
ntStatus = STATUS_SUCCESS;
__leave;
}
}

else
{
//如果来自外部请求,这里啥也没做,看来是默认交给I/O管理器了?
}
}


//里面文件存在,记住这回不是.rem文件了,是真正的文件
if(SbFileExist(gp_Filter, g_SbVolInstance, pInName) )
{
if (bReqInSandbox)//来自里面的请求,直接交给文件系统
{
ntStatus = STATUS_SUCCESS;
__leave;
}

//如果是来自外部请求,那么重定向文件
ntStatus = SbRedirectFile(Data,
FltObjects,
pInName);

if(NT_SUCCESS(ntStatus))
{
ntStatus = STATUS_SB_TRY_REPARSE;
}
else
{
Data->IoStatus.Status = ntStatus;
Data->IoStatus.Information = 0;
}


__leave;
}




//沙盒里面文件不存在情况的处理
if(!bCreateFile && !SbOperWillModifyFile(AccessMask))//如何理解“不创建文件且不修改文件”的情况?一种 只读取 或 查询文件属性 的操作。这类操作通常需要打开文件,但是不涉及对文件内容的修改。IRP_MJ_CREATE 请求在这种情况下是必要的,因为文件需要被打开(即使不进行修改)。
{
if (bReqInSandbox)//来自里面的请求,就重定向到外面,反正也不影响
{
ntStatus = SbRedirectFile(Data,
FltObjects,
pOutName);

if(NT_SUCCESS(ntStatus))
{
ntStatus = STATUS_SB_TRY_REPARSE;
}
else
{
Data->IoStatus.Status = ntStatus;
Data->IoStatus.Information = 0;
}

__leave;
}
else
{
//这里不用多写,直接交给I/O管理器即可
}
ntStatus = STATUS_SUCCESS;
__leave;
}


//里面文件不存在,需要修改和创建该文件
ntStatus = SbPrepareSandboxPath(
gp_Filter,
g_SbVolInstance,
&g_SandboxPath,
pInName,
Data->Iopb->Parameters.Create.SecurityContext->DesiredAccess);

if(! NT_SUCCESS(ntStatus))
{

Data->IoStatus.Status = ntStatus;
Data->IoStatus.Information = 0;

__leave;
}

if(bReqInSandbox || SbFileExist( FltObjects->Filter, pOutVolumeInstance, pOutName) )
{
//如果是来自内部请求,并且外部文件存在,则拷贝进来
ntStatus = SbIsDirectory(NULL, pOutName, FltObjects->Filter, pOutVolumeInstance, &bDir);
if(!NT_SUCCESS(ntStatus))
{
Data->IoStatus.Status = ntStatus;
Data->IoStatus.Information = 0;
__leave;
}

ntStatus = SbCopyFile(gp_Filter,
pOutVolumeInstance,
NULL,
pOutName,
g_SbVolInstance,
pInName,
bDir);
if(!NT_SUCCESS(ntStatus))
{
if(ntStatus != STATUS_SB_DIR_CREATED)
{
Data->IoStatus.Status = ntStatus;
Data->IoStatus.Information = 0;
__leave;
}
}
if(bReqInSandbox)
{
ntStatus = STATUS_SUCCESS;
__leave;
}

}

//重定向到里面
ntStatus = SbRedirectFile(Data,
FltObjects,
pInName);

if(NT_SUCCESS(ntStatus))
{
ntStatus = STATUS_SB_TRY_REPARSE;
}
else
{
Data->IoStatus.Status = ntStatus;
Data->IoStatus.Information = 0;
}


}
__except(EXCEPTION_EXECUTE_HANDLER)
{

}

//释放资源
if(pNameInfo != NULL)
{
if(bNeedFree)
MyDelete(pNameInfo);
else
FltReleaseFileNameInformation(pNameInfo);
}

if(ustrDeledName.Buffer != NULL)
MyDelete(ustrDeledName.Buffer);

if(ntStatus == STATUS_SB_TRY_REPARSE)
{
Data->IoStatus.Status = STATUS_REPARSE;
Data->IoStatus.Information = 0;
}

if(ustrDstFile.Buffer != NULL && ntStatus != STATUS_SB_TRY_REPARSE)
{
ExFreePool(ustrDstFile.Buffer);
ustrDstFile.Buffer = NULL;
}

if(OutFileObject)
ObDereferenceObject(OutFileObject);

return ntStatus;
}

WritePre

注释被我手贱删了….

下次有分析再说吧

无非就是分那几种情况

注意事项:

好像卸载再安装容易蓝屏…..

另外每个电脑的的盘符信息不一样,例如我的C盘是HardDiskVolume3,全局变量记得改一下