如何防止内存编辑以防止挂钩
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我有一个很好的解决方案 如果我不想受到钩子的影响,请不要直接使用钩子函数,而是从蹦床上使用,所以攻击者必须知道你的蹦床的地址
【讨论】:
以上是关于如何防止内存编辑以防止挂钩的主要内容,如果未能解决你的问题,请参考以下文章
如何在使用 Nuxt 的 fetch() 挂钩获取数据时防止模板错误
如何改进我的查询以防止“PHP 致命错误:允许的内存大小”(laravel 5.2 中的排序和分页)
如何防止 JTextPane.setCaretPosition(int) 中的内存泄漏?