菠菜网

电银付安装教程(dianyinzhifu.com):剖析Windows Defender驱动程序:WdFilter(Part 3)

网址简介:未填写

更新时间:4个月前

访问次数:51

详细介绍

剖析Windows Defender驱动程序:WdFilter(Part 1)

剖析Windows Defender驱动程序:WdFilter(Part 2)

前两篇文章,我们先容了回调中用到的主要函数。本文,我们会接着先容回调的详细历程和方式。

MpLoadImageNotifyRoutine

mploadimagenotify例程是一个回调例程,每当一个映像被加载或映射到内存时,它都会被触发。为了注册这个回调,驱动程序使用函数PsSetLoadImageNotifyRoutine。

进入现实的回调代码,首先要检查要加载的映像是否要映射到用户空间或内核空间,并检查IMAGE_INFO中的Properties.SystemModeImage位。若是它是内核模式组件,则映像信息将被添加到一个DRIVER_INFO结构中,然后链接到“加载的驱动程序”列表条目中,这类似于将启动历程添加到启动历程列表的历程建立,该历程是在MpAddDriverInfo内部完成。

完成此检查后,将获取ProcessCtx,并检查ProcessCtx-> ProcessRules以查看是否设置了NotifyWow64cpuLoad(0x800)。若是设置了规则,则函数将继续将FullImageName字节与字符串\Windows\System32\Wow64cpu.dll举行对照。若是它们匹配,则ProcessCtx-> ProcessFlags将与ImageWow64cpuLoaded(0x200)举行或运算,并将ImageBase写入ProcessCtx-> Wow64CpuImageBase,如上所述,则将该字段命名为ImageBase,而且仅在整个代码中在此设置此字段,这就是我重命名它的缘故原由。

此时,纵然ImageName不匹配或未设置NotifyWow64cpuLoad,也体现了其主要功效,这部门代码将首先检查IMAGE_INFO是否具有若是将ExtendedInfoPresent位置1,则将IMAGE_INFO包罗在IMAGE_INFO_EX中,该IMAGE_INFO_EX保留指向FileObject的指针,该指针将用于检索StreamContext(MpGetStreamContextFromFileObject),这基本上是由与Stream工具关联的微型过滤器界说的结构,关于过滤的事情方式,我们将举行更多讨论–使用StreamCtx和ProcessCtx举行以下检查:

1. 若是StreamCtx->StreamCtxRules已激活了NotifyImageLoadRule (0x8000),则设置NotifyImageLoadPerStreamFlag。

2. 若是ProcessCtx->ProcessRules已经激活了NotifyImageLoadRule (0x8000000),那么就设置了NotifyImageLoadPerProcessFlag。

3. 若是ProcessCtx-> ProcessRules的流动状态为0x200(尚未计算出该值),若是未设置,则激活AsyncNotificationFlag。

若是设置了AsyncNotificationFlag,则该函数将建立一个AsyncMessageData结构,其中团结TypeOfMessage将接纳ImageLoadAndProcessNotify结构,我们已经在上一篇文章中看到了此结构,主要区别在于AsyncMessageData-> TypeOfOperation将被设置为LoadImage(0x3)。最后,将通过挪用MpAsyncSendNotification发送通知。

对于其他两种情形,通知将同步发送,而且两种情形下发送的数据将相同。唯一差别的是OperationType和Rule,我们将在研究同步新闻的发送方式时讨论此参数:

1. NotifyImageLoadPerProcessFlag-> OperationType = NewImageLoadPerProcess(0x5)和Rule = ProcessCtx-> ProcessRules;

2. NotifyImageLoadPerStreamFlag-> OperationType = NewImageLoadPerStream(0x1)和Rule = StreamCtx-> StreamCtxRules。

最后,使用参数Data作为UNICODE_STRING挪用函数MpSendSyncMonitorNotification,并使用已加载映像的标准化名称FullImageName。

当在ProcessCtx->ProcessFlags上设置ImageWow64cpuLoaded时,代码流有一点差别。若是发生这种情形,则分配巨细为0x30的AsyncMessageData结构,而且TypeOfMessage将包罗以下结构:

typedef struct _Wow64CpuLoadMessage
{
  INT ProcessId;
  INT ThreadId;
  PVOID Wow64CpuImageBase;
} Wow64CpuLoadMessage, *PWow64CpuLoadMessage;

最后,使用AsyncMessageData填充例程,该例程将挪用FltSendMessage,有趣的是,当结构的现实巨细为0x30时,纵然将FltSendMessage的参数SenderBufferLength设置为0x30,AsyncMessageData-> SizeOfData也会设置为0x70,这可能会导致某些潜在的错误MsMpEng使用AsyncMessageData-> SizeOfData。

