MiniFilter 是目前杀毒软件用来实现“文件系统自我保护”和“文件实时监控”的方法。


由于 MiniFilter 模型简单,开发快捷, 通用性好,以前用 FSD HOOK 或者标准过滤驱动来实现相关功能的杀软纷纷改用 MiniFilter, 比如卡巴斯基。 不过, 枚举 MiniFilter 就跟之前枚举回调的方法不太相同了,因为 MiniFilter 的框架不在 NTOSKRNL 里,自成一套系统, 有专用的 API。 不过“自成一套系统,有专用 API”的好处就是, 无需我们自己通过特征码来定位数组或者链表,直接使用 MiniFilter 提供的 API 就行。


枚举 MiniFilter 主要使用 FltEnumerateFilters 这个 API,它会返回过滤器对象(FLT_FILTER)的地址,然后根据过滤器对象的地址, 加上一个偏移,获得记录过滤器 PreCallPostCallIRP 等信息的结构体指针(PFLT_OPERATION_REGISTRATION)。 上文之所以说要加上偏移,是因为 FLT_FILTER 的定义在每个系统都不同, 比如 WIN7X64 中的定义为:

lkd> dt fltmgr!_FLT_FILTER
+0x000 Base : _FLT_OBJECT
+0x020 Frame : Ptr64 _FLTP_FRAME
+0x028 Name : _UNICODE_STRING
+0x038 DefaultAltitude : _UNICODE_STRING
+0x048 Flags : _FLT_FILTER_FLAGS
+0x050 DriverObject : Ptr64 _DRIVER_OBJECT
+0x058 InstanceList : _FLT_RESOURCE_LIST_HEAD
+0x0d8 VerifierExtension : Ptr64 _FLT_VERIFIER_EXTENSION
+0x0e0 VerifiedFiltersLink : _LIST_ENTRY
+0x0f0 FilterUnload : Ptr64 long
+0x0f8 InstanceSetup : Ptr64 long
+0x100 InstanceQueryTeardown : Ptr64 long
+0x108 InstanceTeardownStart : Ptr64 void
+0x110 InstanceTeardownComplete : Ptr64 void
+0x118 SupportedContextsListHead : Ptr64 _ALLOCATE_CONTEXT_HEADER
+0x120 SupportedContexts : [6] Ptr64 _ALLOCATE_CONTEXT_HEADER
+0x150 PreVolumeMount : Ptr64 _FLT_PREOP_CALLBACK_STATUS
+0x158 PostVolumeMount : Ptr64 _FLT_POSTOP_CALLBACK_STATUS
+0x160 GenerateFileName : Ptr64 long
+0x168 NormalizeNameComponent : Ptr64 long
+0x170 NormalizeNameComponentEx : Ptr64 long
+0x178 NormalizeContextCleanup : Ptr64 void
+0x180 KtmNotification : Ptr64 long
+0x188 Operations : Ptr64 _FLT_OPERATION_REGISTRATION
+0x190 OldDriverUnload : Ptr64 void
+0x198 ActiveOpens : _FLT_MUTEX_LIST_HEAD
+0x1e8 ConnectionList : _FLT_MUTEX_LIST_HEAD
+0x238 PortList : _FLT_MUTEX_LIST_HEAD
+0x288 PortLock : _EX_PUSH_LOCK

不过幸好 FLT_OPERATION_REGISTRATION 的结构体定义是不变的:

lkd> dt_FLT_OPERATION_REGISTRATION
fltmgr!_FLT_OPERATION_REGISTRATION
+0x000 MajorFunction : UChar
+0x004 Flags : Uint4B
+0x008 PreOperation : Ptr64 _FLT_PREOP_CALLBACK_STATUS
+0x010 PostOperation : Ptr64 _FLT_POSTOP_CALLBACK_STATUS
+0x018 Reserved1 : Ptr64 Void

枚举的代码如下:

