本期教程我们要探讨的是如何让一个程序执行自己的代码,绕过R星服务器的检测。执行自身代码有许多方式,但无论是单机还是网络,都与游戏安全的一些敏感内容无关。因为某些问题我不会深入讲解,如果你感兴趣可以自行探索,但如果感到无趣或者困惑,也可以选择退出。
由于当前对逆向工程的管控比较严格,我也不能过于详细地讲解。所以我会尽量简略地介绍,希望你们能理解。目前我们只讲解了一些基础知识,如果有不理解的地方可以提问,但关于单机、网络等其他方面的内容我就不会多说了。这些内容取决于你自己的理解能力。首先,让我介绍一下基本原理。任何程序都不太可能只发一个 ex1,因为现代游戏通常会依赖音频、视频等资源,还可能使用一些库,比如 Open GL 或者微软的一些库。你可能会用到一些开源库,比如 OpenCV。今天我们要讨论的是一个比较简单的方法,不涉及太复杂的内容。
以单人战局为例,通常会涉及到音频、视频或图形图像处理等方面。你至少会涉及其中的两个方面,很少情况下会缺少其中任何一个。这些库通常是开源的,但在自行编译时,开发环境的搭建可能会很困难。因此,很多时候人们不会自己编译,而是直接导入已经编译好的库。
例如,官方可能提供了已编译好的库,大家都可以使用。另外,也有一些开发团队会搭建好环境并编译好库,然后分享给其他人使用。通过这种方式,可以避免静态编译时可能出现的问题。静态编译通常指的是将库文件直接打包到游戏中,但这可能会导致编译参数不一致,进而影响程序的生成。因此,最好将库生成为动态库,这样就不需要担心编译参数的问题。动态库加载时也不存在编译参数的要求,只要加载了库或者导入了库的符号表,就可以正常运行。然而,加载动态库也存在一些问题,比如在加载时可能会依赖导出符号表。
当我看到它时,它会有一个导出符号表,加载起来就会没问题。只要这个符号表中有我原本的七个函数,那加载就不会有问题。在这种情况下,有一个问题就是导出符号表和动态库不会深入去检查。通常情况下,它会先从自己所在的目录或相关目录中加载。然后,他们会将这些内容全部导入。如果动态库中我自己写了一个 A 的函数,然后因为导出符号表是公开的,我可以将所有导出的函数实现一遍。实际上,我只需要保留一个壳子。这时,当 ex1 加载时,我将它改名为下划线,并将 A1 撇改为 A。这样 ex1 执行时会默认找到我的这个函数。当它调用我的函数时,实际上是调用了真正的函数。在调用过程中,我可以进行一些处理或修改。此外,我还可以在其中启动另一个线程去做其他事情。因为库被加载到进程空间中,我就有修改权限,无需其他权限。在这个过程中,我可以加载其他的动态库,或者修改已加载的行为。如果你问为什么动态库必须公开,因为它需要一个导出符号表。这个表提供给 ex1 加载,否则 ex1 如何调用库中的函数呢?我可以将两个源码放到一个解决方案中,这样就可以更好地理解整个原理。如果我不知道你在里面执行了些什么,我就会将你的动态库再加载一遍到我的动态库里。加载后,将对应的符号函数加载到我的库里。当这里调用大 A 的功能时,我实际上是再调用真实的大 A,然后将返回值返回给程序。我就像一个二道贩子。为什么要做这个呢?因为这样可以让我进一步进行各种操作,包括获取返回值和传输参数。拿到参数后,我可以修改值或者执行其他操作,比如在特定情况下触发额外的动作。这种方法更像是劫持而不是注入,因为注入通常是动态的,而我的方法不是。这种替换的可行性更大一些。你可以将真实的动态库放在原来的文件夹下,也可以放在其他地方。这取决于你调用哪个东西去加载它。关于这个原理,你明白了吗?对了,这个公开战局的检测模块是用 C# 写的,所以大部分动态库不能用 C++ 编写。虽然可以用 C# 和 C++/CLI,但因为我们课程主要讲 C++,所以不涉及这一块。IL 是一种字节码,类似于机器码,但需要 IL 解释器来解释。尽管 IL 和机器码相似,但它们并不相同,因此后期修改会有一些困难。幸运的是,在这个项目中有一个叫做 f model 的东西。这个东西是一个音频库,用于处理游戏中的音频。对于游戏开发者来说,使用这样的库是很常见的。然而,有一个比较棘手的问题是,像这种开源库通常会有大量的导出函数,而我们并不总是清楚它们都做了些什么,或者它们使用了哪些库。更进一步,这些导出函数分为两种:一种是标准的 C 函数调用,另一种是 C++ 函数调用。对于后者,特别是使用 C++ 的导出函数,会比较麻烦,因为它们可能使用了 C++ 的名称修饰规则(例如 name mangling),这样就会导致在 C 代码中无法直接调用。解决这个问题的一个方法是将导出函数转换为汇编符号,然后将其重新包装成新的动态库。这样,游戏就可以使用新的动态库而不需要直接处理原始的 C++ 导出函数。需要注意的是,对于使用 SO 文件的库来说,情况可能会简单一些,因为可以使用 C++filt 这样的工具进行反解析。但是对于 DLL 文件来说,情况可能会更加复杂,因为 Microsoft VC++ 使用了不同的命名规则。尽管转换这些导出函数到 C++ 是有可能的,但是目前还没有找到非常有效的方法,尤其是针对 VC++ 的情况。然而,我们可以看到,IDA 成功地将这些导出函数转换为了标准的 C 函数,所以理论上是可以做到的。