一次完整的 Unity Mono 安卓游戏逆向:Frida Hook 绕过碰撞死亡判定

Android安卓游戏Unity Mono 游戏逆向实战:从 APK 到 Hook libmono.so 绕过死亡判定

前言

最近在分析一款 极限摩托基于手机重力控制的 Unity 游戏

  • 通过手机 前后翻转控制角色
  • 人物只要 发生碰撞(翻车 / 头部触地)就会立即失败
  • 没有明显的数值判定,属于典型的 物理 + 碰撞触发死亡

本文完整记录了我 从 APK 分析 → 判断 Unity 架构 → Hook Mono Runtime → 精准拦截死亡函数 的全过程。

最终效果:

人物发生碰撞也不会死亡,游戏可正常继续运行。


一、实验环境

游戏截图


我使用的设备 & 工具版本

  • Android 10 真机(arm64)
  • Frida版本:17.5.2
  • Frida Server版本frida-server-17.5.1-android-arm64
  • Python版本:3.10.0
  • 手机不方便可以使用手机模拟器

Frida Server 启动

上传到手机:

bash 复制代码
adb push frida-server-17.5.1-android-arm64 /data/local/tmp/
bash 复制代码
adb shell chmod +x /data/local/tmp/frida-server-17.5.1-android-arm64

启动frida-server-17.5.1-android-arm64

bash 复制代码
adb shell /data/local/tmp/frida-server-17.5.1-android-arm64

二、从 APK 入手:判断游戏架构

1️⃣ 解包 APK

bash 复制代码
apktool d trial.apk

重点关注:

  • AndroidManifest.xml
  • lib/armeabi-v7a / lib/arm64-v8a
  • assets/

2️⃣ 查看 so 文件(关键判断点)

lib/arm64-v8a/ 目录下发现:

复制代码
libmono.so
libu.so

同时:

  • ❌ 不存在 libil2cpp.so
  • ❌ 不存在 libunity.so(部分 Mono 游戏本来就没有)

✅ 结论

这是一个 Unity Mono 架构游戏(非 IL2CPP)


三、确认 C# 脚本存在

在:

复制代码
assets/bin/Data/Managed/

中可以看到:

复制代码
Assembly-CSharp.dll
UnityEngine.dll
Assembly-CSharp.dll 可以使用ILSpy工具打开,可以直接看到关键代码Bone::OnCollisionEnter,进行分析

说明:

  • 游戏逻辑由 C# 编写
  • 运行在 Mono VM
  • 实际执行发生在 libmono.so

四、为什么选择 Hook libmono.so

❌ 不直接修改 DLL 的原因

  • 需要重打包 APK
  • 可能触发签名 / 完整性校验
  • 不利于动态调试

✅ Hook Mono Runtime 的优势

  • 不修改 APK
  • 可实时观察 C# 函数调用
  • 能拦截 Unity 生命周期 / 碰撞函数

五、锁定关键函数:mono_runtime_invoke

Mono 执行模型简化图

复制代码
Unity 物理 / 碰撞
        ↓
C# 脚本 (Update / OnCollisionEnter)
        ↓
IL Code
        ↓
mono_runtime_invoke
        ↓
Native 执行

所有 C# 方法最终都会经过 mono_runtime_invoke

这意味着:

Hook 一个函数,就能观察并控制所有 C# 方法调用。


六、附加进程并 Hook

1️⃣ 查找游戏 PID

bash 复制代码
adb shell ps -A | grep com.galapagossoft.trial

输出示例:

复制代码
u0_a236   19480   ...   com.galapagossoft.trial

2️⃣ Frida Attach

bash 复制代码
frida -U -p 19480 -l mono_base.js

七、Hook 脚本(核心代码)

js 复制代码
console.log("[*] mono_base.js loaded");

var mono = Process.findModuleByName("libmono.so");
if (!mono) {
    console.log("libmono.so not found");
    return;
}

var mono_method_get_name_ptr = mono.getExportByName("mono_method_get_name");
var mono_runtime_invoke_ptr = mono.getExportByName("mono_runtime_invoke");
var mono_method_get_class_ptr = mono.getExportByName("mono_method_get_class");
var mono_class_get_name_ptr = mono.getExportByName("mono_class_get_name");

var mono_method_get_name = new NativeFunction(
    mono_method_get_name_ptr, "pointer", ["pointer"]
);

var mono_method_get_class = new NativeFunction(
    mono_method_get_class_ptr, "pointer", ["pointer"]
);

var mono_class_get_name = new NativeFunction(
    mono_class_get_name_ptr, "pointer", ["pointer"]
);

var orig_mono_runtime_invoke = new NativeFunction(
    mono_runtime_invoke_ptr,
    "pointer",
    ["pointer", "pointer", "pointer", "pointer"]
);

Interceptor.replace(
    mono_runtime_invoke_ptr,
    new NativeCallback(function (method, obj, params, exc) {

        var klass = mono_method_get_class(method);
        var className = mono_class_get_name(klass).readCString();
        var methodName = mono_method_get_name(method).readCString();

        // 关键:拦截碰撞触发
        if (className === "Bone" && methodName === "OnCollisionEnter") {
            console.log("[BLOCK] " + className + "::" + methodName);
            return ptr(0);
        }

        return orig_mono_runtime_invoke(method, obj, params, exc);

    }, "pointer", ["pointer", "pointer", "pointer", "pointer"])
);

八、分析过程与关键突破

1️⃣ 先打印观察调用

日志中依次出现:

复制代码
Fork::Update
Fork::OnCollisionStay
FailController::OnGUI
Bone::OnCollisionEnter

2️⃣ 排除错误目标

  • FailController::OnGUI

    👉 只是 UI 显示失败画面,不是死亡原因

  • FailController::Start

    👉 失败后的初始化逻辑


3️⃣ 真正的死亡触发点

text 复制代码
Bone::OnCollisionEnter

这正好符合游戏机制:

人物因重力翻转发生碰撞 → 立即失败


九、最终效果

成功拦截后:

  • 人物发生碰撞 不再死亡
  • 物理系统正常
  • 游戏流程可继续

无需修改 APK

无需重打包

精准绕过失败判定


十、总结

本文完整展示了一条 Unity Mono 游戏逆向的通用思路

  1. 从 APK 判断 Unity 架构
  2. 确认 Mono 而非 IL2CPP
  3. 锁定 libmono.so
  4. Hook mono_runtime_invoke
  5. 通过 Runtime 级分析定位关键 C# 方法
  6. 精准拦截 OnCollisionEnter 实现逻辑绕过

理解引擎执行模型,比盲目改代码更重要。


后记

这套方法同样适用于:

  • Unity Mono 手游
  • 碰撞 / 判定 / 失败逻辑分析
  • 无源码、无符号环境下的动态逆向

后续我会继续分享更多 Unity / Mono / Frida 实战分析。

相关推荐
千里马学框架15 小时前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台16 小时前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone16 小时前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc17 小时前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo18 小时前
Kotlin 协程中的 Job 结构化并发与取消
android
sun00770019 小时前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼19 小时前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
魔士于安19 小时前
unity 丑陋哥布林 哥布林有humannoid 动画没 带动画
unity·游戏引擎·材质·贴图·模型
AFinalStone20 小时前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen20 小时前
属性动画原理与高级动画实现
android·kotlin·canvas