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 啓動
上傳到手機:
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.xmllib/armeabi-v7a/lib/arm64-v8aassets/
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 遊戲逆向的通用思路:
- 從 APK 判斷 Unity 架構
- 確認 Mono 而非 IL2CPP
- 鎖定 libmono.so
- Hook
mono_runtime_invoke - 通過 Runtime 級分析定位關鍵 C# 方法
- 精準攔截
OnCollisionEnter實現邏輯繞過
理解引擎執行模型,比盲目改代碼更重要。
後記
這套方法同樣適用於:
- Unity Mono 手遊
- 碰撞 / 判定 / 失敗邏輯分析
- 無源碼、無符號環境下的動態逆向
後續我會繼續分享更多 Unity / Mono / Frida 實戰分析。