
前言
Dive into kernel系列,直译为深入内核系列,但我更喜欢他的另外一个名字:潜水系列,毕竟充分显示了笔者多年潜水不冒泡的功力;)
扯远了,言归正传。写这个系列的初衷是看到如今网络上充斥着各种热点事件的分析报告(当然这并没有什么不好)。然而作为安全研究人员,对系统机制的深入分析文章近年来并不多见。于是乎笔者打算来一波反向操作,写一些深入内核层面的分析报告。
此系列不追求分析最热最新的事件,相反有可能上手的样本都是n年前就出现过的老样本。本文追求通过这些样本使用的手段,研究系统更深层次的机制。
本系列的定位为硬核科普,适合有一定基础需要进一步提升技术能力的读者。至于本系列能写多少篇,取决于各位观众老爷们买不买账,因此请各位观众老爷请多多点赞收藏转发,相信你们的支持会让这个系列走的更远 Orz。
紫狐木马家族
该家族最早可以追溯到2018年,我们近期也在应急处置的过程中多次在客户机器上发现该家族木马的身影。目前网络上对该家族有不少分析报告,因此本文只对重点的地方做简要说明,详情可以参考本文最后引用的来自腾讯安全团队的分析报告。
首先是木马持久化所使用的手段,从行为监控工具中发现其路径如下:
安装包 ---> 写入PendingFileRenameOperations注册表项 ---> 替换系统服务 ---> 释放木马组件 ---> 隐藏关键服务和文件
我们看一下写入的PendingFileRenameOperations注册表键值内容:

该注册表键值的格式为:
源文件1
目的文件1
源文件2
目的文件2
……
如果目的文件为空(0x00),则意为删除源文件。
因此,病毒实际上是通过替换系统文件sens.dll从而达到启动目的的,这个DLL对应的服务为:

由于Windows文件系统规定:被使用的文件不能被删除,而系统服务对应的dll文件肯定是被使用状态,因此病毒采用了这么“麻烦”的方法来删除系统文件。那么问题是,这个键值为何可以用来删除文件?
深呼吸,我们开始第一次下潜。
Windows的启动过程
首先,开局一张图:

可以看到R3的第一个具有实体文件的进程为smss.exe,这意味着此刻服务还没启动,因此必然没有上文中说的服务DLL文件被占用无法删除的情况。
也正是因为此刻大部分系统进程还没有启动,系统有一次删除任何文件的机会,在smss.exe初始化阶段,通过PendingFileRenameOperations键值,系统便可以知道该对哪些文件进行操作。通过对WRK的搜索,发现此键值涉及的代码如下:

通过查询RtlQueryRegistryValues的函数原型可知, 该函数会调用SmpRegistryConfigurationTable中的回调函数:SmpConfigureFileRenames。在调用的同时候会将SmpFileRenameList作为最后一个参数传递给SmpConfigureFileRenames回调函数。通过阅读SmpConfigureFileRenames的源码,发现此函数只是根据PendingFileRenameOperations中的值,初始化了SmpFileRenameList这个链表。因此,我们还需要看一下谁使用了SmpFileRenameList这个链表,通过对交叉引用的查找,得到如下调用路径:

这个SmpProcessFileRenames函数就是执行删除功能的函数:

可以看到,当判断为删除动作时,通过调用NtSetInformationFile并设置SetInfoClass为FileDispositionInformation即可删除文件。
而通过上图中对SmpProcessFileRenames函数的调用链路分析可知,SmpInit函数在smss.c源文件的main函数非常早期便执行了,此时大部分windows的子系统还没开始工作,自然也就不存在文件被占用无法删除的问题。
顺带一提, MoveFileEx函数支持一个MOVEFILE_DELAY_UNTIL_REBOOT的参数。这个参数也是通过本节提到的注册表项,在重启的过程中删除文件的:

注册表回调监控
搞明白病毒如何利用PendingFileRenameOperations获得持久化能力之后,接下来看几个病毒为了对抗查杀埋的几个点。
通过PCHunter工具查看“内核”标签页的“系统回调”选项卡, 发现一个标红的CmpCallback回调。

