Android 安卓游戏逆向实战:Unity Mono 从 APK 到 Frida Hook 绕过死亡判定
作者通过分析一款基于 Unity 的手机游戏 APK 文件,利用 Frida 工具 Hook Mono Runtime 拦截游戏中的碰撞触发死亡函数(Bone::OnCollisionEnter),成功实现人物碰撞后不死亡的效果。 2025-12-28 05:54:4 Author: www.freebuf.com(查看原文) 阅读量:0 收藏

freeBuf

主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

前言

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

  • 通过手机 前后翻转控制角色

  • 人物只要 发生碰撞(翻车 / 头部触地)就会立即失败

  • 没有明显的数值判定,属于典型的 物理 + 碰撞触发死亡

本文完整记录了我 从 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 启动

上传到手机:

adb push frida-server-17.5.1-android-arm64 /data/local/tmp/
adb shell chmod +x /data/local/tmp/frida-server-17.5.1-android-arm64

启动frida-server-17.5.1-android-arm64

adb shell /data/local/tmp/frida-server-17.5.1-android-arm64

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

1、解包 APK

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

adb shell ps -A | grep com.galapagossoft.trial

输出示例:

u0_a236   19480   ...   com.galapagossoft.trial

2、Frida Attach

frida -U -p 19480 -l mono_base.js

七、Hook 脚本(核心代码)

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、真正的死亡触发点

Bone::OnCollisionEnter

这正好符合游戏机制:

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


九、最终效果

成功拦截后:

  • 人物发生碰撞 不再死亡

  • 物理系统正常

  • 游戏流程可继续

无需修改 APK
无需重打包
精准绕过失败判定


十、总结

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

  1. 从 APK 判断 Unity 架构

  2. 确认 Mono 而非 IL2CPP

  3. 锁定 libmono.so

  4. Hookmono_runtime_invoke

  5. 通过 Runtime 级分析定位关键 C# 方法

  6. 精准拦截OnCollisionEnter实现逻辑绕过

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


后记

这套方法同样适用于:

  • Unity Mono 手游

  • 碰撞 / 判定 / 失败逻辑分析

  • 无源码、无符号环境下的动态逆向

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

已在FreeBuf发表 0 篇文章

本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)


文章来源: https://www.freebuf.com/articles/game/464081.html
如有侵权请联系:admin#unsafe.sh