查看: 471|回复: 0
收起左侧

[技术探讨] 【本人手动渣翻系列·其六】MDAV WdFilter分析(二)

[复制链接]
ANY.LNK
发表于 2024-11-9 20:37:25 | 显示全部楼层 |阅读模式
本帖最后由 ANY.LNK 于 2024-11-9 20:37 编辑

(续上篇)

在本节中,我们主要关注:

·映像加载回调
·线程创建回调
·发送同步/异步通知

免责声明:此节中的回调主要依赖于ProcessCtx.ProcessRules,虽然我已尝试不同的进程类型(包括恶意软件),但我仍不能确定每条规则对应的进程类型(或许同Defender配置有关?)我对没有此信息的情况表示深深的抱歉,希望在深入研究驱动时能发现更多关于不同规则的信息。为了测试,我强制了代码执行不同的路径



MpCreateThreadNotifyRoutineEx - MpCreateThreadNotifyRoutine

首先的两个回调是MpCreateThreadNotifyRoutine和MpCreateThreadNotifyRoutineEx,任意线程被创建或删除时它们都会被通知。有两个不同的线程因为前者由PsSetCreateThreadNotifyRoutine注册而后者由PsSetCreateThreadNotifyRoutineEx注册。后者自Windows 10开始可用,对其的指针存储于MpData。当然若指针为NULL则回调就不会注册。

正如PsSetCreateThreadNotifyRoutineEx的文档所述,二函数在执行回调的上下文不同,正如微软文档所言,PsSetCreateThreadNotifyRoutine中回调在父线程上运行,PsSetCreateThreadNotifyRoutineEx中回调在子线程上运行

MpCreateThreadNotifyRoutine

此二回调的代码差异可能比你认为的还要大,所以我们两个都要分析。从MpCreateThreadNotifyRoutine开始——注意此回调在父线程上下文中执行——它将在线程执行前检查如下三点:

·创建参数设置为TRUE
·PID和0x4(System)不同
·当前线程不是系统线程(!PsIsSystemThread)

若符合上述条件,代码将设置标志指示当前进程是否与参数PID中的进程相同。

若相同,则依据规则NotifyNewThreadSameProcess(0x10000000)测试当前进程ProcessCtx.ProcessRules并据此设置标志。若不同,将依据规则NotifyNewThreadDifferentProcess(0x400000)测试ProcessRules并设置另一个规则。若没有标志被设置,回调将返回。


若其中之一标志设置,代码将继续获取被我称之为AuxPidCreationTime的结构——我们曾在文章的第一部分见过,但余下的这部分包含了PID和创建时间——当两个进程都获取完成后(即使是同一进程,也会获取两次),代码将调用MpGetPriorityInfo,此函数主要调用FltRetrieveIoPriorityInfo获取当前线程的IO_PRIORITY_INFO并填充如下我命名为MP_IO_PRIORITY的结构:


不同的消息将依据被设置的标志发送到MsMpEng。在NotifyNewThreadDifferentProcess时,将以OperationType等于NewThreadDifferentProcess(0x3)调用MpSendSyncMonitorNotification,Data为源自将在后续执行线程的进程的AuxPidCreationTime。



当线程在同一进程中创建时,数据将在发送到MpSendSyncMonitorNotification之前初始化,函数MpCreatePsThreadSyncMonitorData用于完成此任务。此函数填充如下结构:



为获取ThreadStartAddress它将获取线程的句柄(PsLookupThreadByThreadId),并使用此句柄以ThreadQuerySetWin32StartAddress类为参数调用ZwQueryInformationThread。一旦ThreadNotifySyncMessage被填充,将在Data(数据)和OperationType(操作类型)等于NewThreadSameProcess(0x6)时以此结构为参数调用MpSendSyncMonitorNotification



最后若设置了NotifyNewThreadDifferentProcess回调将执行最后一步。包括发送以下数据的异步通知。



各字段的含义不言自明,就ImageFileName而言它将从ProcessCtx中获取——此时ProcessCtx对应的是线程创建进程的ProcessCtx,可能同线程将要运行的不同



