什么是模块防篡改?模块篡改保护是一种缓解措施,可防止对流程主映像进行早期修改,例如IAT挂钩或流程挖空。它总共使用了三个API:NtQueryVirtualMemory、NtQueryInformationProcess和NtMapViewOfSection。如果启用,加载程序将在调用入口点之前检查主图像标题和IAT页面中的更改。它通过使用信息类MemoryWorkingSetExInformation调用NtQueryVirtualMemory来完成此操作。返回的结构包含有关页面共享状态的信息,以及它是否从其原始视图进行了修改。如果标头或IAT已从其原始映射中修改。例如,如果主图像已取消映射并且另一个图像已被映射到它的位置,则加载器将调用带有类ProcessImageSection的NtQueryInformationProcess来获取主图像部分,然后将使用NtMapViewOfSection重新映射它。这样,新的部分将被使用,图像的篡改副本将被忽略。此缓解措施从RS3开始可用,并且可以在进程创建时使用PROCESS_CREATION_MITIGATION_POLICY2_MODULE_TAMPERING_PROTECTION_MASK启用。如何发现模块篡改保护缓解措施如果Microsoft从未宣布或记录某些缓解措施,人们是如何发现它们的?因此,一个不错的地方是EPROCESS结构中的各种MitigationFlags字段。当前存在三个MitigationFlags字段(MitigationFlags、MitigationFlags2、MitigationsFlags3),每个包含32位。在前两个中,已经使用了整个32位,所以最近添加了MitigationFlags3,目前包含三个缓解措施,我相信很快会添加更多。这些标志代表流程中启用的缓解措施。例如,我们可以使用WinDbg为当前进程打印EPROCESS.MitigationFlags:最后,在位28和29中,我们可以看到值EnableModuleTamperingProtection和EnableModuleTamperingProtectionNoInherit。不幸的是,搜索这些名称并没有产生任何好的结果。有几个网站只显示结构而没有解释,一个模糊的StackOverflow回答简单地提到了EnableModuleTamperingProtectionNoInherit而没有添加细节,还有这条推文:不出所料,最详细的解释是AlexIonescuarts2017年的推文。这不是完整的文档,但它是一个开始。如果您已经知道并理解构成此缓解措施的概念,那么这一系列推文可能会非常清楚地解释有关该功能的所有内容。开始搜索进程缓解实现的第一个位置通常是内核:ntoskrnl.exe。但是,这是一个巨大的二进制文件,不易搜索。似乎没有与此缓解措施完全相关的函数名称,因此没有明显的起点。相反,您可以尝试不同的方法并尝试找到对EPROCESS的MitigationFlags字段的引用并访问这两个标志之一。但是除非您有权访问Windows源代码,否则没有简单的方法可以做到这一点。但是,您可以利用以下事实:EPROCESS是一个大型结构,并且MitigationFlags存在于它的末尾,偏移量为0x9D0处。一个非常粗暴但有效的方法是使用IDA搜索功能并搜索所有对9D0h的引用:这会很慢,因为它是一个很大的二进制文件,并且一些结果与EPROCESS结构无关,所以你必须搜索结果手动。此外,仅查找对该字段的引用是不够的,MitigationFlags包含32位,其中只有两个与当前上下文相关。因此,您必须搜索以下情况的所有结果:0x9D0用作EPROCESS结构的偏移量-因为无法保证知道每种情况下使用的结构类型,尽管对于较大的偏移量有只有少数选项可以关联,大部分可以通过函数名和上下文猜到。比较或将MitigationFlags字段设置为0x10000000(EnableModuleTamperingProtection)或0x20000000(EnableModuleTamperingProtectionNoInherit)。或者通过bt或bts等汇编指令,按位测试或设置第28位或第29位。运行搜索后,结果如下所示:您现在可以浏览结果并查看内核使用了哪些缓解标志以及在哪些情况。然后我会告诉你,这个努力是完全没用的,因为EnableModuleTamperingProtection在内核中的一个地方被引用:PspApplyMitigationOptions,它在创建新进程时被调用:因此,内核会跟踪是否启用了此缓解措施,但是从不强制执行它测试。这意味着缓解措施本身是在其他地方实施的。这种搜索对于这个特定的缓解措施可能没有用,但它是找出缓解措施实施位置的少数方法之一,而且它对其他流程缓解措施很有用,所以我想提一下。现在回到模块篡改保护,有时实施进程缓解的第二个地方是ntdll.dll,它是每个进程中要加载的第一个用户模式映像。此DLL包含加载程序、系统调用存根和所有进程所需的许多其他基本组件。在这里实施这种缓解措施是有道理的,因为顾名思义,它与模块加载有关,这是通过ntdll.dll中的加载程序发生的。此外,这是一个模块,其中包含Alex在他的推文中提到的功能。即使我们没有推文,只要打开ntdll并搜索“tampering”,我们很快就会得到一个结果:LdrpCheckPagesForTampering函数。寻找这个函数的调用者,我们看到它是从LdrpGetImportDescriptorForSnap调用的:在屏幕截图的第一行,我们可以看到两个检查:第一个验证当前正在处理的条目是主图像,因此加载了模块进入主图像模块。第二项检查是LdrSystemSllInitBlock.MitigationOptionsMap.Map中的两位。我们可以看到这里检查的确切字段只是因为我将正确的类型应用于LdrSystemDllInitBlock,如果您在没有应用正确类型的情况下查看此函数,您将看到一些随机的、未命名的内存地址被引用。LdrSystemDllInitBlock是一种数据结构,包含加载程序所需的所有全局信息,例如进程缓解选项。它没有记录,但在符号中有可用的PS_SYSTEM_DLL_INIT_BLOCK类型,所以我们可以在这里使用它。请注意,虽然此结构在NTDLL符号中不可用,但您可以在ole32.dll和combase.dll的符号中找到它。MitigationOptionsMap字段只是一个包含三个ULONG64的数组,其中包含标记为此进程设置的缓解选项的位。我们可以在WinBase.h中找到所有缓解标志的值。以下是模块篡改保护的值:这些值是相对于映射顶部的DWORD,因此模块篡改保护位实际上位于映射的位置44,与签入的位相同HexRays屏幕截图以及PspApplyMitigationOptions中的。现在我们知道缓解措施在哪里应用了检查,我们可以开始查看实施并了解缓解措施的作用。实现细节再看LdrpGetImportDescriptorForSnap:在我们已经看到的两次检查之后,这个函数获取主图像的NTheader并调用LdrpCheckPagesForTampering两次。第一次发送的地址是imageNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT],图片导入表,大小为8字节。使用NT标头本身的地址和大小第二次调用该函数。如果其中一个页面被认为被篡改,将调用LdrpMapCleanModuleView(根据名称判断)以映射主图像模块的干净视图。让我们看看LdrpCheckPagesForTampering的内部,看看NTDLL是如何判断一个页面是否被篡改的:首先,这个函数统计请求的字节范围内的页面数量(在本文的两个例子中,这个数字都是1)。然后它分配内存并使用MemoryInformationClass==4(MemoryWorkingSetExInformation)调用ZwQueryVirtualMemory。这类系统调用和消息是安全人员可能不会经常看到的东西,工作集是一种根据物理内存页面的当前状态来管理页面和确定页面优先级的方法,因此大多数人通常对它们不感兴趣安全人员。但是,工作集确实包含一些我们感兴趣的属性。特别是“共享”标志。我不会在这里详细介绍映射和共享内存,因为它们在许多其他地方都有解释。但简而言之,系统尽量不复制内存,因为这意味着物理内存将很快被复制的页面填满,主要是那些属于图像和DLL的,系统DLL像ntdll.dll或kernel32.dll被复制映射到大多数系统中的一个进程,因此在物理内存中为每个进程创建一个单独的副本只是浪费。因此,这些图像页面在所有进程之间共享。也就是说,除非以任何方式修改图像。图像页面使用一种称为“写时复制”的特殊保护,它允许页面可写,但如果页面被写入,则会在物理内存中创建一个新副本。这意味着对DLL的本地映射所做的任何更改(例如,写入用户模式挂钩或任何数据更改)只会影响当前进程中的DLL。这些设置被保存为标志,可以通过NtQueryVirtualMemory查询,这里使用的信息类:MemoryWorkingSetExInformation。它将在MEMORY_WORKING_SET_EX_INFORMATION结构中返回有关查询页面的数据:此结构为您提供正在查询的虚拟地址,以及包含页面状态信息的位,例如其有效性、保护和共享状态。有几个与页面共享状态相关的不同位:共享-页面是否可共享?这并不一定意味着该页面当前与任何其他进程共享,但是,例如,除非进程明确请求,否则不会共享私有内存。ShareCount-该字段告诉您该页面存在多少映射。对于当前未与任何其他进程共享的页面,这将为1。对于与其他进程共享的页面,这通常更高。SharedOriginal-此标志将告诉您此页面是否存在映射。因此,如果一个页面被修改,导致在物理内存中创建一个新副本,这将被设置为零,因为这不是该页面的原始映射。LdrpCheckPagesForTampering检查SharedOriginal位以确定页面是原始副本还是由于更改而创建的新副本。如果这不是原始副本,则意味着该页面已被以某种方式篡改,因此该函数将返回TRUE。LdrpCheckPagesForTampering在每个被查询的页面上运行此检查,如果其中任何一个被篡改则返回TRUE。如果该函数对任何已检查的范围返回TRUE,则调用LdrpMapCleanModuleView:此函数简短:它调用InformationClass==89(ProcessImageSection)的NtQueryInformationProcess以获取主图像的节句柄,然后使用NtMapViewOfSection重新映射它并关闭处理。它将新部分的地址写入DataTableEntry->SwitchBackContect以代替原始的篡改映射。为什么该功能选择特别检查这两个范围-导入表和NT标头?这是因为这两个地方经常成为试图虚拟化进程的攻击者的目标。如果主图像被取消映射并被恶意图像替换,则NT标头将不同并被视为已被篡改。进程挖空还可以篡改导入表以指向与进程预期不同的功能。所以,这主要是一个反挖空功能,目的是在主镜像中发现篡改企图,并用未被篡改的镜像的新副本替换它。功能限制遗憾的是,此功能相对有限。您可以启用或禁用它,仅此而已。实现缓解的函数是内部调用,外部无法调用。因此,例如,除非您自己编写代码(并手动映射模块,因为这些部分的句柄不方便存储在任何地方),否则不可能将缓解措施扩展到其他模块。此外,此缓解措施不包含日志记录或ETW事件。当主映像中的缓解通知被篡改时,它会静默映射并使用新副本,安全产品或团队不会发现任何痕迹。唯一的提示是主图像将再次调用NtMapViewOfSection并生成ETW事件和内核回调。但它很可能会被忽视,因为它并不一定意味着发生了不好的事情,并且可能不会导致任何警报或对可能是真正的攻击的重大调查。从好的方面来说,这种缓解措施非常简单有用,如果您想实施它也很容易模仿,例如检测放置在您的进程上的挂钩并映射一个新的、未挂钩的页面副本以供使用。您可以这样做而不是使用直接系统调用!以前有人用过这种缓解措施吗?在WinDbg中运行查询,我没有找到启用了模块篡改保护的进程的任何结果。经过一番摸索,我设法找到了一个启用此功能的进程:SystemSettingsAdminFlows.exe。当您打开Windows设置菜单中的应用程序->可选功能时,将执行此过程。我不知道为什么这个特定的进程使用这种缓解措施,或者为什么它是唯一一个这样做的,但它是迄今为止我设法找到的唯一一个启用了模块篡改保护的。本文翻译自:https://windows-internals.com/understanding-a-new-mitigation-module-tampering-protection/
