MiniFilter内核回调
漏洞分析
微过滤器内核回调是专门的回调例程,它用于在不同的阶段监控和拦截文件系统操作。比如在创建文件阶段监控和拦截,在修改文件内容阶段进行监控和拦截。 而EDR利用这些回调来捕获有关恶意文件系统活动的遥测数据。比如检测攻击者什么时候将恶意文件落地到从磁盘上,识别短时间内大量文件被加密的情况。
#### MiniFilter如何监控文件系统? 微过滤器内核回调是专门的回调例程,它用于在不同的阶段**监控**和**拦截**文件系统操作。比如在**创建文件阶段监控和拦截**,在**修改文件内容阶段进行监控和拦截**。 而`EDR`利用这些回调来捕获有关恶意文件系统活动的遥测数据。比如检测攻击者什么时候将恶意文件落地到从磁盘上,识别短时间内大量文件被加密的情况。 #### CALLBACK\_NODE与双向链表 微过滤器回调存储在一个由**CALLBACK\_NODE**结构组成的双向链表中,每一个**微过滤器实例**都维护着这样一个列表,用于处理不同的文件系统操作。 微过滤器通常注册两种类型的回调: **操作前回调**(`Pre-Operation Callback`)和**操作后回调(Post-operation callback)**。 **操作前回调在文件操作发生之前进行调用,而操作后回调将在文件系统操作完成之后调用。** 微过滤器回调是通过`FltRegisterFilter` `API`函数来进行注册的。 现在我们来分析一下这张图:  像`EDR Filter`和其他微过滤器驱动程序都采用了分层架构来拦截和监控文件系统操作。这里所说的分层是驱动程序在系统堆栈中的垂直排列顺序。**比如当一个 "文件写入" 请求从应用层发出时,它会像流水一样,从最高海拔的过滤器依次向下流动,每一个层级都会机会查看,修改或拦截这个请求。** 每一个微过滤器驱动可以拥有一个或多个实例(`Instance`),使其能够挂钩到不同的文件系统卷或操作中。比如说你有C盘和D盘,微过滤器驱动会分别为他们创建`InstanceC` 和 `InstanceD`的实例。这样的话当你在C盘去创建文件时触发的是`Instance C`实例中的回调,而你要是在`D`盘创建文件时,触发的就是`Instance D`实例中的回调。 在每一个实例的内部,针对特定的文件系统操作注册了多个回调节点结构(Callback Node)。一个回调节点处理一种特定的文件系统操作,比如`Create`节点处理创建文件操作,`Read`节点处理读取文件操作。 每一个回调节点都包含了一个操作前和操作后的内核回调,**操作前的内核回调**位于回调节点偏移**0x18**处,在文件操作执行之前被调用。操作后的内核回调位于回调节点偏移**0x20**处,在文件操作执行之后被调用。  现在我们来看一下`FltRegisterFilter`函数,该函数用于在`Filter Manager`中注册微过滤器。 ```c NTSTATUS FLTAPI FltRegisterFilter( [in] PDRIVER_OBJECT Driver, //程序对象的指针 [in] const FLT_REGISTRATION *Registration, //指向调用方分配的微型筛选器驱动程序注册结构的指针 [out] PFLT_FILTER *RetFilter ); ``` 这里我们需要先来看看`FLT_REGISTRATION`结构体,他是微过滤器配置的核心,其参数中包含了指向各种回调函数的指针。 在`FLT_REGISTRATION`结构体中关键字段包含: 1. **Callback:** 指向操作注册数组,该数组定义了针对不同I/O的操作,比如创建,写入,删除,读取等回调函数。 2. **Unload:** 指向卸载例程的指针 当微过滤器被卸载时,系统会调用该例程。 #### 使用IDA PRO及Windbg分析注册例程 现在我们打开`mssecFlt.sys`驱动程序,并在导入表中搜索`FltRegisterFilter`函数。该函数是在`SecIntilization`中调用的。  我们主要来看他的第二个参数,第二个参数是包含回调函数的`FLT_REGISTRATION`结构体的地址。  我们双击跟进去,如下图中展示该驱动程序`FLT_REGISTRATION`结构体的静态布局,我们主要看第五个成员,也就是`Callbacks`,这是我们`Callback Node`的起始地址,它指向一个`FLT_OPERATION_REGISTRATION`数组,里面包含了`CREATE` `READ` `WRITE`等操作的具体回调指针。  我们双击`Callbacks`进去,我们就可以看到针对不同`I/O`的操作,比如创建,读取,写入等等的各种微过滤器操作前和操作后内核回调。  我们可以点进去任意一个内核回调。然后查看其源代码,在源代码中我们可以了解到驱动程序在文件操作之前执行的具体逻辑。  现在让我们对微过滤器的内核回调进行动态分析。 如果要列出当前系统中加载的所有微过滤器,我们需要使用`!fltkd.filters`命令。该命令将显示所有已注册的`FLT_FILTER`结构列表,每一个结构都代表一个已经加载的微过滤器驱动。  前面提到微过滤器是分层架构的,我们可以看到如下图中是按照`Altitude`从高到底排列的,这决定了`I/O`请求的生命历程。 如下图中的微过滤器名称后面的数字我们统称为`Altitude`,数字越大,层级越高。`I/O`请求会从高到低依次穿过这些驱动。 这些过滤器就好比是一个滤网,每一层都要过一遍。首先经过`bindflt`,如果`bindflt`放行,那么再去经过`UCPD`,以此类推。  从下图中我们得知,每一个微过滤器可以包含一个或或多个实例,它代表着附加到某个磁盘卷上的过滤器实例。比如如下图中的`WdFilter`有五个实例,说明它正在同时监控五个不同的卷,比如说C盘,D盘,网络共享等等。 如果我们要分析这些实例,我们可以通过`!instance`命令来查看。该命令会显示过滤器实例的详细信息,包括其针对特定操作的回调节点。 所以当操作系统发起一个文件操作时,比如用户打开一个文件,也就是`Create`操作时,`Filter Manager`会找到该磁盘卷所对应的所有`FLT_INSTANCE`实例。随后查找每一个实例中所对应的`CALLBACK_NODE`,如果节点存在,那么就执行该节点中注册的函数。 ```js !instance ffffc38faec11930 4 ```  每一个实例中都包含一个或多个`CALLBACK_NODE`(回调节点)结构。一个回调节点处理一种特定的文件系统操作,比如`Create`节点处理创建文件操作,`Read`节点处理读取文件操作。  回调节点(CALLBACK\_NODE)代表了微过滤器驱动中一个已经注册的操作回调。可以使用`dt _CALLBACK_NODE <Address>`来查看。其中的`CallbackLinks`指向回调节点双向链表中上一个和下一个的指针。`Instance`是实例指针,指向关联的`FLT_INSTANCE`结构,该结构包含了特定于该实例的回调数据。我们还有`PreOperation`表示操作前回调函数的指针,`PostOperation`表示操作后回调函数的指针。 下面的图有点问题,**Bink和Flink写反了**。  所以如果我们想要查看`Instance`实例指针,我们只需要查看`FLT_INSTANCE`结构即可。 ```js dt _FLT_INSTANCE 0xffffc38f`ae7db4a0 ```  #### 自定义微过滤器内核回调及动态分析 现在我们来看看微过滤器回调在被调用期间都收集了那些遥测数据。微过滤器内核回调提供了有关文件系统操作的详细信息。收集的关键数据包括对文件系统执行的操作类型,比如创建文件,写入文件。还有就是发起该操作的进程PID以及线程TID等等遥测数据。 现在我们来尝试模拟`EDR`注册微过滤器,并且收集相关的遥测数据。如下微过滤器代码: ```c #include <fltKernel.h> // 声明全局句柄 PFLT_FILTER gFilterHandle = NULL; extern "C" FLT_PREOP_CALLBACK_STATUS PreOperationCallback( _Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, _Flt_CompletionContext_Outptr_ PVOID* CompletionContext ) { UNREFERENCED_PARAMETER(CompletionContext); UNREFERENCED_PARAMETER(FltObjects); //获取执行文件系统操作的进程PID HANDLE processId = PsGetCurrentProcessId(); //获取执行文件系统操作的线程PID HANDLE threadId = PsGetCurrentThreadId(); //判断I/O请求的操作如果是读的操作则进入分支 if (Data->Iopb->MajorFunction == IRP_MJ_READ) { DbgPrintEx(0, 0, "[Minifilter] Read Operation: PID: %p, TID: %p\n", processId, threadId); } //判断I/O请求的操作如果是写的操作则进入分支 else if (Data->Iopb->MajorFunction == IRP_MJ_WRITE) { DbgPrintEx(0, 0, "[Minifilter] Write Operation: PID: %p, TID: %p\n", processId, threadId); } //判断I/O请求的操作如果是删除或重命名的操作则进入分支 else if (Data->Iopb->MajorFunction == IRP_MJ_SET_INFORMATION) { FILE_INFORMATION_CLASS fileInfoClass = Data->Iopb->Parameters.SetFileInformation.FileInformationClass; if (fileInfoClass == FileDispositionInformation || fileInfoClass == FileRenameInformation) { DbgPrintEx(0, 0, "[Minifilter] Delete/Rename: PID: %p\n", processId); } } return FLT_PREOP_SUCCESS_NO_CALLBACK; } extern "C" FLT_POSTOP_CALLBACK_STATUS PostOperationCallback( _Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, _In_opt_ PVOID CompletionContext, _In_ FLT_POST_OPERATION_FLAGS Flags ) { UNREFERENCED_PARAMETER(Data); UNREFERENCED_PARAMETER(FltObjects); UNREFERENCED_PARAMETER(CompletionContext); UNREFERENCED_PARAMETER(Flags); return FLT_POSTOP_FINISHED_PROCESSING; } //Unload例程 extern "C" NTSTATUS Unload(_In_ FLT_FILTER_UNLOAD_FLAGS Flags) { UNREFERENCED_PARAMETER(Flags); if (gFilterHandle != NULL) { FltUnregisterFilter(gFilterHandle); DbgPrintEx(0, 0, "[Minifilter] Unloaded Successfully\n"); } return STATUS_SUCCESS; } // 注册列表 CONST FLT_OPERATION_REGISTRATION CallbackList[] = { //当I/O请求为读的操作时将调用PreOperationCallback函数(操作前回调)和PostOperationCallback操作后回调 { IRP_MJ_READ, 0, PreOperationCallback, PostOperationCallback }, //当I/O请求为写的操作时将调用PreOperationCallback函数(操作前回调)和PostOperationCallback操作后回调 { IRP_MJ_WRITE, 0, PreOperationCallback, PostOperationCallback }, //当I/O请求为删除或重命名的操作时将调用PreOperationCallback函数(操作前回调)和PostOperationCallback操作后回调 { IRP_MJ_SET_INFORMATION, 0, PreOperationCallback, PostOperationCallback }, { IRP_MJ_OPERATION_END } }; // 注册结构 CONST FLT_REGISTRATION FilterRegister = { sizeof(FLT_REGISTRATION), //结构体的大小 FLT_REGISTRATION_VERSION, //如果要注册微过滤器则需要设置FLT_REGISTRATION_VERSION 0, NULL, CallbackList, //指向FLT_OPERATION_REGISTRATION结构数组的指针 该结构数组中设置操作回调 Unload, //当微过滤器卸载时调用 NULL, NULL, NULL, NULL, NULL, NULL, NULL }; extern "C" NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER(RegistryPath); //第二个参数指向FLT_REGISTRATION结构体的指针 所以需要填充该结构体 NTSTATUS status = FltRegisterFilter(DriverObject, &FilterRegister, &gFilterHandle); if (NT_SUCCESS(status)) { status = FltStartFiltering(gFilterHandle); if (!NT_SUCCESS(status)) { FltUnregisterFilter(gFilterHandle); } } return status; } ``` 如果我们想要加载微过滤器驱动,我们不能向往常那样直接使用`sc create`的方式去加载,我们在加载之前需要手动的在`Windows`注册表中配置微型过滤器驱动程序所需的环境。 如下通过`Powershell`代码来进行配置。我们需要将其驱动程序放置到`Public`目录下,当然也可以是其他的目录,只需要更改`ImagePath`这一行的路径即可,我们可以设置其海拔高度,也就是`Altitude`,这决定了我们的微型过滤器驱动什么时候接收到I/O请求。设置的越高,那么接收到该请求越早,因为前面还有其他的过滤器驱动。 ```c # 在 Services(服务)下创建主键 MiniFilterDriver $MiniFilterDriverPath = "HKLM:\SYSTEM\CurrentControlSet\Services\MiniFilterDriver" New-Item -Path $MiniFilterDriverPath -Force # 设置驱动文件路径 (ImagePath)、类型 (Type)、启动方式 (Start) 和错误控制 (ErrorControl) Set-ItemProperty -Path $MiniFilterDriverPath -Name "ImagePath" -Value "\??\C:\Users\Public\MiniFilterDriver.sys" Set-ItemProperty -Path $MiniFilterDriverPath -Name "Type" -Value 0x00000001 Set-ItemProperty -Path $MiniFilterDriverPath -Name "Start" -Value 0x00000003 Set-ItemProperty -Path $MiniFilterDriverPath -Name "ErrorControl" -Value 0x00000001 # 创建 Instances(实例)键及其子键 $instancesPath = "$MiniFilterDriverPath\Instances" New-Item -Path $instancesPath -Force # 在 Instances 键中设置默认实例名称 (DefaultInstance) Set-ItemProperty -Path $instancesPath -Name "DefaultInstance" -Value "MiniFilterDriver Instance" # 在 Instances 下创建具体的 "MiniFilterDriver Instance" 键 $instancePath = "$instancesPath\MiniFilterDriver Instance" New-Item -Path $instancePath -Force # 在该实例键下设置海拔高度 (Altitude) 和标志位 (Flags) Set-ItemProperty -Path $instancePath -Name "Altitude" -Value "370000" Set-ItemProperty -Path $instancePath -Name "Flags" -Value 0x00000000 ``` 以管理员的身份执行上述`Powershell`代码。  接下来我们可以通过`fltmc`命令来列出当前系统所有已加载的微过滤器驱动程序,如果我们需要加载我们的微过滤器驱动程序则需要使用`fltmc load <过滤器名称>` 即可。  可以看到加载之后,在`windbg`这里就已经开始获取遥测数据了。  我们可以通过之前的命令`!fltkd.filter`来查看我们注册的过滤器。  比如说我们查看其中一个实例,`!instance ffff8f8db5768cd0 4`,但是我们发现报错了,这是因为找不到符号文件的问题,我们可以强制开启不明确符号解析: `.symopt- 100`。  这样就可以了。这些`CALLBACK_NODE`就是我们在数组中设置的那些。  现在我们来看看如何移除微过滤器的回调节点,我们需要操作当前回调节点的`Flink`和`Blink`指针,从链表结构中断开微过滤器回调节点的连接。 每一个回调节点都是回调链表结构的一部分,该结构的类型为`LIST_ENTRY`,这意味着他是一个包含`Flink`和`Blink`字段的双向链表。  如果我们在此定位到回调节点的地址,按照`LIST_ENTRY`结构进行解析,就能看到回调节点的`Flink`指针和`Blink`指针。 所以我们可以执行`dt nt!_LIST_ENTRY <回调节点的地址>`,可以输出该回调节点的前后链接`Flink`和`Blink`。  所以我们只需要将其相邻节点的指针相互指向,并把当前节点的`Flink`和`Blink`指针指向当前节点自身的地址,从而将该节点从链表中断开。 例如我们现在要对其如下节点进行断链。  我们现在得到的下一个节点(`Flink`)地址为:`0xffff8f8d`b5768f00`。上一个节点(`Blink`)的地址为: `0xffff8f8d`b30e32d0`。 所以我们可以画如下这张图: 如下图是一个双向链表,如果我们现在需要断链的话,我们就需要让`0xffff8f8d`b30e32d0`地址节点的`Flink`指向`0xffff8f8d`b5768f00`节点,而让`0xffff8f8d`b5768f00`地址的`Blink`指向`0xffff8f8d`b30e32d0`节点。  那么我们就可以使用`eq`命令来进行修改。 ```c eq 0xffff8f8d`b30e32d0 0xffff8f8d`b5768f00 //``0xffff8f8d`b30e32d0``地址节点的``Flink``指向``0xffff8f8d`b5768f00``节点 eq 0xffff8f8d`b5768f00+0x8 0xffff8f8d`b30e32d0 ``` 现在就变成了:  现在还有一个问题就是中间这个情况没有断干净,所以我们还需要让中间这个节点的`Flink`和`Blink`都指向其自身。 ```c eq 0xffff8f8d`b31802c0 0xffff8f8d`b31802c0 eq 0xffff8f8d`b31802c0+0x8 0xffff8f8d`b31802c0 ```  所以这样就可以了。 #### 移除Sysmon微过滤器内核回调 接下来我们将移除`Sysmon`中的微过滤器内核回调。 安装`Sysmon`工具如下: [https://blog.csdn.net/weixin\_52444045/article/details/138791291](https://blog.csdn.net/weixin_52444045/article/details/138791291)  我们打开事件查看器->应用程序和服务日志->Microsoft->Windows->Sysmon来查看操作日志。 这是我们刚才打开了一个`notepad.exe`所产生的日志。  那么我们来到`windbg`这里查看微过滤器。可以看到`Sysmon`的微过滤器名称为`SysmonDrv`。  我们将脱链所有与文件创建操作相关的回调节点,使`Sysmon`无法再拦截这些事件。我们可以在如上图中看到有4个实例,我们将针对所有的`Sysmon`实例来执行该操作。 我们现在来查看这些实例,首先是第一个实例。我们主要对`CREATE`这个`CALLBACK_NODE`感兴趣。  现在我们来列出它的`Flink`和`Blink`指针。  那么现在我们就可以来进行脱链了。 ```c kd> eq 0xffffcf0c`806a8270+0x8 0xffffcf0c`83b37240 kd> eq 0xffffcf0c`83b37240 0xffffcf0c`806a8270 kd> eq 0xffffcf0c83b79e80 ffffcf0c83b79e80 kd> eq ffffcf0c83b79e80+0x8 ffffcf0c83b79e80 ``` 这只是一个实例,我们需要将其余的3个实例都进行这些操作。    修改之后,我们再去尝试打开文件,看看`Sysmon`是否还会记录。当新创建一个文件时`Sysmon`已经不会有日志记录了。  除了`Sysmon`之外,我们可以对其他微过滤器的不同文件系统输入/输出进行类似的操作。通过这种方式可以直接把恶意文件拉到磁盘上,因为回调被断开连接,所以回调是不知道我们文件到磁盘上的,所以也就不会进行扫描了。 #### 枚举系统中的微过滤器 现在我们将通过代码编写的方式来枚举系统上的微过滤器驱动 首先我们需要编写一个`Rookit`驱动程序,该驱动程序的主要作用是用来枚举系统上的微过滤器,并获取到其为微过滤器的名称以及海拔高度。 我们主要使用到两个函数: `FltEnumerateFilters`和`FltGetFilterInformation`函数。 `FltEnumerateFilters`函数主要用于枚举系统中已注册的微过滤器驱动程序的句柄。 ```c NTSTATUS FltEnumerateFilters( [out] PFLT_FILTER *FilterList, //一个指向缓冲区数组的指针 用于存放获取到的微过滤器句柄 第一次调用时为NULL [in] ULONG FilterListSize, //缓冲区能容纳的句柄数量。 [out] PULONG NumberFiltersReturned //实际存入缓冲区的句柄数量 如果缓冲区太小,那么它将返回系统中实际存在的过滤器总数 方便你后续分配内存 ); ``` 正如注释一样,该函数需要我们调用两次,第一次将返回系统中实际存在的过滤器总数,通过过滤器的总数去乘以`PFLT_FILTER`结构的大小就可以计算出所有句柄所需的字节数,然后为其分配内存缓冲区。 如下代码: ```c status = FltEnumerateFilters(NULL, bufferSize, &filterCount); if (status != STATUS_BUFFER_TOO_SMALL) { return status; } //filterCount是过滤器的总数 而size(PFLT_FILTER)是单个过滤器句柄指针的大小 计算得出存储所有句柄所需的字节数 bufferSize = filterCount * sizeof(PFLT_FILTER); filterList = (PFLT_FILTER*)ExAllocatePool2(POOL_FLAG_NON_PAGED, bufferSize, DRIVER_TAG); ``` 第二次调用时将填充`FilterList`,填充后,`FilterList`中现在存储着所有微过滤器的句柄。 ```c status = FltEnumerateFilters(filterList, bufferSize, &filterCount); if (!NT_SUCCESS(status)) { ExFreePool(filterList); return status; } ``` 然后我们定义了一个`MiniFilterData`的结构,让该结构的指针指向系统缓冲区Buffer。 ```c typedef struct _MinifilterData { WCHAR FilterName[256]; //存储过滤器名称的宽字符数组 WCHAR FilterAltitude[256]; //存储过滤器海拔(Altitude)的宽字符数组 } MinifilterData, * PMinifilterData; //将系统缓冲区Buffer赋值给MinifilterData结构类型的指针 MinifilterData* filtersData = (MinifilterData*)(*pBuffer); ``` 然后遍历所有过滤器的句柄,filterCount是系统上所有微过滤器的总数。从之前分配的列表中取出过滤器句柄,之后调用`FltGetFilterInformation`函数来获取微过滤器的基本信息,这基本信息包含了微过滤器的名称以及海拔高度。 该函数也需要调用两次,第一次将返回过滤器信息所需的字节数。第二次是真正获取微过滤器的基本信息。 ```c NTSTATUS FltGetFilterInformation( [in] PFLT_FILTER Filter, // 目标过滤器的句柄 [in] FILTER_INFORMATION_CLASS InformationClass, // 请求的信息类型 [out] PVOID Buffer, // 接收数据的缓冲区 [in] ULONG BufferSize, // 缓冲区大小 [out] PULONG BytesReturned // 实际写入或需要的字节数 ); ``` ```c //遍历所有的微过滤器 filterCount是系统上所有微过滤器的总数 for (ULONG i = 0; i < filterCount; i++) { PFLT_FILTER filter = filterList[i]; //从之前分配的列表中取出当前的过滤器句柄 PFILTER_AGGREGATE_BASIC_INFORMATION filterInfo = NULL; //定义一个指向基本信息结构体的指针 ULONG filterInfoSize = 0; //定义变量用于接收系统返回的实际信息大小 ULONG filterInfoBufferSize = 0; //定义变量用于指定我们为该过滤器分配的缓冲区大小 //FltGetFilterInformation函数是根据过滤器句柄来提取其详细的元数据,比如名称,海拔高度,实例的数量等等。 /* NTSTATUS FltGetFilterInformation( [in] PFLT_FILTER Filter, // 目标过滤器的句柄 [in] FILTER_INFORMATION_CLASS InformationClass, // 请求的信息类型 [out] PVOID Buffer, // 接收数据的缓冲区 [in] ULONG BufferSize, // 缓冲区大小 [out] PULONG BytesReturned // 实际写入或需要的字节数 ); */ //FilterAggregateBasicInformation 请求的信息类型指的是获取汇总后的基本信息 包括名称和海拔 //第一次调用是为了返回存储该过滤器信息所需的字节数 status = FltGetFilterInformation(filter, FilterAggregateBasicInformation, NULL, 0, &filterInfoBufferSize); //分配内存 和上面一样 分配的内存主要是存储过滤器的信息 filterInfo = (PFILTER_AGGREGATE_BASIC_INFORMATION)ExAllocatePool2( POOL_FLAG_NON_PAGED, // 对应旧的 NonPagedPool filterInfoBufferSize, DRIVER_TAG ); if (!filterInfo) { continue; } //第二次调用来获取数据 status = FltGetFilterInformation(filter, FilterAggregateBasicInformation, filterInfo, filterInfoBufferSize, &filterInfoSize); if (!NT_SUCCESS(status)) { ExFreePool(filterInfo); continue; } ``` 当第二次调用的时候它会填充`PFILTER_AGGREGATE_BASIC_INFORMATION`结构。 我们来看一下该结构,该结构中我们关心的是`Type.MiniFilter` ,其中的`FilterNameLength`表示过滤器名称的长度,`FilterNameBufferOffset`表示名称字符串偏移地址,这意味着我们需要从该结构体的起始地址加上`FilterNameBufferOffset`偏移才可以获取到微过滤器的名称。`FilterAltitudeLength`是海拔高度字符串的长度,而真正的海拔高度存储在`FilterAltitudeBufferOffset`偏移量中,这意味着需要从结构的初始地址加上`FilterAltitudeBufferOffset`偏移量才能获取到海拔高度。 ```c typedef struct _FILTER_AGGREGATE_BASIC_INFORMATION { ULONG NextEntryOffset; ULONG Flags; union { struct { ULONG FrameID; ULONG NumberOfInstances; USHORT FilterNameLength; USHORT FilterNameBufferOffset; USHORT FilterAltitudeLength; USHORT FilterAltitudeBufferOffset; } MiniFilter; struct { USHORT FilterNameLength; USHORT FilterNameBufferOffset; } LegacyFilter; } Type; } FILTER_AGGREGATE_BASIC_INFORMATION, *PFILTER_AGGREGATE_BASIC_INFORMATION; ``` 所以我们可以通过偏移量访问的方式来获取到微过滤器的名称和海拔高度。 ```c //通过PFILTER_AGGREGATE_BASIC_INFORMATION结构体的起始地址 + 偏移 计算出过滤器名称在内存中的绝对路径 PWCHAR filterNameAddr = (PWCHAR)((PCHAR)filterInfo + filterInfo->Type.MiniFilter.FilterNameBufferOffset); //通过PFILTER_AGGREGATE_BASIC_INFORMATION结构体的起始地址 + 偏移 计算出海拔高度在内存中的绝对路径 PWCHAR filterAltitudeAddr = (PWCHAR)((PCHAR)filterInfo + filterInfo->Type.MiniFilter.FilterAltitudeBufferOffset); ``` 现在我们得到了微过滤器的名称和海拔高度,分别存储在`filterNameAddr`和`filterAltitudeAddr`中。 那么现在我们就可以将其复制到系统缓冲区`Buffer`中了。 ```c //将filterNameAddr地址所指向的内容复制到filtersData数组的FilterName成员中。 wcsncpy(filtersData[i].FilterName, filterNameAddr, min(filterInfo->Type.MiniFilter.FilterNameLength / sizeof(WCHAR), 255)); filtersData[i].FilterName[min(filterInfo->Type.MiniFilter.FilterNameLength / sizeof(WCHAR), 255)] = L'\0'; //将filterAltitudeAddr地址所指向的内容复制到filtersData数组的FilterAltitude成员中。 wcsncpy(filtersData[i].FilterAltitude, filterAltitudeAddr, min(filterInfo->Type.MiniFilter.FilterAltitudeLength / sizeof(WCHAR), 255)); filtersData[i].FilterAltitude[min(filterInfo->Type.MiniFilter.FilterAltitudeLength / sizeof(WCHAR), 255)] = L'\0'; ``` 这就是驱动层的代码了。 现在来看一下用户层的代码,首先打开驱动设备的句柄,通过`CreateFileA`函数。 ```c HANDLE Hdevice =CreateFileA("\\\\.\\LCKDriver", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); ``` 然后在堆栈上分配内存用于接收数据,下一步则是发送I/O设备请求,需要注意的是这里的IOCTL控制码要和驱动程序上的一样。 ```c //在堆栈分配64kb内存 DWORD lpBytesReturned = 0; // 接收驱动程序实际返回的字节数 BYTE buffer[1 << 16]; // 分配 64KB 的缓冲区 (1 << 16 = 65536) memset(buffer, 0, sizeof(buffer)); // 初始化缓冲区为 0 BOOL success = DeviceIoControl( Hdevice, IOCTL_LIST_MINIFILTERS, // 控制码 NULL, 0, // 输入缓冲区(这里不需要输入) buffer, sizeof(buffer), // 输出缓冲区(驱动填充数据的地方) &lpBytesReturned, // 实际返回的长度 NULL ); ``` 最后接收数据打印即可。 ```c if (success) { DWORD count = lpBytesReturned / sizeof(MinifilterData); MinifilterData* data = (MinifilterData*)buffer; wprintf(L"\n成功获取到 %lu 个微过滤器信息:\n", count); wprintf(L"--------------------------------------------------\n"); //表头应该只打印一次,放在循环外面 --- wprintf(L"%-4s | %-20s | %s\n", L"ID", L"Filter Name", L"Altitude"); wprintf(L"--------------------------------------------------\n"); //修正:只需要这一个循环即可 --- for (DWORD i = 0; i < count; i++) { //打印数据 wprintf(L"[%02lu] | %-20ls | %ls\n", i, data[i].FilterName, data[i].FilterAltitude); } wprintf(L"--------------------------------------------------\n"); } else { printf("IOCTL 请求失败,错误代码: %lu\n", GetLastError()); } ``` 如下图,我们成功枚举出了系统上的所有的微过滤器。  本文就先到这里,期待下次相遇!!!
发表于 2026-03-19 09:00:02
阅读 ( 112 )
分类:
二进制
0 推荐
收藏
0 条评论
南陈
3 篇文章
×
温馨提示
您当前没有「奇安信攻防社区」的账号,注册后可获取更多的使用权限。
×
温馨提示
您当前没有「奇安信攻防社区」的账号,注册后可获取更多的使用权限。
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!