MpCreateThreadNotifyRoutineEx
此例程比先前那个简单不少,此函数运行在新线程上,基本意味着当前进程总是匹配参数ProcessID指示的ID。首先,实际发送通知需要满足许多条件:

·MpProcessTable->CreateThreadNotifyLock设定值与0不同
·创建参数设置为TRUE
·当前进程不是PsInitialSystemProcess
·ProcessCtx.ProcessFlags中设置了标志ThreadNotifyRoutineExSet(0x400)
·ProcessCtx.ProcessRules中设置了规则NotifyProcessCmdLine(0x20000000)



这里有几处说明,若MP_DATA中指向PsSetCreateThreadNotifyRoutineEx的指针不为NULL,则会为每个ProcessCtx设置标志ThreadNotifyRoutineExSet



规则NotifyProcessCmdLine,源自设置进程信息时的MsMpEng——再一次,我没有在任何进程中触发此规则,因而不知道该规则适用于哪些进程——若此规则设立MpProcessTable->CreateThreadNotifyLock值将会递增:



回到实际函数,若符合上述所有条件,首先是递减CreateThreadNotifyLock并从ProcessCtx中移除ThreadNotifyRoutineExSet;完成后,将获取进程对象的句柄(ObOpenObjectByPointer,ObjectType为PsProcessType),此句柄将被用于获取进程命令行(Process CommandLine),使用MpGetProcessCommandLineByHandle,此函数主要调用ZwQueryInformationProcess将ProcessInformationClass设置为ProcessCommandLineInformation。获取到的命令行将同ProcessCtx->ProcessCmdLine对比,若不匹配,函数将获取MP_IO_PRIORITY,AuxPidCreationTime并使用两个命令行作为数据Data调用MpSendSyncMonitorNotification。



正如图片所示,若有人用PEB篡改了命令行,此回调将通知MsMpEng命令行篡改(当然,在它的ProcessCtx规则和标志设置的情况下)

MpLoadImageNotifyRoutine

此回调例程会在映像加载或导入内存的时候被触发。驱动使用PsSetLoadImageNotifyRoutine函数注册回调。

深入回调的实际代码,首先检查通过检查IMAGE_INFO中的比特Properties.SystemModeImage判断映像将被导入用户或还是内核空间。若这是内核模式组件,映像信息将被添加至DRIVER_INFO结构并链入已加载驱动列表条目——类似于将启动进程添加到启动进程列表的创建过程——由MpAddDriverInfo完成。

检查完成后,将获取ProcessCtx并检查ProcessCtx->ProcessRules中NotifyWow64cpuLoad(0x800)是否设置。若设置,函数将按字节继续比对FullImageName是否和字符串\Windows\System32\Wow64cpu.dll匹配。若匹配,则用ImageWow64cpuLoaded(0x200)OR运算出ProcessCtx->ProcessFlags,同时,ImageBase将被写入ProcessCtx->Wow64CpuImageBase。如果你还记得文章的第一部分中的ImageBase,我再次检查了此区域,确认了整个代码只在此设置了字段,这就是我重命名它的原因。



从这里开始例程的主要功能。这段代码首先检查IMAGE_INFO是否设置了比特ExtendedInfoPresent,若设置则IMAGE_INFO包含在IMAGE_INFO_EX内。而IMAGE_INFO_EX又保存了指向FileObject的指针,该指针将用于获取一个StreamContext(MpGetStreamContextFromFileObject)——基本上是个由minifilter定义的,与流对象(Stream Objects)关联的结构,我们将在过滤器如何工作的时候对此进行更多讨论。

通过StreamCtx和ProcessCtx,将进行如此下检查:

·若StreamCtx->StreamCtxRules有激活的NotifyImageLoadRule(0x8000),设置NotifyImageLoadPerStreamFlag标志
·若ProcessCtx->ProcessRules有激活的NotifyImageLoadRule(0x8000000),设置NotifyImageLoadPerProcessFlag标志
·若ProcessCtx->ProcessRules有激活的0x200(尚未明白此值功能),若未设置则AsyncNotificationFlag被激活