因此,稍微解释一下该流程,一旦Wow64cpu.dll被加载,此回调将在ProcessCtx-> ProcessFlags中设置ImageWow64cpuLoaded,并将继续通过主路径执行。下次此历程加载映像时,由于先前已设置ImageWow64cpuLoaded,因此代码将在接纳主路径之前遵照此路径。

同步通知

NTSTATUS MpSendSyncMonitorNotification(
    MP_SYNC_NOTIFICATION OperationType, 
    PAuxPidCreationTime ProcessIdAndCreationTime, 
    PVOID Data, 
    PMP_IO_PRIORITY MpIoPriority,
    PULONG Rule
);

typedef enum _MP_SYNC_NOTIFICATION_OPERATION 
{
  NewImageLoadPerStream = 0x1,
  RegistryEventSync = 0x2,
  NewThreadDifferentProcess = 0x3,
  NewImageLoadPerProcess = 0x5,
  NewThreadSameProcess = 0x6,
  NewThreadProcessCmdLine = 0x7,
} MP_SYNC_NOTIFICATION_OPERATION;

MpSendSyncMonitorNotification是卖力通过MicrosoftMalwareProtectionPort发送同步新闻的人,为了使此函数通过MP_DATA执行标志SyncMonitorNotificationFlag,必须举行设置。完成此检查后,代码将检查OperationType是否在MP_SYNC_NOTIFICATION枚举的范围内,还将检查有没有其他任何参数是NULL。

若是完成所有检查,代码将继续获取参数数据的巨细,如上所述,每种操作类型在此参数中提供的数据都差别。为此,代码使用函数MpConstructSyncMonitorVariableData。

ULONG MpConstructSyncMonitorVariableData(
  INT OperationType, 
  PVOID Data, 
  PVOID *__shifted(SyncMessageData,0x30) DataToSend, 
  ULONG SizeOfData
)

这个函数有两种使用方式:

1. 获取要发送的数据巨细(DataToSend == NULL);

2. 使用参数Data中的数据填充要发送的缓冲区。

在第一种情形下,伪代码看起来如下所示:

if (!DataToSend) {
  switch (OperationType) {
    case NewImageLoadPerStream:
    case NewImageLoadPerProcess:
    case NewThreadAndCmdLine:
      return (UNICODE_STRING *) Data->Length + 0xA;
    case RegistryEventSync:
      return (RegistryNotifySyncMessage) Data->RegDataLength;
    case NewThreadDifferentProcess:
      return sizeof(AuxPidCreationTime);
    case NewThreadSameProcess:
      return sizeof(ThreadNotifySyncMessage);
  }
}

回到主函数,第一次挪用MpConstructSyncMonitorVariableData之后,代码将获取要发送的数据的巨细,此巨细将添加到新闻头的巨细(0x30)中,而且整个巨细将是一个池,被分配并响应地填充。新闻头具有以下界说:

typedef struct _SyncMessageData
{
  SHORT Magic;      // Set to 0x5D 
  SHORT SizeHeader; // Sizeof 0x30 
  ULONG TotalSize;
  MP_IO_PRIORITY MpIoPriority;
  INT TypeOfOperation;
  AuxPidCreationTime CurrentProcess;
  INT SizeOfData;
  union SyncVariableData {
    WCHAR * NewThreadAndCmdLine;
    WCHAR * NewImageLoadPerStream;
    WCHAR * NewImageLoadPerProcess;
    RegistryNotifySyncMessage RegistryEventSync;
    AuxPidCreationTime NewThreadDifferentProcess;
    ThreadNotifySyncMessage NewThreadSameProcess;
  };
} SyncMessageData, *PSyncMessageData;

最后,在发送新闻之前,必须将变量数据复制到SyncMessageData结构中,以再次执行此操作MpConstructSyncMonitorVariableData,但这一次参数DataToSend指向结构SyncMessageData偏移了0x30(指向变量数据),在这种情形下该函数只会将数据从缓冲区数据复制到缓冲区DataToSend –若是缓冲区数据是UNICODE_STRING,则UNICODE_STRING。将使用memcpy_s复制缓冲区。

至此,一切准备就绪,可以将数据发送到MsMpEng了,只需在MpAcquireSendingSyncMonitorNotification内部执行另一项检查,该检查将基本上检查MpData-> SendSyncNotificationFlag是否处于流动状态,今后该函数将使用FltCancellableWaitForSingleObject守候MpData-> SendingSyncSemaphore,用于此守候的超时来自变量MpData-> SyncMonitorNotificationTimeout,若是守候返回除STATUS_SUCCESS以外的任何值,则主函数将不发送任何新闻,而且将递增并响应地设置以下两个变量:

