Unity游戏逆向实战 - Mono打包方式完全解析
字数 2406
更新时间 2026-02-06 04:49:52

Unity游戏逆向实战 - Mono打包方式完全解析

1. Unity框架与Mono技术基础

1.1 Unity跨平台架构

Unity3D最大的特点是"一次制作,多平台部署",这一核心功能通过Mono技术实现。Mono作为Unity3D的核心组件,是Unity跨平台能力的根本基础。

1.2 Mono技术原理

  • Mono是一个开源的.NET框架实现
  • 允许Unity在多种支持.NET的平台上运行
  • 提供兼容的运行时环境和类库
  • 将源代码编译成IL(中间语言)代码

1.3 安全考虑与IL2CPP

由于Mono将代码编译为IL中间语言,使得逆向工程相对容易。为增强安全性,Unity从2014年开始引入IL2CPP技术:

  • 将IL代码转化为C++代码
  • 再编译成机器码
  • 提高代码安全性和性能

2. Mono打包游戏的文件结构分析

2.1 打包方式识别

Mono打包特征:

  • 游戏目录下存在:Data/Managed/Assembly-CSharp.dll
  • 主要逻辑代码集中在此文件中

IL2CPP打包特征:

  • 游戏目录下存在大型GameAssembly.dll文件
  • 包含Data/il2cpp_data文件夹

2.2 文件结构详解

游戏根目录/
└── Data/
    └── Managed/
        ├── Assembly-CSharp.dll          # 游戏主要逻辑代码
        ├── Assembly-CSharp-firstpass.dll # 第三方插件代码
        ├── UnityEngine.dll              # Unity引擎库
        └── 其他.NET框架库文件

3. dnSpy工具深度分析

3.1 基本操作流程

  1. 使用dnSpy打开Assembly-CSharp.dll文件
  2. 浏览游戏的所有类和方法结构
  3. 重点关注特殊Unity组件类

3.2 Unity特殊类识别

MonoBehaviour派生类特征:

  • Start(): 脚本启动时调用一次
  • Update(): 每帧调用一次
  • OnClick(): 按钮点击事件处理
  • 这些方法通常包含游戏核心逻辑

3.3 关键API搜索技巧

常用Unity API关键词搜索:

  • PlayerPrefs: 游戏数据存储
  • SceneManager: 场景管理
  • Input: 输入处理
  • Debug.Log: 调试信息输出
  • 验证相关:flagpasswordcheckverify

4. 实战案例深度解析

4.1 案例一:BJDCTF2020 BJD hamburger competition

解题步骤:

  1. 定位关键代码

    • ButtonSpawnFruit类的Spawn方法中发现flag相关逻辑
    • 找到SHA1密文:DD01903921EA24941C26A48F2CEC24E0BB0E8CC7
  2. 解密过程

    • 在线解密SHA1得到明文:1001
    • 分析MD5加密函数发现特殊处理:
      • 结果转换为大写
      • 只取前20个字符
  3. Flag生成

    # 计算过程
    明文 = "1001"
    MD5("1001") = "B8C37E33DEFDE51CF91E1E1E1E1E1E1E"
    取前20字符 = "B8C37E33DEFDE51CF91E"
    最终flag = "BJDCTF{B8C37E33DEFDE51CF91E}"
    

4.2 案例二:SCTF2019 Who is he

加密逻辑分析:

  1. 发现加密机制

    • TestClick类中的EncryptData变量存储密文
    • 加解密流程:明文 → DES加密 → Base64编码
  2. 密钥分析

    • 关键发现:C#采用Unicode字符存储
    • 密钥和IV:b"1\x002\x003\x004\x00"
  3. 解密脚本

    import base64
    from Crypto.Cipher import DES
    
    enc = "1Tsy0ZGotyMinSpxqYzVBWnfMdUcqCMLu0MA+22Jnp+MNwLHvYuFToxRQr0c+ONZc6Q7L0EAmzbycqobZHh4H23U4WDTNmmXwusW4E+SZjygsntGkO2sGA=="
    enc = base64.b64decode(enc.encode())
    
    key = iv = b"1\x002\x003\x004\x00"
    des = DES.new(key, DES.MODE_CBC, iv)
    dec = des.decrypt(enc)
    print(dec.decode())  # 结果: He_P1ay_Basketball_Very_We11!Hahahahaha!
    

4.3 Cheat Engine在Unity逆向中的应用

CE的Mono功能详解:

  1. 进程附加

    • 使用CE附加到Unity游戏进程
    • 激活Mono功能菜单
  2. 类结构分析

    • 使用"Dissect mono"功能列出所有类和对象实例
    • 查看类字段、方法、继承关系
  3. 关键信息提取

    • 查找加密密钥(如encryptKey字段)
    • 查看对象属性值(玩家数据、游戏状态)
    • 动态修改内存值