切换到“文件系统”选项卡, 在“微端口过滤器”中,对DIRECTORY_CONTROL有两个pre和post的hook。

有经验的朋友可能一看到这两处Hook就能猜到大概是干什么得了。对于不熟悉的朋友们, 此处是见证奇迹的时刻——摘掉这三处钩子,我们会“惊奇”的发现,服务中多了一项服务,system32目录下多出一个文件:


在没摘掉这两个钩子之前, 服务和文件是无法在PCHunter中看到的,因此我们目前有充分的理由相信,这三处钩子的作用是隐藏服务项和对应的文件。
可是,原理是什么?看来时候进行第二次下潜了。
首先我们要搞明白,CmpCallback是什么,使用双机内核调试,在上图中的CmpCallback函数地址(0x95DF64F0)上下断点,恢复运行后立刻中断,此时的栈回溯如下图(由于上文摘了回调,重启了一次,回调地址变为0x961D64F0,下文异同):

其实看到Cm开头的这几个函数,PCHunter中的CmpCallback表明的意义已经很明显了。不过我们还是要照顾一下不熟悉内核的朋友,毕竟本文的定位为硬核科普;)
由上图的栈回溯可以知道,最近的一次系统调用为CmpCallCallBacks,这个和PCHunter中的CmpCallback很像, 但是依然“不知”是干嘛用的,此时我们打开IDA,看一下这个函数的反汇编函数和交叉引用。
首先看一眼交叉引用:

似乎所有的注册表操作最终都会调用这个函数, 莫非CmpCallCallBacks是注册表监控用的?目前所有的线索都指向这个答案,但是我们还没有石锤。
接下来我们再看一下CmpCallCallBacks的反汇编代码吧,祭出IDA的F5大法:
(此代码加载了微软的公开符号,且关键数据结构经过调整)

上文中WinDBG中断后返回处为上图中55行处的v8->callback这行,纵观上面的代码,主要是在遍历v8这个双向链表中的callback函数,并依次调用(注册表回调嫌疑++)。
v8来自于CallbackListHead这个全局变量, 如果我们能找到谁在向这个全局链表添加数据,说不定我们挖掘出CmpCallCallBacks的作用,首先我们看一下有哪些函数在使用这个全局变量:

反正让我选,我会选CmpInsertCallbackInListByAltitude, 看下这个函数的调用链路,看下都有谁在调用他:

我看到了CmRegisterCallbackEx,然后是更上层的ETW。ETW我们挖个巨坑,后面有机会再讲,这里我们关注的是CmRegisterCallbackEx,下面是MSDN的解释:

好了,破案了,我们的猜测是正确的,这个玩意的确是用于监控注册表操作的。
挖到了关键的数据结构(CallbackListHead), 这里顺便看一下这个结构长什么样子:
- typedef struct _CM_CALLBACK_LIST_INFO {
- LIST_ENTRY list;
- #ifdef _WIN64
- UHALF_PTR counter;
- UHALF_PTR unknown;
- #else
- ULONG_PTR counter;
- PULONG_PTR unknown
- #endif
- LARGE_INTEGER cookie;
- PVOID context;
- PEX_CALLBACK_FUNCTION callback;
- UNICODE_STRING attidude;
- } CM_CALLBACK_LIST_INFO, *PCM_CALLBACK_LIST_INFO;
关键数据结构掌握了,自然可以遍历这个链表中所有的注册表回调函数。写个程序验证一下上述数据结构是不是有问题,作为教学目的硬编码了CallbackListHead的地址,这个地址从WinDBG中获得且每次重启后都不一样:

- #include
- typedef struct _CM_CALLBACK_LIST_INFO
- {
- LIST_ENTRY list;
- #ifdef _WIN64
- UHALF_PTR counter;
- UHALF_PTR unknown;
- #else
- ULONG_PTR counter;
- PULONG_PTR unknown;
- #endif
- LARGE_INTEGER cookie;
- ULONG_PTR context;
- ULONG_PTR callback;
- PUNICODE_STRING attidude;
- } CM_CALLBACK_LIST_INFO, *PCM_CALLBACK_LIST_INFO;
- NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ) {
- LIST_ENTRY *CallbackListHead = 0x83d5f570;
- LIST_ENTRY *i = CallbackListHead;
- DbgPrint("[IterateSys] start\n");
- DbgPrint("[IterateSys] i: %x\n", i);
- do {
- if (i) {
- DbgPrint("[IterateSys] address: %x\n", ((PCM_CALLBACK_LIST_INFO)i)->callback);
- i = i->Flink;
- } else DbgPrint("[IterateSys] i is NULL\n");
- } while(i->Flink != CallbackListHead->Blink);
- DbgPrint("[IterateSys] end\n");
- return STATUS_SUCCESS;
- }
再通过PCHunter中的数据交叉验证一下, 可知上面获得到的地址没有问题:

