前言

前面总结了 sfilter 相关的内容,接下来就该轮到minifilter了。

minifilter基础

minifilter可以简单理解为微软自己开发了一个相当于sfilter的驱动,并提供了相关接口供其他驱动使用。

由于sfilter本身的复杂性极高,一个是在不同版本系统下的兼容性问题,一个是与核心需要的业务逻辑相比,无关的代码太多,故使用 minifilter是一个很好的选择,但是由于 minifilter本身也是基于传统的设备过滤驱动实现的,故在系统中存在其他 sfilter 的时候优先级会比别人低。

minifilter框架

DriverEntry

微型文件过滤驱动程序的DriverEntry例程必须按顺序执行以下步骤:

  1. 初始化所需的全局变量
  2. 通过调用FltRegisterFilter注册一个过滤器。
  3. 通过调用FltStartFiltering启动过滤器。
  4. 返回相应的 NTSTATUS 值。
  5. 在卸载驱动时调用 FltUnregisterFilter 卸载过滤器

相关函数及结构

NTSTATUS FLTAPI FltRegisterFilter(
  PDRIVER_OBJECT          Driver,
  const FLT_REGISTRATION *Registration,
  PFLT_FILTER            *RetFilter
);

共有三个参数,第一个参数传入本驱动对象的指针,第二个参数是一个 FLT_REGISTRATION 结构,其中便存在注册过滤例程的地方了,第三个参数是一个返回值,其作用相当于一个句柄。

NTSTATUS FLTAPI FltStartFiltering(
  PFLT_FILTER Filter
);

一个参数,即注册时返回的句柄。

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;

具体可以参考 MSDN 相关

其中最为重要的便是 OperationRegistration 成员。

typedef struct _FLT_OPERATION_REGISTRATION {
  UCHAR                            MajorFunction;
  FLT_OPERATION_REGISTRATION_FLAGS Flags;
  PFLT_PRE_OPERATION_CALLBACK      PreOperation;
  PFLT_POST_OPERATION_CALLBACK     PostOperation;
  PVOID                            Reserved1;
} FLT_OPERATION_REGISTRATION, *PFLT_OPERATION_REGISTRATION;

MajorFunction 主功能号,Flags 指定过滤哪种类型的请求,可以为0, Pre IRP完成前执行的回调例程,Post IRP完成后执行的完成例程,Reserved1 保留供系统使用。

例子:

const FLT_OPERATION_REGISTRATION
fileMonitorCallbacks[] =
{
    {
        IRP_MJ_CREATE,
        0
        PreNtCreateFile,
        PostNtCreateFile
    },
    {
        IRP_MJ_WRITE,
        FLTFL_OPERATION_REGISTRATION_SKIP_PAGING_IO,// 忽略分页读/写 请求
        PreNtWriteFile,
        PostNtWriteFile
    },
    {
        IRP_MJ_OPERATION_END//这个是必须要加的
    }
};

PFLT_PRE_OPERATION_CALLBACK FLT_POSTOP_CALLBACK_STATUS 原型

PFLT_PRE_OPERATION_CALLBACK PfltPreOperationCallback;

FLT_PREOP_CALLBACK_STATUS PfltPreOperationCallback(
  PFLT_CALLBACK_DATA Data,
  PCFLT_RELATED_OBJECTS FltObjects,
  PVOID *CompletionContext
)
{...}

PFLT_POST_OPERATION_CALLBACK PfltPostOperationCallback;

FLT_POSTOP_CALLBACK_STATUS PfltPostOperationCallback(
  PFLT_CALLBACK_DATA Data,
  PCFLT_RELATED_OBJECTS FltObjects,
  PVOID CompletionContext,
  FLT_POST_OPERATION_FLAGS Flags
)
{...}

其参数 FLT_CALLBACK_DATA 相当于对原来的 IRP 进行了包装,避免直接与IRP打交道。
另外可以通过其返回值轻易控制 IRP的状态,完成(FLT_PREOP_COMPLETE),挂起(FLT_PREOP_PENDING),传递(FLT_PREOP_SUCCESS_WITH_CALLBACK) 等等。

