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

Posted

技术标签:

【中文标题】如何防止内存编辑以防止挂钩【英文标题】:How to prevent memory editing to prevent hooking 【发布时间】:2018-07-12 15:05:00 【问题描述】:

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

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

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

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

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

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

真的有办法完全防御钩子,或者至少让它变得很难吗?

【问题讨论】:

这里的威胁场景是什么?混乱的管理员(VirtualProtect 和 NTVirtualProtect 需要提升权限)、随机恶意软件或针对您的应用程序的特定恶意软件? 不,它不需要管理员权限,您可以在非管理员运行的程序中轻松使用它们 VirtualProtect 只能访问当前进程的页面,VirtualProtectEx 需要外部进程的 PROCESS_VM_OPERATION 访问权限。由于我假设您不想保护进程免受自身攻击,所以我再次问威胁是什么 @SergeBallesta 他可以使用 ObRegisterCallbacks 来防止句柄创建和复制到特定目标,但显然他故意想搞砸事情,因为他专注于整个 API 挂钩场景。因此,让他连接 API 并把事情搞砸,希望他能从错误中吸取教训。 我不能制作内核驱动,因为我现在不能购买数字证书来签署驱动 【参考方案1】:

我认为你无法完全避免被钩住。黑客仍然可以修改磁盘上的可执行文件,使其不会安装挂钩。或者他可以自己在你的可执行文件中安装钩子。

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

【讨论】:

您忘记了一个重点:保护越复杂,用户/管理员体验越差。我记得在我的组织支付了许可费用的情况下,我花了几个小时让 2 个受保护的软件在同一台机器上协作:两个支持都只是说另一个应用程序是罪魁祸首。后来我们决定不再购买任何受保护的软件。 将检测到用户正在编辑原始文件,因为最后一次写入的日期会发生变化。此外,我没有发布游戏,而是发布了反用户模式 ​​rootkit,因此我的应用程序将检测到使用这种技术的恶意软件。你可能会说杀毒软件可以完成这项工作,但实际上这些公司都懒得实施这样的事情。我自己测试了 hooking 用户模式功能,杀毒软件完全失明 @SergeBallesta 并非总是如此。好吧,在大多数情况下,都会出现这样的问题。但保护量与用户体验破坏不成正比。这是软件质量的问题。质量差的保护系统会给用户带来很大的痛苦,并且不会阻止黑客入侵它。请记住那些可以轻松避免的带有漫长而痛苦的 CD 检查的游戏 CD。良好的保护系统不会让用户注意到自己。保护系统开发人员的目标之一是使保护系统不被用户注意到。 @dev65 您知道用户模式挂钩仅适用于您当前的进程吗?您正在修改进程内存(函数VirtualProtect)内的可执行代码,以确保该进程不会调用函数VirtualProtect? 这些恶意软件通过在父进程中挂钩 CreateProcess 来挂钩几乎所有正在运行的进程,父进程将挂钩子进程等等。他们挂钩了一些 nt 函数,例如 NtQuerySystemInformation 和其他导致检测恶意软件 pid 甚至可执行文件路径的函数,因为它们挂钩了 NtQueryDirectoryFile,因此恶意软件将从任何用户模式应用程序中隐藏,因此首先我需要删除挂钩然后防止又上瘾了【参考方案2】:

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

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

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

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

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

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

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

尽管如此,对流程的代码和数据部分进行强大的配置仍然有很长的路要走。事实上,确保您的进程中的所有安全描述符、DACL 等都正确配置非常重要。首先,确保您的代码页是只读/可执行的。每个进程都包含一个VAD (virtual address descriptor) tree (implemented as an AVL tree) 到内核。 VAD 包含进程中一系列虚拟地址的属性和标志。此类属性和标志包括各种类型的保护,例如读/写/执行、写时复制、内存是否已提交/保留等...

在 VAD 标志中使用了一个标志,称为 SecNoChange。如果您使用NtCreateSection 重新映射进程中的代码部分,然后在AllocationAttributes 中传递SecNoChange,然后调用NtMapViewOfSection 将内存映射回进程,那么它将使诸如NtProtectVirtualMemory 之类的调用失败。这将防止攻击者修改您的代码的页面属性,以便他们不会导致任意异常并处理它们以重定向代码,如果它们是只读/执行写入该内存,他们也不能更改保护。写入只读存储器当然会失败。这就是将代码页标记为只读/执行而不是读/写/执行的重要之处,这样攻击者就无法更改保护或写入地址范围。

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

Windows 提供了多种缓解技术和策略。我也会研究 Windows 提供的process mitigation policies。

【讨论】:

但是如果您已经在目标进程中(来自 dll)并且阻止 OpenProcess 和 CreateRemoteThread 不是最好的解决方案,那么 WriteProcessMemory 不是必需的,因为这将阻止调试器调试进程(我'不是试图保护我的进程,而是另一个)攻击者可以使用 memcpy 或简单的类似这样的东西: void copy(void *v,const void *v2,size_t sz)for (size_t i=0;i 这可能会掩盖您的代码,但攻击者可能正在执行签名扫描并找到同一函数的多个引用并决定修改所有引用。使用 SecNoChange 重新映射这些部分将阻止攻击者成功调用 VirtualProtect,如果内存是只读/执行的,这也将使 memcpy 和其他内存写入函数首先失败。使用具有正确页面保护的 SecNoChange 将阻止内存编辑。 当节已经映射到进程中时,在此操作期间暂停进程的执行,然后临时复制节的内存并使用 NtUnmapViewOfSection 取消映射节。然后使用 NtCreateSection 创建一个将 SEC_NOCHANGE 作为分配属性传递的新节。创建section后,可以用NtMapViewOfSection将其映射到与旧section相同的基地址,并将临时缓冲区复制到新的section中,然后可以删除临时缓冲区,恢复流程执行。这将阻止编辑页面。 真正有用的信息。但在我的情况下,该进程需要分配可执行代码,因此我将中断该进程。如果我可以阻止任何可执行代码的生成,我将使用进程缓解,因为该进程将受到内核的保护,因此攻击者需要一个内核 rootkit 来分配钩子,但是您提供的解决方案可以通过再次重新映射该部分来绕过,但好处是这不是著名的方式,所以大多数攻击者不会知道发生了什么!!!【参考方案3】:

我有一个很好的解决方案 如果我不想受到钩子的影响,请不要直接使用钩子函数,而是从蹦床上使用,所以攻击者必须知道你的蹦床的地址

【讨论】:

以上是关于如何防止内存编辑以防止挂钩的主要内容,如果未能解决你的问题,请参考以下文章

如何防止 node.js 中的内存泄漏?

如何在使用 Nuxt 的 fetch() 挂钩获取数据时防止模板错误

如何改进我的查询以防止“PHP 致命错误:允许的内存大小”(laravel 5.2 中的排序和分页)

如何防止 JTextPane.setCaretPosition(int) 中的内存泄漏?

反应JS |为啥在输入更改时更新此状态挂钩会清除输入以防止写入任何内容?

是否需要分离 pthread 以防止内存泄漏?