Windows函数调用路径与服务隐藏
上文中提到,CmpCallCallBacks函数可以监控所有的注册表操作。那么病毒是如何利用这个监控能力达成服务隐藏功能的呢?
要解释这个问题,我们需要先搞明白:如何获得当前系统的服务列表?emmm……这个我会,一般是像下面这段程序一样:
- #include
- #include
- int main() {
- HKEY hKey;
- LSTATUS status = RegOpenKeyEx(
- HKEY_LOCAL_MACHINE,
- "SYSTEM\\CurrentControlSet\\Services",
- 0,
- KEY_ALL_ACCESS,
- &hKey
- );
- printf("OpenKey status:%x\n", status);
- if(status == ERROR_SUCCESS) {
- printf("success\n");
- DWORD numSubKey = 0;
- RegQueryInfoKey(
- hKey,
- NULL, NULL, NULL,
- &numSubKey,
- NULL,NULL,NULL,NULL,NULL,NULL,NULL
- );
- printf("NumSubKey: %d\n", numSubKey);
- getch();
- for(int i = 0; i < numSubKey; ++i) {
- TCHAR subKey[MAX_PATH] = {0};
- DWORD sizeSubKey = MAX_PATH;
- status = RegEnumKey(
- hKey, i,
- subKey, &sizeSubKey
- );
- printf("Index:%d \t Status: %x \t SubKey: %s\n", i, status, subKey);
- }
- }
- }
可以看到是利用RegEnumKey函数遍历HKLM\SYSTEM\CurrentControlSet\Services注册表键值获取的当前系统的所有服务。
我们在R0下看一下这个行为的调用堆栈回溯:

可以看到,从R3开始到R0的调用路径是下图这个样子的:

由上图可知,R3的RegEnumKey会调用内核的NtEnumerateKey,而NtEnumerateKey又会通知CmpCallCallBacks函数查询结果。如果我们想要隐藏服务,只需要在被通知后修改NtEnumerateKey的返回数据不就可以了?
事实上紫狐家族正是利用这一点实现隐藏自己服务项的。首先病毒通过CmCallCallBacks函数Hook了RegNtPostEnumerateKey动作:

在Hook函数中, 通过重新调用ZwEnumerateKey函数跳过当前查询index,然后修改返回值。这样操作相当于跳过了某些服务,从而达到隐藏这些服务的目的:

如果我们在ntoskrnl.exe中查找EnumerateKey,会发现有4个函数:

通过梳理, 发现这四个函数的调用关系如下:
NtEnumearteKey ---> CmEnumerateKey
VfZwEnumerateKey ---> ZwEnumerateKey
而ZwEnumerateKey的函数体却比较“奇怪”:

这其实是大家熟知的SSDT表调用,此处涉及到KiServiceTable符号。上图中的74h即为ZwEnumerateKey调用的SSDT表中的服务编号。
我这边IDA中SSDT表的位置如下:

看一下74h号调用,首先计算地址:
0x45c5f4 + 74h * 4 = 0x45c7c4
IDA中转到这个地址看一下是哪个函数:

因此我们完善一下上面函数的调用链条:
VfZwEnumerateKey ---> ZwEnumerateKey -(SSDT)-> NtEnumearteKey ---> CmEnumerateKey
插曲
在逆向的过程中,通过在github上搜索病毒驱动分配内存函数ExAllocatePoolWithTag所使用的Tag,找到了这个驱动对应的开源代码:

