以下是对基本问题的一些想法和可能的解决方案;即使整个系统远远超出了您的开发预算,一些关键想法仍然可能对您制定自己的解决方案有用。
如果您没有使加密算法本身成为系统中最薄弱环节的杠杆作用,那么加密就没什么用了,就像一扇十英寸的钢门如果安装在帐篷或膝盖上就不会提供安全性一样-高栅栏。如果 AES 实现在您的字节码 blob 外部,那么它的入口点很容易挂钩,将密钥放在银盘上。
这里有几个对你不利的因素。
(1) 模块化对分析有很大帮助。一旦代码体被识别为服务于某个功能——甚至可能是众所周知的算法,如 CRC32、MD5 或 AES——其调用的目的就很明确了。
(2) 优化编译器通过减少冗余来极大地帮助分析。除非他们过于喜欢内联。
(3) 代码的所有外部交互(操作系统调用、库调用)都为攻击者提供了有关正在发生的事情的线索。
无论如何,这就是您获胜的方法:您需要建立一个大型壳牌游戏来使这些线索毫无价值,您需要通过交错大量计算来打破模块化;此外,您需要将要验证的状态作为密钥的一部分,并且您需要将整个shebang 绑定到特定的游戏会话、客户端进程和机器。
这样做的一种方法是编写一种字节码解释器,它实现验证所需的基本操作:异或两个单元格、添加另外两个单元格、散列字符串、校验和内存范围、检索进程 ID 等。本质上是一个类似于 IDC 或 NWScript 的小型通用脚本,其中包含一些特殊的“宏”操作。基本操作从单元格(寄存器)数组中获取操作数,并将其结果传递给数组中的某个单元格。每个循环(操作)还涉及循环一组简单的 RNG(xorshift、KISS 等)以及基于这些 RNG 的函数表和元胞数组的混洗步骤。关键点是要验证的状态元素必须可以简化为数值,这些数值也可用于扰乱 RNG 状态。
一旦验证的单个步骤返回错误的结果,所有后续指令都将被错误解码,这意味着即使在验证失败的环境中也无法分析代码。这可以保护在正确的验证码结束后计算的所有内容,例如会话密钥。
剩下的就是将每个字节码 blob 绑定到特定的客户端会话,即专门为它生成一个 blob。另一个客户端会话获得不同的 blob,因此没有可以从合法客户端会话中提取并用于解锁机器人会话的信息。
验证需要分解成一系列小步骤;即,不是一次性对一个大内存范围进行校验和,而是进行数十次较小的校验和操作。
您将像往常一样用简单的脚本语言编写验证码;一个特殊的程序会分析生成的目标代码的依赖关系图,并确定可以在何时执行哪些字节码,从而有效地将所有内容内联到一个巨大的怪物函数中。这是预处理步骤,完成一次。然后是代码生成步骤,其中以伪随机方式(基于会话密钥)选择可调度的字节码并将其编译为 blob。在选择每个字节码时,它也必须在通过集合的环境中执行,以正确更新解释器状态(RNG 状态、函数表和元胞数组的混洗状态)。可能有数百个独立的计算链可以在给定点进行调度,至少在早期是这样。
除了控制字节码调度之外,会话密钥还可以控制您编译到特定会话 blob 中的验证操作(可能从池中选择,并参数化基本校验和范围)。这样,分析合法客户端的 blob 只为攻击者提供了一小部分验证操作,他们必须愚弄/模仿才能成功。在极端情况下,您可以只对过程图像的一小部分进行校验和,而您的攻击者需要以原始方式呈现(或虚拟化)整个图像,因为他们不知道将对哪些特定区域进行校验和。
在另一个字节码系统之上实现字节码系统可能会限制一些事情,主要是在性能(可实现的复杂性)和可实现的模糊性方面(可识别的函数调用来检索进程 id,而不是gs:[30h]
从那里偷看和运行的简单、未命名的机器指令) . 但原则是成立的,即使你必须使用一个巨大的 switch 语句而不是一个函数表。出于某种奇怪的原因,在字节码系统之上实现加密操作(RNG,如 Marsaglia 的 KISS、散列、密码)似乎有些习惯……可能是因为它很容易编写而很难分析。
杠杆——它集成到整个系统的方式——在这里与加密一样重要。你不希望你的十英寸钢门被绕过。
此外,像这样的系统根本没有弹性,它们非常脆弱。如果一个单一的验证操作过于急切/限制,那么您将有成千上万的合法客户愤怒地嚎叫......
这里的问题是诸如反恶意软件系统、图形驱动程序等之类的东西喜欢把事情搞砸并将 DLL 注入正在运行的进程中,这会使将加载的模块列入白名单有点棘手。例如,攻击者可能会将带有 NVIDIA 名称的 DLL 注入在 ATI 卡上运行的游戏中。绕路也很常见。
背景资料:
注意:像自动混淆器这样的罐头解决方案往往会落入罐头黑客。这是否是一个问题更多地取决于意外情况(在您的听众中出现熟练的逆向者,以及他们愿意投入的时间)而不是其他任何事情。例如,似乎没有任何通用的 EA/Denuvo sh*te 解包器,即使他们的方案比最先进的技术落后大约十年。我想没有人兼具本领和对名望的渴望......