5. 高级案例:FlareOn5 Ultimate Minesweeper

5.1 程序结构分析

关键类识别:

  • Program: 程序入口点
  • MainForm: 主窗口类
  • MineField: 雷区逻辑封装
  • SuccessPopup/FailurePopup: 成功/失败弹窗

5.2 核心逻辑逆向

AllocateMemory函数分析:

private void AllocateMemory(MineField mf)
{
    for (uint num = 0U; num < MainForm.VALLOC_NODE_LIMIT; num += 1U)
    {
        for (uint num2 = 0U; num2 < MainForm.VALLOC_NODE_LIMIT; num2 += 1U)
        {
            bool flag = true;
            uint r = num + 1U;  // 行索引
            uint c = num2 + 1U; // 列索引
            
            // 地雷位置判断逻辑
            if (this.VALLOC_TYPES.Contains(this.DeriveVallocType(r, c)))
            {
                flag = false;  // 标记为地雷
            }
            
            mf.GarbageCollect[(int)num2, (int)num] = flag;
        }
    }
}

SquareRevealedCallback回调函数:

private void SquareRevealedCallback(uint column, uint row)
{
    // 地雷检测逻辑(触发即失败)
    if (this.MineField.BombRevealed)
    {
        // 失败处理代码...
    }
    
    // 记录已揭示格子
    this.RevealedCells.Add(row * MainForm.VALLOC_NODE_LIMIT + column);
    
    // 胜利条件检测
    if (this.MineField.TotalUnrevealedEmptySquares == 0)
    {
        // 调用GetKey生成flag
        new SuccessPopup(this.GetKey(this.RevealedCells)).ShowDialog();
    }
}

5.3 Flag生成算法(GetKey函数)

算法步骤:

  1. 种子生成

    revealedCells.Sort();
    Random random = new Random(Convert.ToInt32(
        revealedCells[0] << 20 | 
        revealedCells[1] << 10 | 
        revealedCells[2]));
    
  2. 密钥材料准备

    • 生成32字节随机数组
    • 预定义密文字节数组
  3. 异或解密

    while ((ulong)num < (ulong)((long)array2.Length))
    {
        byte[] array3 = array2;
        uint num2 = num;
        array3[(int)num2] = (array3[(int)num2] ^ array[(int)num]);
        num += 1U;
    }
    

5.4 两种解题方案

方案一:代码修改(推荐)

  1. 删除地雷检测逻辑
  2. 修改胜利条件检测
  3. 保存并运行修改后的程序

修改后的关键代码:

private void SquareRevealedCallback(uint column, uint row)
{
    // 删除地雷检测代码块
    
    this.RevealedCells.Add(row * MainForm.VALLOC_NODE_LIMIT + column);
    
    // 修改条件:点击3个格子即触发
    if (this.RevealedCells.Count >= 3)
    {
        new SuccessPopup(this.GetKey(this.RevealedCells)).ShowDialog();
    }
}

方案二:动态调试

  1. 在GetKey函数返回处设置断点
  2. 启动调试模式运行程序
  3. 触发断点后查看解密结果

6. Mono游戏保护与绕过技术

6.1 常见保护措施

代码混淆

  • 类名、方法名、变量名替换为无意义字符串
  • 反制工具:de4dot等去混淆工具

字符串加密

  • 运行时动态解密字符串常量
  • 应对方法:动态调试或分析解密函数

反调试检测

  • 检测调试器进程存在
  • 检查调试标志位
  • 时间差检测
  • 绕过方法:修改检测代码或使用插件

完整性校验

  • 文件哈希值验证
  • 代码校验和检查
  • 应对:NOP校验代码或修改校验逻辑

6.2 高级绕过技巧

IL代码修改

  • 直接修改IL指令绕过检测
  • 使用dnSpy的IL编辑功能

内存补丁

  • 运行时修改关键内存区域
  • 使用CE进行动态内存修改

7. 总结与最佳实践

7.1 逆向方法论

  1. 环境识别:首先判断打包方式(Mono/IL2CPP)
  2. 文件分析:定位关键dll文件和资源
  3. 静态分析:使用dnSpy进行代码分析
  4. 动态调试:结合CE进行运行时分析
  5. 代码修改:合理修改逻辑实现目标

7.2 工具链配置

  • 主要工具:dnSpy、Cheat Engine
  • 辅助工具:de4dot(去混淆)、PE工具
  • 调试环境:虚拟机隔离测试

7.3 技能要求

  • 熟悉C#和.NET框架
  • 理解Unity引擎架构
  • 掌握加密解密基础知识
  • 具备基本的汇编语言理解能力

通过系统掌握上述技术和方法,可以有效分析和修改大多数使用Mono打包的Unity游戏,为安全研究、CTF竞赛和游戏修改提供坚实的技术基础。

 全屏