如何防止内存编辑以防止挂钩

How to prevent memory editing to prevent hooking

我最近学习了内联挂钩 x32 和 x64,它基于用 jmp 覆盖函数的第一个字节到挂钩函数,或者通过将 64 地址推送到 rax 然后 jmp rax 以在 x64 架构上执行远跳转 我还学习了iat hooking和delay imports hooking,这需要在import table中编辑一个offest holding function address来指向我的hook函数 此外,异常挂钩需要至少将第一个字节编辑为未知字节,以便抛出异常,您将使用已安装的处理程序捕获它并重定向到您的蹦床

所有这些类型的挂钩都需要编辑内存,内存通常是 PAGE_EXECUTEREAD 函数或只读的导入 table

因此攻击者将使用 VirtualProtect 或 NtVirtualProtect 来编辑字节

另一种挂钩方法是通过保护异常,它几乎不需要对字节进行任何编辑,但需要对内存保护进行任何编辑,因此在访问该函数时会引发异常,您将处理它们并做任何您想做的事情

所以这些方法都需要更改内存的保护,所以我考虑挂钩 VirtualProtect 和 NtVirtualProtect 以防止对特定地址进行任何编辑,但挂钩者可以取消挂钩函数并绕过这个

我听说有新的缓解措施,例如防止动态代码生成,但我需要分配一些 executable 代码,所以我不能使用它,它不会防止 iat 挂钩和保护异常挂钩

是否真的有一种方法可以完全抵御 hook 或至少使其变得非常困难?

我认为您不能完全防止挂钩。黑客仍然可以修改磁盘上的可执行文件,使其不会安装挂钩。或者他可以自己在您的可执行文件中安装挂钩。

要防止这种情况实际上有很多技术。例如,您可以对程序的修改进行大量检查。您可以对程序中特定代码部分的修改进行隐藏检查,并且可以混淆您的代码。还有许多其他技术,通常将它们组合起来以构成高效的软件保护系统。但是 none 的方法可以使您的代码完全免受黑客的修改。如果有这样的保护,就不会有software/game被盗版了。现在分发给客户的盗版软件只是一个困难的问题。保护系统越复杂,破解它所需的时间就越多。但这绝非不可能。

我有一个很好的解决方案 如果我不想受到 hook 的影响,就不要直接使用 hook 函数,而是从 trampoline 中使用,这样攻击者就必须知道你的 trampoline 的地址

为了防止内存编辑进而可能阻止挂钩,必须对进程进行适当的配置。当指定的内存被传递到调用中时,攻击者将通过调用 WriteProcessMemory/NtWriteVirtualMemory 来修改代码。攻击者将使用这些例程,而不管它们是否在他们正在修改的进程的上下文中执行。还有各种其他方法可以在进程内写入内存,攻击者也可以使用这些方法,我将介绍如何防范这些方法。

请注意,如果没有为包含被修改地址的页面配置适当的属性,您无法阻止 WriteProcessMemory/NtWriteVirtualMemory 调用从用户模式修改代码。当 NtWriteVirtualMemory 发起系统服务请求时,内核会处理剩下的事情。

从内核模式的角度来看,为 Windows 驱动程序提供的 API 确实提供了一个例程,该例程为某些类型的句柄(例如进程和线程句柄)上的操作注册回调。此例程称为 ObRegisterCallbacks。某些软件使用此例程来阻止对其句柄的某些操作完成。

ObRegisterCallbacks 可以为以下类型的句柄操作注册回调

  • 进程句柄
  • 线程句柄
  • 桌面手柄

阻止对指定进程句柄的特定操作可以阻止NtWriteVirtualMemory(WriteProcessMemory)和NtProtectVirtualMemory(VirtualProtect/Ex)等系统服务请求完成。这样做可以防止攻击者通过服务请求将内存写入您的进程,并防止他们更改您进程中虚拟页面的保护。

从用户模式的角度来看,您在读取进程和线程等对象的内核数据结构方面受到更多限制,并且您在可以调用的例程类型方面受到更多限制。如果攻击者只是决定调用 VirtualProtectEx 并将页面从 read-only/execute 更改为 read/write/execute.

,则无法阻止 WriteProcessMemory

您的流程的代码和数据部分的强大配置仍然会有很长的路要走。事实上,确保正确配置您的进程中的所有安全描述符、DACL 等非常重要。首先,确保您的代码页是 read-only/execute。每个进程都包含一个 VAD (virtual address descriptor) tree (implemented as an AVL tree) 到内核。 VAD 包含进程中一系列虚拟地址的属性和标志。这些属性和标志包括各种类型的保护,例如 read/write/execute、写入时复制、内存是否 committed/reserved 等...

VAD 标志中使用了一个名为 SecNoChange 的标志。如果您使用 NtCreateSection then pass SecNoChange in the AllocationAttributes and then call NtMapViewOfSection 重新映射进程中的代码部分以将内存映射回进程,那么它将使 NtProtectVirtualMemory 等调用失败。这将防止攻击者修改您代码的页面属性,这样他们就不会导致任意异常并处理它们以重定向代码,如果他们 read-only/execute 写入该内存,他们也不能更改保护。写入只读存储器当然会失败。这是将代码页标记为 read-only/execute 而不是 read/write/execute 很重要的地方,这样攻击者就无法更改保护或写入地址范围。

即使您的部分配置了 SecNoChange,使用内存完整性检查等额外的缓解技术也无妨。在基本层面上,内存完整性检查通常通过对一组页面的内存内容进行哈希处理并使用生成的第一个哈希值验证下一个周期的完整性,如果哈希值不同,则内存已被修改并且程序可能终止其当前 activity。

Windows 提供了许多类型的缓解技术和策略。我还会研究 Windows 提供的 process mitigation policies