• 64位内核开发第十四讲,MiniFilter文件过滤框架以及安装方式


    MiniFilter文件过滤第一讲 文件过滤框架以及安装方式

    一丶MiniFilter 文件过滤框架

    1.1 简介

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

    MiniFilter的优点和不足如下:

    优点:

    1.增加开发速度

    2.不用关心IRP处理工作,这些交给 Filter Manager处理即可.

    不足:

    MiniFilter开发的时候虽然简单了但是隐藏了很多细节.比如设备对象等等.如果使用以前的方式进行开发 那么就如同 C语言内嵌汇编 对兼容性不好 也失去了MiniFilter的意义.

    1.2 MiniFilter框架

    框架如下:

    在IO管理器中我们的 MiniFilter会去进行注册. 如上图所示. 有 A B C三个.

    而MiniFilter中最重要的是 高度值(Altitude) 不光有高度值还有分组.

    比如A的分组就在 FSFilter Activity Monitor B在 FSFilter Anti-Virus 也就是反病毒层级. 高度越高越会被先执行.假设你拦截了文件访问你可以不发送给下一层. 这样 B C 就接受不到了. 所以这个高度值需要我们找微软申请.(但是不申请好像也能用.只要不影响即可)

    高度值 是从 20000 ~ 429999 的.而高度值又有分组. 所以高度值不能乱写.一般就是每个分组有个高度值范围.

    查询地址如下: 微筛选器驱动程序的加载顺序组和高度 - Windows drivers | Microsoft Docs

    二丶MiniFilter 编程框架

    2.1 简介

    对应到程序来说 MiniFilter是很简单的. 只需要三个内核API就可以使用MiniFilter了.

    而API中所需要的参数就是结构体. 所以我们搞清楚结构体中的参数就可以了. 其实就是往结构体里面填写东西即可.

    内核API如下:

    NTSTATUS
      FltRegisterFilter(
        IN PDRIVER_OBJECT  Driver,                  
        IN CONST FLT_REGISTRATION  *Registration,   
        OUT PFLT_FILTER  *RetFilter
        ); 
    
    NTSTATUS
      FltStartFiltering(
        IN PFLT_FILTER  Filter);
    
    VOID
      FltUnregisterFilter(
        IN PFLT_FILTER  Filter
        );  
    
    
    

    API就三个. 分为 注册 启动 卸载 其中启动和卸载都是一个参数.就是Filter句柄.此句柄是从FltRegisterFilter 第三个参数传出的. 所以主要学习的就是第一个.

    此函数有三个参数

    • 参数1 Driver 在DDK驱动中的 DriverEntry中的驱动对象.

    • 参数2 一个结构体 此结构体就是我们要了解的结构体.下面说.

    • 参数3 传出的句柄. 文件管理器的句柄. 注册成功后会传出句柄 给 启动 和卸载函数使用.

    2.2 FLT_REGISTRATION 结构体

    在我们的注册函数中有次结构体. 此结构体如下:

    typedef struct _FLT_REGISTRATION {
      USHORT  Size;                    @1 指向自身的大小sizeof(FLT_REGISTRATION). 
      USHORT  Version;                 版本 必须设置为FLT_REGISTRATION_VERSION
      FLT_REGISTRATION_FLAGS  Flags;   标志   @1
      CONST FLT_CONTEXT_REGISTRATION  *ContextRegistration; 上下文@2
      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;
    #if FLT_MGR_LONGHORN
      PFLT_TRANSACTION_NOTIFICATION_CALLBACK TransactionNotificationCallback;
      PFLT_NORMALIZE_NAME_COMPONENT_EX  NormalizeNameComponentExCallback;
    #endif // FLT_MGR_LONGHORN
    } FLT_REGISTRATION, *PFLT_REGISTRATION;
    

    含义如下:

    成员 含义 说明 是否重点 ⚪了解  √号 重点 ×极少或不使用
    Size 大小 指向自身的大小 sizeof(FLT_REGISTRATION)
    Version 版本 必须设置为 FLT_REGISTRATION_VERSION
    Flags 标志 两种设置,设置为NULL或者 FLTFL_REGISTRATION_DO_NOT_SUPPORT_SERVICE_STOP 设置为STOP的时候 MinniFilter停止服务的时候不会进行卸载不管你的卸载函数是否设置
    ContextRegistration 上下文 注册处理上下文的函数 如果注册了则结构体数组的最后一项必须设置为 FLT_CONTEXT_END
    OperationRegistration 回调函数集 重点中的重点,主要学习的就是这个域怎么设置. 是一个结构体数组可以设置我们感兴趣的回调. 最后一项设置为 IRP_MJ_OPERATION_END
    FilterUnloadCallback 卸载函数 卸载MiniFilter回调.如果flags = xx_STOP 那么不管你是否设置都不会卸载
    InstanceSetupCallback 卷实例加载回调 当一个卷加载的时候MiniFilter会为其生成一个实例并且绑定,比如移动硬盘接入的时候就会生成一个实例. 可以设置为NULL.
    InstanceQueryTeardownCallback 控制实例销毁函数 这个实例只会在手工解除绑定的时候会来.
    InstanceTeardownStartCallback 实例销毁函数 当调用的时候代表已经解除绑定,可以设置为NULL
    InstanceTeardownCompleteCallback 实例解绑定完成函数 当确定时调用解除绑定后的完成函数,可以设置为NULL.
    GenerateFileNameCallback 文件名字回调 生成文件名可以设置回调,可以设置为NULL.
    NormalizeNameComponentCallback 查询WDK ⚪×
    NormalizeContextCleanupCallback 查询WDK ⚪×
    TransactionNotificationCallback 查询WDK ⚪×
    NormalizeNameComponentExCallback 查询WDK ⚪×

    其实本质就是学习 回调函数集 他是一个对象数组.我们看下它的结构吧.

    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;
    
    • 参数1 指明的你想监控的IRP操作

    • 参数2 是个标志

    • 参数3 是你执行的监控回调 pre代表的意思是先前回调. 比如文件创建 还未创建之前调用你

    • 参数4 监控后回调. 文件创建完会调用的回调

    • 参数五 保留参数 给NULL即可.

    IRP可以监控很多 这个查询WDK文档即可.

    这里说一下 标志

    标志如下:

    标志 含义
    FLTFL_OPERATION_REGISTRATION_SKIP_CACHED_IO 使用此标志代表了不对缓存的IO处理进行 pre和post函数操作 适用于快速IO 因为所有快速IO已经缓存
    FLTFL_OPERATION_REGISTRATION_SKIP_PAGING_IO 指定不应该为分页操作的IO进行回调操作.对于不是基于IRP的io操作都会跳过.不会调用我们的函数

    看着比较蒙对吧. 那我说一下. 其实在我们写一个文件的时候并不是直接写入到磁盘中.

    而是先写到缓存中的. 缓存在写到内存中的.

    调用链大概如下:

    APP->IO->FSD->Cache->MM->IO->FSD->DISk  第一种
    APP->IO->FSD->DISk                      第二种
    

    第一种就是先写到缓存中,当满足1024个字节的时候再由MM发起IO请求.然后在通知文件系统最好写道磁盘中.

    第二种就是直接通过IO到文件系统,然后写入到磁盘中.

    如果频繁读写是影响效率的.所以对于第一种不是IRP发起的请求我们都可以忽略掉.

    所以这两个标志的意思就是差不多这个意思.

    2.3 pre回调 和post回调

    pre回调函数原型如下:

    typedef FLT_PREOP_CALLBACK_STATUS
      (*PFLT_PRE_OPERATION_CALLBACK) (
        __inout PFLT_CALLBACK_DATA Data,
        __in PCFLT_RELATED_OBJECTS FltObjects,
        __deref_out_opt PVOID *CompletionContext
        );
    

    post回调函数如下:

    typedef FLT_POSTOP_CALLBACK_STATUS
    (FLTAPI *PFLT_POST_OPERATION_CALLBACK) (
        __inout PFLT_CALLBACK_DATA Data,
        __in PCFLT_RELATED_OBJECTS FltObjects,
        __in_opt PVOID CompletionContext,
        __in FLT_POST_OPERATION_FLAGS Flags
        );
    

    2.3.1 pre返回值和post返回值

    首先说一下返回值

    pre返回值如下

    返回值 含义 是否是重点
    FLT_PREOP_SUCCESS_WITH_CALLBACK 完成回调的调用并且callbackData往下发,post中可以使用CallbackData
    FLT_PREOP_SUCCESS_NO_CALLBACK 完成回调,不带参数往下发.
    FLT_PREOP_PENDING 挂起
    FLT_PREOP_DISALLOW_FASTIO 禁用Fastio
    FLT_PREOP_COMPLETE 完成回调,不会往下发
    FLT_PREOP_SYNCHRONIZE 同步

    其实主要就是三个常用的就是 FLT_PREOP_SUCCESS_WITH_CALLBACKFLT_PREOP_COMPLETE

    POST回调

    返回值 含义 是否常用
    FLT_POSTOP_FINISHED_PROCESSING 完成,筛选器管理器将继续完成 I/O 操作的处理。
    FLT_POSTOP_MORE_PROCESSING_REQUIRED 微筛选器驱动程序已停止 I/O 操作的完成处理,但它不会将操作的控制权返回给筛选器管理器。
    FLT_POSTOP_DISALLOW_FSFILTER_IO 微筛选器驱动程序不允许快速 QueryOpen 操作,并强制该操作沿慢速路径向下。这样做会导致 I/O 管理器通过执行文件的打开/查询/关闭来为请求提供服务。微筛选器驱动程序应仅返回 QueryOpen 的此状态。

    2.3.2 PFLT_CALLBACK_DATA 数据获取

    此参数是参数1 是很重要的参数. 其结构如下

    typedef struct _FLT_CALLBACK_DATA {
      FLT_CALLBACK_DATA_FLAGS  Flags;
      PETHREAD CONST  Thread;
      PFLT_IO_PARAMETER_BLOCK CONST  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;
    

    其中记录了线程. 标志 iopb 以及 IoStatus

    线程可以判断是否是哪个进程的

    PFLT_CALLBACK_DATA Data;
    PEPROCESS processObject = 
    		Data->Thread ? IoThreadToProcess(Data->Thread) : PsGetCurrentProcess();
    

    其中Iopb域则记录了我们之前从IRP堆栈中要获取的Buffer值.

    Data->Iopb->Parameters.Create.SecurityContext->DesiredAccess
    
    PVOID	pQueryBuffer 	= 
    		Data->Iopb->Parameters.DirectoryControl.QueryDirectory.DirectoryBuffer;
    ULONG	uQueryBufferSize 	=  
    		Data->Iopb->Parameters.DirectoryControl.QueryDirectory.Length
    
    PMDL pReadMdl 		= Data->Iopb->Parameters.Read. MdlAddress;
    PVOID pReadBuffer 		= Data->Iopb->Parameters.Read. ReadBuffer;
    ULONG uReadLength 		= Data->Iopb->Parameters.Read.Length;
    
    

    IoStatus则记录了读写操作的长度值

    还可以判断Data是什么操作. 也有宏.

    FLT_IS_IRP_OPERATION
    FLT_IS_FASTIO_OPERATION
    FLT_IS_FS_FILTER_OPERATION
    例子
    if (FLT_IS_FASTIO_OPERATION(DATA))
    {
        status = STATUS_FLT_DISALLOW_FAST_IO;
        Data->IoStatus.Status = status;
        Data->IoStatus.information = 0;
        return FLT_PREOP_DISALLOW_FASTIO;
    }
    
    

    2.3.3 对象的获取 FltObjects

    它也是一个结构,记录了所有的你可以使用到的对象.

    typedef struct _FLT_RELATED_OBJECTS {
      USHORT CONST  Size;
      USHORT CONST  TransactionContext;
      PFLT_FILTER CONST  Filter;
      PFLT_VOLUME CONST  Volume;
      PFLT_INSTANCE CONST  Instance;
      PFILE_OBJECT CONST  FileObject;
      PKTRANSACTION CONST  Transaction;
    } FLT_RELATED_OBJECTS, *PFLT_RELATED_OBJECTS;
    typedef CONST struct _FLT_RELATED_OBJECTS *PCFLT_RELATED_OBJECTS;
    

    常用的就是如下:

    FltObjects->Volume,
    FltObjects->Instance,
    FltObjects->FileObject,
    FltObjects->FileObject->DeviceObject
    

    2.3.4 例子学习

    在WDK7600的Src目录下 有MiniFilter框架. 可以学习一下. 而如果使用VS高版本则需要自己选择模板进行生成.

    例子目录: x:\WinDDK\7600.16385.1\src\filesys\miniFilter

    • nullFilter

      一个基本的框架,没有过滤的用处.可以看看怎么写的.

    • passThrough

      一个完整的框架 所以过滤已经设置,但是并不使用.以它学习是最好的.

    • scanner

    一个拦截框架可以看看例子.

    三丶MiniFilter的使用

    MiniFilter需要进行安装方式有两种 inf安装方式和动态加载方式.

    3.1 Inf安装方式 以passThrough 此例子的Inf讲解

    inf了解即可.使用的时候拷贝一个inf即可.里面东西换成自己的就行

    Inf如下:

    ;;;
    ;;; PassThrough
    ;;;
    ;;;
    ;;; Copyright (c) 1999 - 2001, Microsoft Corporation
    ;;;
    
    [Version]
    Signature   = "$Windows NT$"     
    Class       = "ActivityMonitor"  ;指明了驱动的分组,必须指定.
    ClassGuid   = {b86dff51-a31e-4bac-b3cf-e8cfe75c9fc2}  ;GUID 每个分组都有固定的GUID
    Provider    = %Msft% ;变量值 从STRING节中可以看到驱动提供者的名称 
    DriverVer   = 06/16/2007,1.0.0.1 ;版本号
    CatalogFile = passthrough.cat    ;inf对应的cat 文件 可以不需要
    
    
    [DestinationDirs]
    DefaultDestDir          = 12    ;告诉我们驱动拷贝到哪里 13代表拷贝到%windir%
    MiniFilter.DriverFiles  = 12            ;%windir%\system32\drivers
    
    ;;
    ;; Default install sections
    ;;
    
    [DefaultInstall]
    OptionDesc          = %ServiceDescription%
    CopyFiles           = MiniFilter.DriverFiles
    
    [DefaultInstall.Services]
    AddService          = %ServiceName%,,MiniFilter.Service
    
    ;;
    ;; Default uninstall sections
    ;;
    
    [DefaultUninstall]
    DelFiles   = MiniFilter.DriverFiles
    
    [DefaultUninstall.Services]
    DelService = %ServiceName%,0x200      ;Ensure service is stopped before deleting
    
    ;
    ; Services Section
    ;
    
    [MiniFilter.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           = MiniFilter.AddRegistry       ;文件过滤注册表需要添加的高度值等信息
    
    ;
    ; Registry Modifications
    ;
    
    [MiniFilter.AddRegistry]
    HKR,,"DebugFlags",0x00010001 ,0x0
    HKR,"Instances","DefaultInstance",0x00000000,%DefaultInstance%
    HKR,"Instances\"%Instance1.Name%,"Altitude",0x00000000,%Instance1.Altitude%
    HKR,"Instances\"%Instance1.Name%,"Flags",0x00010001,%Instance1.Flags%
    
    ;
    ; Copy Files
    ;
    
    [MiniFilter.DriverFiles]
    %DriverName%.sys
    
    [SourceDisksFiles]
    passthrough.sys = 1,,
    
    [SourceDisksNames]
    1 = %DiskId1%,,,
    
    ;;
    ;; String Section
    ;;
    
    [Strings]
    Msft                    = "Microsoft Corporation"
    ServiceDescription      = "PassThrough Mini-Filter Driver"
    ServiceName             = "PassThrough"
    DriverName              = "PassThrough"
    DiskId1                 = "PassThrough Device Installation Disk"
    
    ;Instances specific information.
    DefaultInstance         = "PassThrough Instance"
    Instance1.Name          = "PassThrough Instance"
    Instance1.Altitude      = "370030"
    Instance1.Flags         = 0x0              ; Allow all attachments
    
    
    

    其实INF文件本质就是跟我们正常的DDK安装服务的方式一样.只不过他是在注册表中多加两个一个注册表键和实例.

    INF安装后首先会拷贝驱动到 C:\\windows\\System32\driver

    然后往注册表下面注册信息.

    如下:

    所以 如果我们动态加载驱动的时候需要多建立两个键 分别是 instances xxxinstance 然后添加高度值.

    在动态加载的时候 我们的依赖驱动要修改为 FltMgr 分组要写为 FSFilter Activity Monitor

    伪代码可使用的部分例子
    创建驱动服务如下:

        //创建驱动所对应的服务
        hService = CreateService( hServiceMgr,
            lpszDriverName,             // 驱动程序的在注册表中的名字
            lpszDriverName,             // 注册表驱动程序的DisplayName 值
            SERVICE_ALL_ACCESS,         // 加载驱动程序的访问权限
            SERVICE_FILE_SYSTEM_DRIVER, // 表示加载的服务是文件系统驱动程序
            SERVICE_DEMAND_START,       // 注册表驱动程序的Start 值       01234 五个选项 0 由系统核心进行加载 1 io子系统加载 2自动启动 3 手动启动 4禁止启动
            SERVICE_ERROR_IGNORE,       // 注册表驱动程序的ErrorControl 值
            szDriverImagePath,          // 注册表驱动程序的ImagePath 值
            "FSFilter Activity Monitor",// 注册表驱动程序的Group 值       如果是文件过滤驱动动态加载则需要指定这个分组
            NULL, 
            "FltMgr",                   // 注册表驱动程序的DependOnService 值  文件过滤驱动需要依赖FltMgr 需要在这里执行
            NULL, 
            NULL);
    

    其它比如启动服务 停止服务都是一样的.和DDK一样的.
    安装文件过滤驱动的时候还需要我们为其在注册表中创建两个键.并且还需要设置高度值.
    代码如下:

    strcpy(szTempStr,"SYSTEM\\CurrentControlSet\\Services\\");
        strcat(szTempStr,lpszDriverName);
        strcat(szTempStr,"\\Instances");
        if(RegCreateKeyEx(HKEY_LOCAL_MACHINE,szTempStr,0,"",REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,&hKey,(LPDWORD)&dwData)!=ERROR_SUCCESS)
        {
            return FALSE;
        }
        // 注册表驱动程序的DefaultInstance 值 
        strcpy(szTempStr,lpszDriverName);
        strcat(szTempStr," Instance");
        if(RegSetValueEx(hKey,"DefaultInstance",0,REG_SZ,(CONST BYTE*)szTempStr,(DWORD)strlen(szTempStr))!=ERROR_SUCCESS)
        {
            return FALSE;
        }
        RegFlushKey(hKey);//刷新注册表
        RegCloseKey(hKey);
     
    
        //-------------------------------------------------------------------------------------------------------
        // SYSTEM\\CurrentControlSet\\Services\\DriverName\\Instances\\DriverName Instance子健下的键值项 
        //-------------------------------------------------------------------------------------------------------
        strcpy(szTempStr,"SYSTEM\\CurrentControlSet\\Services\\");
        strcat(szTempStr,lpszDriverName);
        strcat(szTempStr,"\\Instances\\");
        strcat(szTempStr,lpszDriverName);
        strcat(szTempStr," Instance");
        if(RegCreateKeyEx(HKEY_LOCAL_MACHINE,szTempStr,0,"",REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,&hKey,(LPDWORD)&dwData)!=ERROR_SUCCESS)
        {
            return FALSE;
        }
        // 注册表驱动程序的Altitude 值
        strcpy(szTempStr,lpszAltitude);
        if(RegSetValueEx(hKey,"Altitude",0,REG_SZ,(CONST BYTE*)szTempStr,(DWORD)strlen(szTempStr))!=ERROR_SUCCESS)
        {
            return FALSE;
        }
        // 注册表驱动程序的Flags 值
        dwData=0x0;
        if(RegSetValueEx(hKey,"Flags",0,REG_DWORD,(CONST BYTE*)&dwData,sizeof(DWORD))!=ERROR_SUCCESS)
        {
            return FALSE;
        }
        RegFlushKey(hKey);//刷新注册表
        RegCloseKey(hKey);
    
    

    3.2 使用驱动

    inf安装完 只不过是注册了一个项到注册表.拷贝了驱动到系统目录.

    我们可以使用两种方式启动

    net start PassThrough
    fltmc load PassThrough
    

    两种方式都可以.
    inf也可以通过 函数 SetupCopyOEMInfA 来进行加载. 当然我们一般都是右键点击进行安装.
    如果是动态加载的方式,那么我们就按照动态加载的方式启动即可.

  • 相关阅读:
    fork()和vfork()的区别(转载)
    Linux中fork()函数详解(转载)
    ERROR:Simulator861-Failed to link the design解决办法
    ISE 14.7安装教程最新版(Win10安装)
    实验2用户及文件权限管理
    检验
    实验1基本概念及操作
    日常学习笔记(2)
    日常笔记1
    拷贝初始化的几种情况
  • 原文地址:https://www.cnblogs.com/iBinary/p/15809124.html
Copyright © 2020-2023  润新知