1. MpData-> ErrorSyncNotificationsCount [OperationType];

2. MpData-> ErrorSyncNotificationsStatus [OperationType]。

若是守候乐成,则将挪用FltSendMessage,并凭据返回的状态填充差别的变量。第一个变量是一个结构,用于保留通知及其总时间戳(针对每个OperationType)的计数器。结构数组可以在变量MpData-> SyncNotifications [OperationType]中找到,其界说如下所示:

,

联博接口

www.326681.com采用以太坊区块链高度哈希值作为统计数据,联博以太坊统计数据开源、公平、无任何作弊可能性。联博统计免费提供API接口,支持多语言接入。

,
typedef struct _MP_SYNC_NOTIFICATIONS
{
  INT64 Timestamp;
  INT NotificationsCount;
} MP_SYNC_NOTIFICATIONS, *PMP_SYNC_NOTIFICATIONS;

万一FltSendMessage返回错误,则会更新以下变量:

1. MpData-> ErrorSyncNotificationsCount [OperationType];

2. MpData-> ErrorSyncNotificationsStatus [OperationType];

3. 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建立事情线程,将MpAsyncpWorkerThread设置为StartRoutine,没有StartContext被通报到这个新线程中。

该线程主要与MP_ASYNC结构一起使用,该结构具有以下界说(无论我若何实验交织引用这个结构,我现在无法获得更多的字段,这就是为什么我缺少许多字段的主要缘故原由字段):

typedef struct _MP_ASYNC
{
  SHORT Magic;      // Set to 0xDA07
  SHORT StructSize; // Sizeof 0x180
  LIST_ENTRY HighPriorityNotificationsList;
  LIST_ENTRY NotificationsList;
  PETHREAD WorkerThread;
  KEVENT AsyncNotificationEvent;
  KSEMAPHORE AsyncSemaphore;
  FAST_MUTEX AsyncFastMutex;
  INT NotificationsCount;
  INT64 field_A8;
  INT64 field_B0;
  INT64 field_B8;
  PAGED_LOOKASIDE_LIST NotificationsLookaside;
  INT64 TotalSizeNotificationsSent;
  INT64 TotalSizeRemainingNotifications;
  INT FailedNotifications;
  INT64 field_158;
  INT64 field_160;
  INT64 field_168;
  INT64 field_170;
  INT64 field_178;
} MP_ASYNC, *PMP_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。若是到达极限,将以相反顺序检查列表条目,首先是正常优先级,然后是较高优先级。以下伪代码显示了这一点:

if (MpConfig.AsyncStarvationLimit == _InterlockedCompareExchange(
                                        &AsyncStarvationLimit,
                                        0,
                                        MpConfig.AsyncStarvationLimit)) {
  if (&MpAsync->NotificationsList != MpAsync->NotificationsList.Flink)
    goto SendMessage;

  if (&MpAsync->HighPriorityNotificationsList != MpAsync->HighPriorityNotificationsList.Flink) {
IncrementLimit:
    _InterlockedAdd(&AsyncStarvationLimit, 1);
    goto SendMessage
  }
}

if (&MpAsync->HighPriorityNotificationsList != MpAsync->HighPriorityNotificationsList.Flink)
  goto IncrementLimit

if (&MpAsync->NotificationsList != MpAsync->NotificationsList.Flink) {
  _InterlockedCompareExchange(&AsyncStarvationLimit, 0, AsyncStarvationLimit); // Atomic set to 0
  goto SendMessage;
}
A

如你所见,来自MpAsync-> HighPriorityNotificationsList的新闻具有更高的优先级,由于除非到达限制,否则将首先检查此LIST_ENTRY。

接下来的部门异常简朴,若是在两个列表条目中找到一个条目,则需要执行以下步骤:

1. 递减MpAsync-> NotificationsCount;

2. 从MpAsync-> TotalSizeRemainingNotifications中减去数据巨细;

3. 将MP_ASYNC_NOTIFICATION的Magic 和Size(稍后将看到此结构)设置为0xBABAFAFA

推送或释放,将MP_ASYNC_NOTIFICATION条目添加到后备MpAsync-> AsyncNotificationsLookaside

4. 使用FltSendMessage发送现实新闻;

5. 以防错误增添MpAsync-> FailedNotifications;

6. 将数据巨细添加到MpAsync-> TotalSizeNotificationsSent。

之后,线程将再次遍历for循环,守候这两个工具中的任何一个发出信号。