ULONG EnumMiniFilter()
{
    long ntStatus;
    ULONG uNumber;
    PVOID pBuffer = NULL;
    ULONG uIndex = 0, DrvCount = 0;
    PVOID pCallBacks, pFilter;
    PFLT_OPERATION_REGISTRATION pNode;
    do
    {
        if (pBuffer != NULL)
        {
            ExFreePool(pBuffer);
            pBuffer = NULL;
        }
        ntStatus = FltEnumerateFilters(NULL, 0, &uNumber);
        if (ntStatus != STATUS_BUFFER_TOO_SMALL)
            break;
        pBuffer = ExAllocatePoolWithTag(NonPagedPool, sizeof(PFLT_FILTER) * uNumber,'mnft');
        if (pBuffer == NULL)
        {
            ntStatus = STATUS_INSUFFICIENT_RESOURCES;
            break;
        }
        ntStatus = FltEnumerateFilters(pBuffer, uNumber, &uNumber);
    } while (ntStatus == STATUS_BUFFER_TOO_SMALL);
    if (!NT_SUCCESS(ntStatus))
    {
        if (pBuffer != NULL)
            ExFreePool(pBuffer);
        return 0;
    }
    DbgPrint("MiniFilter Count: %ld\n", uNumber);
    DbgPrint("------\n");
    __try
    {
        while (DrvCount < uNumber)
        {
            pFilter = (PVOID)(*(PULONG64)((PUCHAR)pBuffer + DrvCount * 8));
            pCallBacks = (PVOID)((PUCHAR)pFilter + FltFilterOperationsOffset);
            pNode = (PFLT_OPERATION_REGISTRATION)(*(PULONG64)pCallBacks);
            __try
            {
                while (pNode->MajorFunction != 0x80) //IRP_MJ_OPERATION_END
                {
                    if (pNode->MajorFunction < 28) //MajorFunction id is 0~27
                    {
                        DbgPrint("Object=%p\tPreFunc=%p\tPostFunc=%p\tIRP=%d\n",
                            pFilter,
                            pNode->PreOperation,
                            pNode->PostOperation,
                            pNode->MajorFunction);
                    }
                    pNode++;
                }
            }
            __except (EXCEPTION_EXECUTE_HANDLER)
            {
                FltObjectDereference(pFilter);
                DbgPrint("[EnumMiniFilter]EXCEPTION_EXECUTE_HANDLER: pNode->MajorFunction\n");
                ntStatus = GetExceptionCode();
                ExFreePool(pBuffer);
                return uIndex;
            }
            DrvCount++;
            FltObjectDereference(pFilter);
            DbgPrint("------\n");
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        FltObjectDereference(pFilter);
        DbgPrint("[EnumMiniFilter]EXCEPTION_EXECUTE_HANDLER\n");
        ntStatus = GetExceptionCode();
        ExFreePool(pBuffer);
        return uIndex;
    }
    if (pBuffer != NULL)
    {
        ExFreePool(pBuffer);
        ntStatus = STATUS_SUCCESS;
    }
    return uIndex;
}

代码执行的效果如下图(可以对比一下运行 WIN64AST 前后枚举的结果有什么不同):

不过对抗 MiniFilter 似乎就只有两种方法了:

1.把记录的函数地址改为自己设置的空函数;

2.把处理函数头改为 RET 直接返回。 

为什么不能直接把 MiniFilter 对象反注册呢?因为MSDN 对 FltUnregisterFilter 的用途给出了这样的解释A minifilter driver can only call FltUnregisterFilter to unregister itself, not another minifilter driver。 据我测试,如果第三方驱动强制使用此函数注销一个 MiniFilter, 轻则无效,重则蓝屏。

MINIFILTER 的处理函数禁用掉之后,卡巴斯基 2013 WIN64 系统上的文件保护就彻底失效了, 可以直接使用最简单的方法来删除卡巴斯基文件夹内的文件,国内那些采用同样方法实现文件自我保护的杀毒软件(360、 金山毒霸等)同理。






打赏作者