typedef struct _FLT_CALLBACK_DATA {
  FLT_CALLBACK_DATA_FLAGS     Flags;
  PETHREAD                    Thread;
  PFLT_IO_PARAMETER_BLOCK     Iopb;
  IO_STATUS_BLOCK             IoStatus;
  struct _FLT_TAG_DATA_BUFFER *TagData;
  union {
    struct {
      LIST_ENTRY QueueLinks;
      PVOID      QueueContext[2];
    };
    PVOID FilterContext[4];
  };
  KPROCESSOR_MODE             RequestorMode;
} FLT_CALLBACK_DATA, *PFLT_CALLBACK_DATA;

minifilter 本身也提供了一系列函数用于处理 FLT_CALLBACK_DATA 的相关信息。

例如可以通过 FltGetFileNameInformation 获取文件名信息,再通过 FltParseFileNameInformation 拆分为更加易用的格式,使用完毕之后再通过 FltReleaseFileNameInformation 回收相关资源。
又例如给文件对象绑定上下文信息等等操作。

通过设置 IoStatus 则可以控制请求的结果信息。
FLT_IO_PARAMETER_BLOCK 也存放着许多需要关注的信息,如要 得到读写的具体内容等就要从该结构入手。
由于篇幅限制,就不一一展开,在MSDN文档上有对其详细的描述。

minifilter之应用层通信

除了常规的设备通信方式外,minifilter内部封装了一套更为方便的通信机制。

主要函数如下:
FltBuildDefaultSecurityDescriptor 创建默认的安全描述符
FltCreateCommunicationPort 创建一个服务端口

    NTSTATUS status;
    PSECURITY_DESCRIPTOR sd;
    OBJECT_ATTRIBUTES oa;
    UNICODE_STRING uniString;

    status=FltBuildDefaultSecurityDescriptor(&sd,FLT_PORT_ALL_ACCESS);
    RtlInitUnicodeString( &uniString, MINISPY_PORT_NAME );

    //初始化对象属性
    InitializeObjectAttributes( &oa,
        &uniString,
        OBJ_KERNEL_HANDLE|OBJ_CASE_INSENSITIVE,
        NULL,
        sd );

    //内核建立通信端口
    status = FltCreateCommunicationPort(gFilterHandle,&gServerPort,&oa,NULL,MiniConnect,MiniDisconnect,MiniMessage,1);
    FltFreeSecurityDescriptor( sd );

其中 gFilterHandle 及注册minifilter返回的句柄,MiniConnect MiniDisconnect MiniMessage 为事件回调,在相应事件到达时触发。
相关函数
FltCloseClientPortMiniDisconnect 中关闭用户端口
FltSendMessage 向正在等待的用户层发送
FltCloseCommunicationPort 在驱动卸载时关闭监听端口

小拓展

Ps.刚刚在看相关博客时就发现一个他写了个bug,网上的代码真的不要随便用

实际上之前用内核管道做过测试,这种异步IO的方式本身效率不高,并不适合频繁传输,
对频繁传输,还是推荐使用 IO方式中的其他方式,在同一进程上下文时,直接操作进程,并通过共享事件的方式做互相通知更为高效。

有人肯定会说,别人都是说线程上下文,为什么你说的是进程上下文?
能否直接操作r3进程内存,看的是当前是进程上下文,一个进程中的多个线程的上下文都可以被直接操作,只是说判断某个线程是否属于某个进程要麻烦一丢丢,并非在某个特定的线程上下文才能访问进程空间。
例如调用 同步的DeviceIoControl 的线程会陷入内核调用派遣函数,那么派遣函数就和 DeviceIoControl 在同一线程上下文,也就在同一进程上下文,此时是可以直接操作进程空间的。
一般可以认为在异步中,其上下文是不可控的,这是由于实现异步的交付者是不可控的,你要说是某进程内部实现的协程也是可以的。这里主要指系统级的异步,极有可能会在其他进程的上下文交付。

minifilter 动态安装卸载

网上随便找了一篇,并未测试,按照他的说法是在注册表中创建相关子键和键值

SYSTEM\\CurrentControlSet\\Services\\DriverName\\Instances
SYSTEM\\CurrentControlSet\\Services\\DriverName\\Instances\\DriverName Instance

话说回来,网上大多数流传已久的驱动加载方式本来就有点问题,会出现重启都卸载不干净的情况,编码不规范造成的,这块是个细节。

总结

本文是官方文档和众多参考文章汇集而成,由于 minifilter 并不需要像 sfilter 那样悉知细节,故相较之要简单许多。