StealthHook – 一种在不修改内存保护的情况下挂接函数的方法

常见的用户模式函数截获方法包括内联代码挂钩、IAT 挂钩和硬件断点挂钩。这些方法很有效,但它们需要修改 .text 部分、使用 NtProtectVirtualMemory 更改内存保护或自定义异常处理程序,所有这些都可能是“干扰”。这篇文章描述了一种在不修改内存保护的情况下秘密挂接函数的方法。通过覆盖嵌套在目标函数中的全局指针或虚拟表条目,可以挂钩函数而不会引起怀疑,因为其中许多内存区域已经启用了写入权限。这是我多年来一直使用的方法,效果很好。

原文链接:https://www.x86matthew.com/view_post?id=stealth_hook

如果我们能够截获嵌套在目标函数中的子函数,我们就可以操纵原始函数的执行流和返回值。通过从嵌套函数遍历堆栈,我们可以找到原始目标函数的返回地址。然后,我们可以用新值覆盖原始返回地址,强制程序在继续使用原始代码之前执行进一步的指令。这种函数挂钩方法不需要对可执行代码进行任何修改,也不需要任何内存保护更改,因此很难使用传统技术进行检测。

此方法背后的概念相对简单,但手动查找合适的挂钩点可能既困难又耗时,尤其是在大型函数中。为了简化此过程,我开发了一个工具,可以自动执行此过程,并为任何给定函数快速找到合适的钩点(如果存在)。这使得在实践中实现此方法变得容易,并且同时支持 32 位和 64 位代码。

该程序通过扫描指向目标函数中指令的可写指针来构建引用列表。它通过安装自定义异常处理程序并单步执行函数(一次一条指令)来实现此目的。对于每条指令,程序会扫描每个加载的模块,以查看是否存在指向当前指令的可写指针。异常处理程序仅由开发人员用于初始发现潜在的挂钩点,最终的挂钩代码不需要安装自定义异常处理程序。

在 WoW64 进程中运行时,我们无法单步进入本机 64 位代码 – 在这种情况下,程序在 Wow64Transition 函数中捕获从 32 位到 64 位模式的转换,并在返回地址设置硬件断点。单步执行暂时禁用,直到目标函数返回到 32 位模式,此时它将恢复并继续监视函数。

创建引用列表后,程序将一次覆盖列表中的每个指针,然后再次运行目标函数。如果在运行期间执行覆盖的指针,则表明它是一个成功的挂钩点,程序将以此方式标记它。对列表中的所有指针重复此过程,直到测试完所有潜在的挂钩点。

该程序还计算并显示堆栈增量,这是需要向下移动到堆栈中的字节数或der 覆盖它标识的每个挂钩点的原始返回地址。

重要的是要考虑指向正在挂钩的子函数的指针可能被程序中的其他函数调用的可能性。在这种情况下,必须注意确保调用方是目标函数,而不是可能使用相同子函数的其他函数。

使用此方法的一个潜在缺点是目标函数中的代码流可能因目标模块的不同版本而异 – 这可能会使可靠地拦截函数调用变得更加困难。为了使用此方法提高钩子的可靠性,我建议正确枚举堆栈帧,而不是使用固定堆栈增量。现代控制流强制方法将来可能会有效地防止这些类型的钩子。

为了演示,我们将使用该工具在 CreateFileA 函数中搜索挂钩点:

正如我们在上面看到的,该工具发现了两个适合挂钩 CreateFileA 的可写全局指针。通过查看调试符号,我们可以看到这两个函数分别是_pfnEightBitStringToUnicodeString和feclient_EfsClientFreeProtectorList。由于feclient_EfsClientFreeProtectorList函数不常用(与_pfnEightBitStringToUnicodeString不同),因此在这种情况下使用这是最佳选择。

下面的代码展示了如何使用上面发现的信息在 32 位测试程序中钩住和操作 CreateFileA 调用的返回值:

该测试程序的结果如下所示:

输出显示第一个创建文件调用正常完成。安装钩子后的第二个调用返回了我们的钩子值 0x12345678。

该工具的完整源代码如下:

发表评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部