若AsyncNotificationFlag已设置函数将创建AsyncMessageData结构,其中并集TypeOfMessage采用ImageLoadAndProcessNotify结构,在先前的文章中我们已经见过,最主要的不同在于AsyncMessageData->TypeOfOperation被设置为LoadImage(0x3)。最后通知将通过调用MpAsyncSendNotification发送。



在其余两种情况,通知将同步发送,发送的数据也相同。唯一不同的是操作类型OperationType和规则Rule——我们很快会在研究同步信息如何发送时讨论这个参数。

·NotifyImageLoadPerProcessFlag -> OperationType = NewImageLoadPerProcess (0x5) 且 Rule = ProcessCtx->ProcessRules
·NotifyImageLoadPerStreamFlag -> OperationType = NewImageLoadPerStream (0x1) 且 Rule = StreamCtx->StreamCtxRules

最终函数MpSendSyncMonitorNotification将被调用,其中参数Data将以Unicode字符串(UNICODE_STRING)的形式传入,包含已加载映像的规范化名称FullImageName



当ProcessCtx->ProcessFlags设置了ImageWow64cpuLoaded时,情况会有些许不同,会分配大小为0x30的AsyncMessageData结构,且TypeOfMessage会包含如下结构:


最后在AsyncMessageData填充的情况下例程将调用FltSendMessage——有趣的是,AsyncMessageData->SizeOfData被设置为0x70,而实际此结构的大小为0x30,甚至FltSendMessage的参数SenderBufferLength也被设置为0x30。这可能会在MsMpEng使用AsyncMessageData->SizeOfData时造成可能的错误。



所以解释一点流程,一旦Wow64cpu.dll加载,回调将在ProcessCtx->ProcessFlags设置ImageWow64cpuLoaded并继续执行主路径。下次此进程加载映像时,由于ImageWow64cpuLoaded已设置,代码将继续在执行主路径前执行此路径

同步通知



MpSendSyncMonitorNotification负责通过MicrosoftMalwareProtectionPort发送同步信息。为执行此函数,必须设置MP_DATA中的SyncMonitorNotificationFlag标志。完成后,代码将检查OperationType是否在MP_SYNC_NOTIFICATION枚举的范围内,且其他参数为NULL。

若每项检查均通过,代码将继续获取参数Data的大小——正如文中所示,此参数中提供的数据因操作类型而异——执行此功能代码使用的函数是MpConstructSyncMonitorVariableData。



此函数有两种使用方法:

·获取要发送的数据大小(DataToSend == NULL)
·使用参数Data中的数据填充要发送的缓冲区

第一种情况,代码查找:



回到主函数,首次调用MpConstructSyncMonitorVariableData后,代码将获得要发送的数据的大小,此大小将被添加到报文头(0x30)的大小中,并依据相加后的大小分配并填充池。报文头有如下定义



最后发送数据前,必须将变量数据复制到SyncMessageData结构中。MpConstructSyncMonitorVariableData被再次调用但这次参数DataToSend指向移位0x30的结构SyncMessageData(指向变量数据),此时函数只会将数据从缓冲区Data复制到缓冲区DataToSend。如果缓冲区是UNICODE_STRING,将用memcpy_s复制UNICODE_STRING.Buffer。

此时,所有数据都准备好发送到MsMpEng了,只需要最后一步检查。在MpAcquireSendingSyncMonitorNotification中将检查MpData->SendSyncNotificationFlag是否为激活状态,然后使用FltCancellableWaitForSingleObject等待MpData->SendingSyncSemaphore——等待的超时时间源自变量MpData->SyncMonitorNotificationTimeout——如果等待返回STATUS_SUCCESS之外的任何状态,则主函数不会发送任何报文,并递增设置以下变量:

·MpData->ErrorSyncNotificationsCount[OperationType]
·MpData->ErrorSyncNotificationsStatus[OperationType]