只是为了完成该事情线程的完整循环,函数MpAsyncpShutdownWorkerThreads是使用MpAsync-> AsyncNotificationEvent作为要发出信号的事宜来挪用KeSetEvent函数,正如我们之前所看到的,它将竣事循环并终止线程。这个函数是从MpAsyncShutdown中挪用的,它卖力消灭所有与异步通知相关的内容。

MpAsyncSendNotification

NTSTATUS MpAsyncSendNotification(
  PVOID *__shifted(AsyncMessageData,8) AsyncMessageBuffer,
  ULONG SizeOfBuffer, 
  INT PriorityFlag, 
  PProcessCtx ProcessCtx
);

我们已经看到了一些例子,其中代码将建立一个AsyncMessageData结构,并使用随后将要发送到MsMpEng的数据填充该结构。我们刚刚看到了这些数据的发送方式,现在我们将领会若何将这些数据添加到以前看到的列表条目中。

卖力此功效的函数是MpAsyncSendNotification,它将首先对AsyncMessageBuffer和SizeOfBuffer举行完整性检查。若是检查完成,函数将测试SenderBuffer-> TypeOfOperation是否小于0xA,若是小于0xA,则MpData-> AsyncNotificationCount值将递增并分配给SenderBuffer-> NotificationNumber,TypeOfOperation的可能值如下:

typedef enum _MP_ASYNC_NOTIFICATION_OPERATION
{
  CreateProcess = 0x0, 
  RegistryEvent = 0x1,
  SendFile = 0x2,
  LoadImage = 0x3,
  OpenProcess = 0x4,
  RawVolumeWrite = 0x5, // High-Priority
  CreateThread = 0x6,
  DocOpen = 0x7,
  PostMount = 0x8, // High-Priority
  OpenDesktop = 0x9,
  PanicMode = 0xB,
  CheckJournal = 0xC, // High-Priority
  TrustedOrUntrustedProcess = 0xD, // High-Priority
  LogPrint = 0xE,
  Wow64cpuLoad = 0xF,
  OpenWithoutRead = 0x10,
  FolderGuardEvents = 0x11,
  DlpOnFileObjectClose = 0x13,
} MP_ASYNC_NOTIFICATION_OPERATION;

下一步是增添ProcessCtx-> NotificationsSent,若是存在ProcessCtx ,则完成此操作后,将弹出或分配来自MpAsync-> AsyncNotificationsLookaside的条目,并将在该缓冲区中初始化以下结构:

typedef struct _MP_ASYNC_NOTIFICATION
{
  SHORT Magic;        // Set to 0xDA08
  SHORT StructSize;   // Sizeof 0x18 - Header Size
  LIST_ENTRY AsyncNotificationsList;
  PVOID *__shifted(AsyncMessageData,8) pMessageBuffer;
  INT MessageBufferSize;
} MP_ASYNC_NOTIFICATION, *PMP_ASYNC_NOTIFICATION;

初始化此结构后,将有两个可能的路径,若是MpAsync-> NotificationsCount小于MpConfig.MaxAsyncNotificationCount,则为第一个路径。在这种情形下,将基于PriorityFlag将初始化的结构插入MpAsync-> HighPriorityNotificationsList或MpAsync-> NotificationsList的末尾,若是已设置,则链接到前者,在另一种情形下,链接到后者,然后是MpAsync-> NotificationsCount递增,MessageBufferSize被添加到MpAsync-> TotalSizeRemainingNotifications中,最后发出信号量:KeReleaseSemaphore。

当MpAsync-> NotificationsCount大于或即是MpConfig.MaxAsyncNotificationCount时,接纳第二条路径,若是发生这种情形,那么MpAsync->HighPriorityNotificationsList或MpAsync->NotificationsList(同样基于PriorityFlag)的第一个条目将从LIST_ENTRY中被释放,而且新建立的条目将插入到它的末尾。在竣事之前,正如我们在事情线程中看到的,该函数将增添MpAsync->AsyncMessagesFailed,并将未链接的条目推入或释放到后备列表,或者从后备列表释放(将第一个字节设置为0xBABAFAFA之后)。

这是为了确保在必须建立新通知的情形下有足够的资源从后备列表中分配池,另外也是确保较新的通知是保留在LIST_ENTRY上的通知,以防事情线程无法获取有足够的执行时间来释放通知列表。

在下一篇文章中,我们将研究工具(PsProcessType和ExDesktopObjectType)的注册回调,还将研究若何保留驱动程序信息以及若何举行验证。

本文翻译自:https://n4r1b.netlify.com/posts/2020/02/dissecting-the-windows-defender-driver-wdfilter-part-2/如若转载,请注明原文地址:

网友评论