通过对比IDA中的反汇编代码和github上的源码,发现逻辑一致,进一步确定紫狐使用的底层代码即github上此项目的代码(不知道病毒作者在不在这个项目的star列表里面/doge)。
文件隐藏
找到源码后,文件隐藏的步骤就比较清楚了:注册minifilter过滤器,过滤遍历文件的结果,如果发现是待隐藏文件,则返回下一个文件的信息。
病毒注册的过滤器信息如下, 下图左侧是命中过滤器时的堆栈调用,可以看到此时正在使用FindFirstFileW遍历文件。

隐藏文件逻辑对应的核心C代码如下:
- NTSTATUS CleanFileFullDirectoryInformation(PFILE_FULL_DIR_INFORMATION info, PFLT_FILE_NAME_INFORMATION fltName)
- {
- PFILE_FULL_DIR_INFORMATION nextInfo, prevInfo = NULL;
- UNICODE_STRING fileName;
- UINT32 offset, moveLength;
- BOOLEAN matched, search;
- NTSTATUS status = STATUS_SUCCESS;
- offset = 0;
- search = TRUE;
- do
- {
- fileName.Buffer = info->FileName;
- fileName.Length = (USHORT)info->FileNameLength;
- fileName.MaximumLength = (USHORT)info->FileNameLength;
- if (info->FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
- matched = CheckExcludeListDirFile(g_excludeDirectoryContext, &fltName->Name, &fileName);
- else
- matched = CheckExcludeListDirFile(g_excludeFileContext, &fltName->Name, &fileName);
- if (matched)
- {
- BOOLEAN retn = FALSE;
- if (prevInfo != NULL)
- {
- if (info->NextEntryOffset != 0)
- {
- prevInfo->NextEntryOffset += info->NextEntryOffset;
- offset = info->NextEntryOffset;
- }
- else
- {
- prevInfo->NextEntryOffset = 0;
- status = STATUS_SUCCESS;
- retn = TRUE;
- }
- RtlFillMemory(info, sizeof(FILE_FULL_DIR_INFORMATION), 0);
- }
- else
- {
- if (info->NextEntryOffset != 0)
- {
- nextInfo = (PFILE_FULL_DIR_INFORMATION)((PUCHAR)info + info->NextEntryOffset);
- moveLength = 0;
- while (nextInfo->NextEntryOffset != 0)
- {
- moveLength += nextInfo->NextEntryOffset;
- nextInfo = (PFILE_FULL_DIR_INFORMATION)((PUCHAR)nextInfo + nextInfo->NextEntryOffset);
- }
- moveLength += FIELD_OFFSET(FILE_FULL_DIR_INFORMATION, FileName) + nextInfo->FileNameLength;
- RtlMoveMemory(info, (PUCHAR)info + info->NextEntryOffset, moveLength);//continue
- }
- else
- {
- status = STATUS_NO_MORE_ENTRIES;
- retn = TRUE;
- }
- }
- LogTrace("Removed from query: %wZ\\%wZ", &fltName->Name, &fileName);
- if (retn)
- return status;
- info = (PFILE_FULL_DIR_INFORMATION)((PCHAR)info + offset);
- continue;
- }
- offset = info->NextEntryOffset;
- prevInfo = info;
- info = (PFILE_FULL_DIR_INFORMATION)((PCHAR)info + offset);
- if (offset == 0)
- search = FALSE;
- } while (search);
- return STATUS_SUCCESS;
- }
我们可以在PCHunter中看到病毒设置的过滤器地址为:0x88F8A3D8,同时提示模块不存在。这个地址,其实是病毒注册的回调函数第二个参数中某结构的地址:

从上图中可知该地址为_FLT_FILTER结构, 其实根据这个结构我们是可以得到模块名称的,这个算是一个PCHunter的改进点吧:

知道这个地址后,我们便可以调用函数取消掉这个回调从而去除病毒的文件保护:
- VOID FLTAPI FltUnregisterFilter(
- PFLT_FILTER Filter
- );
遍历过滤器
其实微软的minifilter本身提供了一系列函数让开发者有能力去遍历各个过滤器,这些函数由flgmgr.sys导出,作为我们手工遍历的研究对象非常的合适。
Windows内核中很多重要的数据结构,都以链表的形式放在全局变量中,当需要使用时只需要从全局变量中找到链表的开头,就可以遍历整个列表从而找到需要使用的对象。
看一下fltmgr.sys导出的函数,结合上文中PCHunter显示的是_FLT_FILTER结构可以“猜”到,FltEnumerateFilters是一个比较合适的入手点:

该函数从全局变量g_listFltpFrame_4中获得一个LIST_ENTRY结构,然后通过这个结构的Flink指针遍历整个_FLTP_FRMAE帧,最后将遍历完成的指针放到传入的FilterList数组中。不过这个地方有一个坑,我们先来看一下g_listFltpFrame_4这个全局变量是在哪赋值的:

通过研究赋值处的上下文可得知, v4的类型为_FLTP_FRAME *,而g_listFltpFrame_4被赋值成了这个数据结构Links成员域的地址、地址、地址(重要的事情说三遍),接下来通过windbg看一下此数据结构长什么样子:
kd> dt -b fltmgr!_FLTP_FRAME
+0x000 Type : _FLT_TYPE
+0x004 Links : _LIST_ENTRY
+0x000 Flink : Ptr32
+0x004 Blink : Ptr32
+0x00c FrameID : Uint4B
+0x010 AltitudeIntervalLow : _UNICODE_STRING
+0x018 AltitudeIntervalHigh : _UNICODE_STRING
+0x020 LargeIrpCtrlStackSize : UChar
+0x021 SmallIrpCtrlStackSize : UChar
+0x024 RegisteredFilters : _FLT_RESOURCE_LIST_HEAD
+0x000 rLock : _ERESOURCE
+0x038 rList : _LIST_ENTRY
…………省略…………
Flink成员在偏移+0x4的位置, 因此g_listFltpFrame_4需要减去4才是第一个_FLTP_FRAME结构的地址,而根据FltEnumerateFilters的上下文, 我们可以合理的推测_FLTP_FRAME+0x4处的双向链表存储的是_FLT_FILTER结构。这个trick在内核中有非常多的应用, 例如下图中从_FLT_FILTER获取_FLT_INSTANCE的代码:

可以看到IDA的Hexray反编译出来的伪C代码其实是有问题的,结合汇编代码可以看到真正的操作其实是对指针减去0x34大小, 那么0x34这个位置是什么呢?还是用数据结果来说话吧:
kd> dt -b fltmgr!_FLT_FILTER
+0x000 Base : _FLT_OBJECT
+0x000 Flags :
+0x004 PointerCount : Uint4B
+0x008 RundownRef : _EX_RUNDOWN_REF
+0x00c PrimaryLink : _LIST_ENTRY
+0x000 Flink : Ptr32
+0x004 Blink : Ptr32
+0x014 Frame : Ptr32
+0x018 Name : _UNICODE_STRING
+0x020 DefaultAltitude : _UNICODE_STRING
+0x028 Flags :
+0x02c DriverObject : Ptr32
+0x030 InstanceList : _FLT_RESOURCE_LIST_HEAD
+0x000 rLock : _ERESOURCE
+0x038 rList : _LIST_ENTRY
+0x000 Flink : Ptr32
+0x004 Blink : Ptr32
+0x040 rCount : Uint4B
首先从_FLT_FILTER->InstanceList->rList->Flink取出一个地址, 然后此地址减去0x34。InstanceList存的是啥?看名字,猜测是一个_FLT_INSTANCE *, 再看一下_FLT_INSTACNE结构:
kd> dt -b fltmgr!_FLT_INSTANCE
+0x000 Base : _FLT_OBJECT
+0x014 OperationRundownRef : Ptr32
+0x018 Volume : Ptr32
+0x01c Filter : Ptr32
+0x020 Flags :
+0x024 Altitude : _UNICODE_STRING
+0x02c Name : _UNICODE_STRING
+0x034 FilterLink : _LIST_ENTRY
+0x000 Flink : Ptr32
+0x004 Blink : Ptr32
…………省略…………
经过研究之后, 我们可以得出_FLTP_FRAME、_FLT_FILTER、_FLT_INSTANCE结构的关系如下图(注意,此结构在不同版本的系统上不同):

此处省略了逆向从_FLTP_FRAME->RegisteredFilters->rList->Flink到_FLT_FILTER->PrimaryLink->Flink的过程, 各位自己去看一下吧,此处只要记住需要减去0xC的偏移即可。
上文图中的负偏移看着太难受了, 介绍个IDA的小技巧把Hexray反编译后的伪代码弄得好看一点。上文负数组索引的图中,我们已经知道i变量是_FLT_INSTANCE类型了, 在此变量上点鼠标右键,选择Convert to struct *,然后找到_FLT_INSTANCE结构,在下方的Pointer shift value处填写我们逆向得出的偏移地址0x34, 如下图:

经过规整后, 同一个函数的伪C代码就易读多了:

可以看到IDA使用ADJ标识经过偏移的数据结构, 而本身i被标记为如下类型:
_LIST_ENTRY *__shifted(_FLT_INSTANCE, 0x34)
实际上就是CONTANING_RECORD宏, 这个宏大家可以看一下具体定义, 使用的技巧还是很多的。
都逆到这了, 必须写点代码奖励一下自己/doge。下面是遍历minifilter各个过滤器的代码, 由于涉及的数据结构太多, 我们将一些不使用的成员转换成了同等大小的指针数组(其实也可以引用ReactOS中对应的头文件):
- #include
- union u1
- {
- NTSTATUS (__stdcall *PreOperation)(int *, int *, void **);
- int (__stdcall *GenerateFileName)(int *, int *, int *, unsigned int, unsigned __int8 *, int *);
- int (__stdcall *NormalizeNameComponent) \
- (int *, const UNICODE_STRING *, unsigned __int16, const UNICODE_STRING *, int *, unsigned int, unsigned int, void **);
- int (__stdcall *NormalizeNameComponentEx) \
- (int *, int *, const UNICODE_STRING *, unsigned __int16, const UNICODE_STRING *, int *, unsigned int, unsigned int, void **);
- };
- union u2
- {
- NTSTATUS (__stdcall *PostOperation)(int *, int *, void *, unsigned int);
- void (__stdcall *NormalizeContextCleanup)(void **);
- };
- struct _FLT_RESOURCE_LIST_HEAD
- {
- unsigned int *rLock[14];
- LIST_ENTRY rList;
- unsigned int rCount;
- };
- struct _CALLBACK_NODE
- {
- LIST_ENTRY CallbackLinks;
- struct _FLT_INSTANCE *Instance;
- union u1 *_u1;
- union u2 *_u2;
- int *Flags;
- };
- struct _FLT_INSTANCE
- {
- unsigned int *Base[0x5];
- int *OperationRundownRef;
- int *Volume;
- struct _FLT_FILTER *Filter;
- int *Flags;
- UNICODE_STRING Altitude;
- UNICODE_STRING Name;
- LIST_ENTRY FilterLink;
- int *ContextLock;
- int *Context;
- int *TransactionContexts;
- int *TrackCompletionNodes;
- struct _CALLBACK_NODE *CallbackNodes[50];
- };
- struct _FLTP_FRAME
- {
- unsigned int* usless1;
- LIST_ENTRY Links;
- unsigned int FrameID;
- UNICODE_STRING AltitudeIntervalLow;
- UNICODE_STRING AltitudeIntervalHigh;
- unsigned __int8 LargeIrpCtrlStackSize;
- unsigned __int8 SmallIrpCtrlStackSize;
- struct _FLT_RESOURCE_LIST_HEAD RegisteredFilters;
- struct _FLT_RESOURCE_LIST_HEAD AttachedVolumes;
- LIST_ENTRY MountingVolumes;
- unsigned int *AttachedFileSystems[11];
- unsigned int *ZombiedFltObjectContexts[11];
- unsigned int *KtmResourceManagerHandle;
- struct _KRESOURCEMANAGER *KtmResourceManager;
- unsigned int *FilterUnloadLock[14];
- FAST_MUTEX DeviceObjectAttachLock;
- unsigned int *Prcb;
- unsigned int *PrcbPoolToFree;
- unsigned int *LookasidePoolToFree;
- unsigned int *IrpCtrlStackProfiler[32];
- __declspec(align(16)) unsigned int *SmallIrpCtrlLookasideList[20];
- __declspec(align(64)) unsigned int *LargeIrpCtrlLookasideList[20];
- unsigned int *BackpocketIrpCtrls[21];
- };
- typedef struct _FLT_FILTER
- {
- unsigned int *Base[0x5];
- struct _FLTP_FRAME *Frame;
- UNICODE_STRING Name;
- UNICODE_STRING DefaultAltitude;
- unsigned int *Flags;
- DRIVER_OBJECT *DriverObject;
- struct _FLT_RESOURCE_LIST_HEAD InstanceList;
- unsigned int *VerifierExtension;
- LIST_ENTRY VerifiedFiltersLink;
- int (__stdcall *FilterUnload)(unsigned int);
- int (__stdcall *InstanceSetup)(const unsigned int *, unsigned int, unsigned int, unsigned int *);
- int (__stdcall *InstanceQueryTeardown)(const unsigned int *, unsigned int);
- unsigned int (__stdcall *InstanceTeardownStart)(const unsigned int *, unsigned int);
- unsigned int (__stdcall *InstanceTeardownComplete)(const unsigned int *, unsigned int);
- unsigned int *SupportedContextsListHead;
- unsigned int *SupportedContexts[6];
- NTSTATUS (__stdcall *PreVolumeMount)(unsigned int *, const unsigned int *, unsigned int **);
- NTSTATUS (__stdcall *PostVolumeMount)(unsigned int *, const unsigned int *, unsigned int *, unsigned int);
- int (__stdcall *GenerateFileName)(unsigned int *, unsigned int *, unsigned int *, unsigned int, unsigned __int8 *, unsigned int *);
- int (__stdcall *NormalizeNameComponent) \
- (unsigned int *, const UNICODE_STRING *, unsigned __int16, const UNICODE_STRING *, unsigned int *, unsigned int, unsigned int, unsigned int **);
- int (__stdcall *NormalizeNameComponentEx) \
- (unsigned int *, unsigned int *, UNICODE_STRING *, unsigned __int16, UNICODE_STRING *, unsigned int *, unsigned int, unsigned int, unsigned int **);
- unsigned int (__stdcall *NormalizeContextCleanup)(unsigned int **);
- int (__stdcall *KtmNotification)(const unsigned int *, unsigned int *, unsigned int);
- unsigned int *Operations;
- unsigned int (__stdcall *OldDriverUnload)(DRIVER_OBJECT *);
- unsigned int *ActiveOpens[11];
- unsigned int *ConnectionList[11];
- unsigned int *PortList[11];
- unsigned int *PortLock;
- }*PFLT_FILTER, FLT_FILTER;
- NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ) {
- unsigned int *g_filterFrame = (unsigned int *)0x8db819f8;
- struct _FLTP_FRAME *frame = (struct _FLTP_FRAME*)((char *)(*g_filterFrame) - 0x4);
- for(LIST_ENTRY *i = frame->RegisteredFilters.rList.Flink; i->Flink != frame->RegisteredFilters.rList.Flink; i = i->Flink) {
- struct _FLT_FILTER *filter = (struct _FLT_FILTER*)((char*)(i) - 0xC);
- for(LIST_ENTRY *j = filter->InstanceList.rList.Flink; j->Flink != filter->InstanceList.rList.Flink; j = j->Flink) {
- struct _FLT_INSTANCE *instance = (struct _FLT_INSTANCE *)((char*)j - 0x34);
- for(int index = 0; index < 50; index += 1) {
- if(instance->CallbackNodes != 0 && instance->CallbackNodes[index] != 0) {
- DbgPrint("[IterateFilters] filter:%x\tname:%wZ\tinstance:%x\tindex:%d\tpre_cb:%x\tpost_cb:%x\n",
- filter, &filter->Name, instance, index,
- instance->CallbackNodes[index]->_u1, instance->CallbackNodes[index]->_u2);
- }
- }
- }
- }
- return STATUS_SUCCESS;
- }
最后和PCHunter中的数据对比一下, 发现一致:

OK, 打完收工!
后记
似乎没什么好说的,只是为了求各位观众老爷转发强行后记一下……;)