若等待成功,将调用FltSendMessage并基于返回状态填充不同变量。第一个变量是个保存了通知计数器和每个操作类型(OperationType)的时间戳的结构。这个结构组可以在变量MpData->SyncNotifications[OperationType]中找到,定义如下:



若FltSendMessage返回错误,下列变量将被更新:

·MpData->ErrorSyncNotificationsCount[OperationType]
·MpData->ErrorSyncNotificationsStatus[OperationType]
·MpData->SyncNotificationsIoTimeoutCount[OperationType](仅在FltSendMessage返回的状态为STATUS_TIMEOUT增加)

若FltSendMessage返回STATUS_SUCCESS函数将继续检查响应缓冲区。缓冲区应包含同样的偏移量为0x8的OperationType,操作成功完成后,它将重置触发此特定通知的ProcessCtx->ProcessRules或StreamCtx->StreamCtxRule——使用参数Rule——可在下图中看到:



最后一步还有两个变量MpData->SyncNotificationRecvCount[OperationType]和MpData->SyncNotificationsRecvErrorCount[OperationType,后者在ReplyBuffer检查不匹配时增加,前者为另一种情况

异步通知

此节我将解释此驱动句柄是如何发送异步通知的。有两个函数负责此事。MpAsyncSendNotification负责添加报文消息到异步消息的尾部,MpAsyncpWorkerThread是检查异步消息尾部并在存在消息的时候发送的工作线程。

MpAsyncpWorkerThread

在本系列的第一部分我们已经提到了这个工作线程,见证了它和异步结构在MpAsyncInitialize中初始化。此函数使用PsCreateSystemThread创建工作线程,以StartRoutine设立MpAsyncpWorkerThread——没有向新线程传递StartContext。
线程主要以MP_ASYNC结构工作,拥有如下结构



一旦工作线程开始执行,它将进入一个无限循环等待两个同步对象,MpAsync->AsyncSemaphore和MpAsync->AsyncNotificationEvent,使用KeWaitForMultipleObjects



我们可以看到,WaitType被设置为WaitAny,这意味着它会等待直到任何一个对象达到发出信号的状态。且使用WaitAny还意味着如果函数返回STATUS_SUCCESS,它将实际返回对象的零级索引作为NTSTATUS。考虑到这一点,由于事件被设置为对象数组的第一个元素,因此每当事件发出信号时返回值将为STATUS_WAIT_0,对应0x0。如图所示,这会使for循环的条件变为false,进而调用PsTerminateSystemThread终止线程结束循环


若信号量为信号对象,线程将继续获取必须发送到MsMpEng的数据。首先MpConfig.AsyncStarvationLimit将和全局变量AsyncStarvationLimit对比——若相同则AsyncStarvationLimit将被设置为0x0——若不匹配,则在MpAsync->HighPriorityNotificationsList搜索数据。若在LIST_ENTRY中找到任意条目,则AsyncStarvationLimit加1。若没有发现条目,将继续检查MpAsync->NotificationsList,若发现条目,清空AsyncStarvationLimit。若达到饥饿限制,则按相反的顺序检查条目,先检查正常优先级,再检查高优先级。



如你所见,源自MpAsync->HighPriorityNotificationsList拥有更高的优先级因为该LIST_ENTRY在除非饥饿限制达到时都将被首先检查。

下一部分很简单,如果在两个列表中的任意列表中找到条目,则会执行以下步骤:

·减少MpAsync->NotificationsCount
·从MpAsync->TotalSizeRemainingNotifications减去数据大小
·设置MP_ASYNC_NOTIFICATION的Magic和Size为0xBABAFAFA
·压入栈或释放MP_ASYNC_NOTIFICATION条目到MpAsync->AsyncNotificationsLookaside,若达到最大深度。
·用FltSendMessage发送实际消息
·错误时增加MpAsync->FailedNotifications
·添加数据大小到MpAsync->TotalSizeNotificationsSent



之后线程恢复遍历for循环,直到两个对象中的任何一个发出信号。

为完成工作线程的整个循环,函数MpAsyncpShutdownWorkerThreads用MpAsync->AsyncNotificationEvent作为事件信号调用KeSetEvent,正如我们之前看到的那样,这会结束循环并终止线程。该函数在MpAsyncShutdown中调用,而它负责清理所有与异步通知相关的内容


MpAsyncSendNotification



我们已经看到过几种代码会创建AsyncMessageData结构的情况并在其中填充稍后要发送到MsMpEng的数据。我们只是看到了数据是怎样发送到,现在我们将看看这些数据是如何添加到之前看到的列表条目中的。

负责此功能的函数是MpAsyncSendNotification。它首先会对AsyncMessageBuffer和SizeOfBuffer进行正确性检查。若检查通过,函数将测试SenderBuffer->TypeOfOperation是否小于0xA,再度符合后,MpData->AsyncNotificationCount值将会增加并分配给SenderBuffer->NotificationNumber。TypeOfOperation的可能值如下:



下一步是增加ProcessCtx->NotificationsSent——如果有ProcessCtx的话——完成后MpAsync->AsyncNotificationsLookaside中的一个条目将被出栈或分配,下面的结构将在分配出的缓冲区初始化:



一旦该结构被初始化,就会出现两种可能的执行路径:
其一是MpAsync->NotificationsCount小于MpConfig.MaxAsyncNotificationCount的情况下。此时初始化的结构将被插入MpAsync->HighPriorityNotificationsList的尾部或基于PriorityFlag插入MpAsync->NotificationsList尾部——若已设置则接下来它将链入前者,其他情况链入后者——接下来MpAsync->NotificationsCount增加,MessageBufferSize添加到MpAsync->TotalSizeRemainingNotifications最后发出信号量——KeReleaseSemaphore



其二是当MpAsync->NotificationsCount大于等于MpConfig.MaxAsyncNotificationCount。这种情况下MpAsync->HighPriorityNotificationsList 或 MpAsync->NotificationsList (同样基于PriorityFlag)的首个条目将从LIST_ENTRY中解链,而新创建的条目将被插入到尾部。

完成前,正如我们在工作线程中看到的,函数将增加MpAsync->AsyncMessagesFailed并压入/释放解链的条目到/从查找列表(将首字节设置为0xBABAFAFA后)

这是为了保证在从查找列表中分配池时有足够的资源,此时必须创建新通知;同时这也确保了新通知保存在LIST_ENTRY中,此时工作线程没有足够的执行时间释放通知列表


总结

这部分有点乱,因为目前哪些选项或过程触发的某些路径尚不明确,这让事情变得困难。这仍然还只是WdFilter的冰山一角,在下一篇中,我将继续介绍注册的对象回调PsProcessType和ExDesktopObjectType,还将介绍驱动信息是如何保存的、它们的验证是怎么完成的。




这是WdFilter的第二篇,由于最近工作或学业的问题,翻译更新可能延迟,感谢谅解

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?快速注册

x

评分

参与人数 2魅力 +2 人气 +3 收起 理由
驭龙 + 3 版区有你更精彩: )
白露为霜 + 2 版区有你更精彩: )

查看全部评分

您需要登录后才可以回帖 登录 | 快速注册

本版积分规则

手机版|杀毒软件|软件论坛| 卡饭论坛

Copyright © KaFan  KaFan.cn All Rights Reserved.

Powered by Discuz! X3.4( 沪ICP备2020031077号-2 ) GMT+8, 2024-11-21 17:59 , Processed in 0.131670 second(s), 19 queries .

卡饭网所发布的一切软件、样本、工具、文章等仅限用于学习和研究,不得将上述内容用于商业或者其他非法用途,否则产生的一切后果自负,本站信息来自网络,版权争议问题与本站无关,您必须在下载后的24小时之内从您的电脑中彻底删除上述信息,如有问题请通过邮件与我们联系。

快速回复 客服 返回顶部 返回列表