PC端Unity游戏Mono注入实现作弊分析

/ 0评 / 12

这是一篇 PC 平台的 Unity 游戏逆向分析文章,仅支持非 IL2CPP 编译方式的游戏。

不要以任何方式联系我并让我提供源代码和技术支持!!!

Unity逆向须知

破解 Unity 游戏的最佳方法是 C# 编写代码并使用 Mono 注入。

由于 Unity 引擎的特性,多级指针在 Unity 逆向中非常恶心(也就是找基址再找N级偏移),尽量使用 Hook/特征码扫描 获取想要的基址或数据。

游戏逻辑通常在 Assembly-CSharp.dll 中,可以用 dnSpy 等工具反编译分析游戏逻辑或直接修改游戏逻辑。

CE 自带了一个 Mono 解析器,在附加 Unity 引擎开发的游戏后,会在菜单栏显示出来。

使用 IL2CPP 编译(也就是把C#代码转为C++代码再编译)会让 dnSpy 反编译和 Mono 注入更加困难。

创建工程

使用 VS2019 创建一个 C# 的 DLL 工程:

修改 Properties/AssemblyInfo.cs 让每次编译 DLL 都生成不同的版本号,防止 Mono 注入后不能卸载,一直保留在内存中:

[assembly: AssemblyVersion("1.0.*")]
//[assembly: AssemblyVersion("1.0.0.0")]

这样改了后肯定会报错的,还需要关闭 VS2019,然后文本编辑器打开工程下的 工程名.csproj 文件,进行以下修改:

<Deterministic>false</Deterministic>

代码编写

和我上一篇文章里 C++ 的 DLL 注入方式不同,Unity 的 Mono 注入需要指定入口函数,所以我们先将工程默认创建的 Class1 改名为 Loader,并编写入口函数和辅助的主类代码。

不过在编写代码之前,我们需要为工程引用一些游戏的 DLL 文件,让我们可以直接调用这些 DLL 文件中的代码。

在解决方案资源管理器中,右键引用并选择添加引用:

UnityEngine.dllAssembly-CSharp.dll 是我们必须添加的 DLL。由于 Unity 版本的原因,某些系统的类不一定是在 UnityEngine.dll 中,则我们可以通过反编译工具 dnSpy 去搜索我们想要的类,然后在我们工程中引用它所在的 DLL 文件即可。

编写入口类,Loader.cs 文件:

using UnityEngine;
namespace SSJJ_Mono
{
    public class Loader
    {
        private static GameObject _Load;
        public static void Init()
        {
            _Load = new GameObject();
            _Load.AddComponent<Main>();
            GameObject.DontDestroyOnLoad(_Load);
        }
        public static void Unload()
        {
            _Unload();
        }
        private static void _Unload()
        {
            GameObject.Destroy(_Load);
        }
    }
}

编写主类(也就是我们辅助的逻辑),新建项 Main.cs 文件:

using UnityEngine;
namespace SSJJ_Mono
{
    class Main : MonoBehaviour
    {
        public void Start()
        {
        }
        public void Update()
        {
        }
        public void OnGUI()
        {
            GUI.Label(new Rect(Screen.width / 2, Screen.height / 2, 150f, 50f), "Injected");
        }
    }
}

代码解释

上面编写的代码中,我们任何辅助的逻辑都没实现,只是在屏幕上绘制了一个文字 Injected 来测试我们是否能够成功注入目标进程中。

函数 SSJJ_Mono.Loader.Init() 就是我们 DLL 的入口文件,在通过 Mono 注入工具注入的时候,我们需要指定入口文件的命名空间、类名、函数名。

在这个入口函数中,我们创建了一个 GameObject 并给他添加了一个 Main 组件,然后把它设置为加载后不销毁,以此来保证我们添加的组件会一直存在。

然后在看看我们创建的 Main 类,它是继承自 MonoBehaviour 的,我们要编写可以添加为 GameObject 组件的类,记住继承自它就行。这样 Unity 引擎就会自动去执行组件中的生命周期函数,我们也是在这些函数中实现我们的逻辑。

组件的生命周期方法远远不止这几个,其他可以去 Unity 文档或搜索引擎自行查询。

测试注入

快捷键 Ctrl + Shift + B 编译我们的工程,生成 DLL 文件。

如果你的工程配置和编写的代码没有问题,在输出控制台则会提示生成 DLL 成功:

已启动生成…
1>------ 已启动生成: 项目: SSJJ_Mono, 配置: Debug Any CPU ------
1>  SSJJ_Mono -> E:\Code\VSRepos\SSJJ_Mono\SSJJ_Mono\bin\Debug\SSJJ_Mono.dll
========== 生成: 成功 1 个,失败 0 个,最新 0 个,跳过 0 个 ==========

然后打开游戏,并使用 Mono 注入工具来注入我们编写的 DLL,这里我使用的注入工具是 SharpMonoInjector,这个工具可以在 GitHub 找到。

点击 Refresh 即可自动找到目标进程,Assembly to inject 为我们自己编写的 DLL 文件的全路径,下面3个文本框就不需要解释了吧。

填写完毕后,点击 Inject 按钮注入:

如果想要卸载,则在右边文本框输入我们事先写好的卸载的函数名,其实也就是销毁我们创建的 GameObject,当 GameObject 被销毁时,它所挂载的全部组件都会跟着释放并销毁。

点击 Eject 调用卸载函数:

该怎么去实现作弊功能?

上面我们实现了简单的注入功能,并在游戏内绘制了一段文字。

如果还想要实现一些其他功能,比如 FPS 游戏的所有玩家的属性和人物透视、自瞄等功能,需要通过 dnSpy 等反编译工具去分析游戏逻辑,找到游戏内玩家列表和视角控制等逻辑。

通过分析反编译后的游戏逻辑,找出自己需要的数据并引用相关的 DLL,实现一些简易功能:

绘制使用 Unity 的 GUI,最终效果图:

发表评论

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