清汤泡面 發表於 2025-12-28 13:26:00

Unity Mono 安卓游戏逆向实战:APK 分析 + Frida Hook 绕过死亡判定

<h1 id="android安卓游戏unity-mono-游戏逆向实战从-apk-到-hook-libmonoso-绕过死亡判定">Android安卓游戏Unity Mono 游戏逆向实战:从 APK 到 Hook libmono.so 绕过死亡判定</h1>
<h2 id="前言">前言</h2>
<p>最近在分析一款 <strong>极限摩托基于手机重力控制的 Unity 游戏</strong>:</p>
<p><img src="https://img2024.cnblogs.com/blog/2145203/202512/2145203-20251228132246333-1415348309.png" alt="game" loading="lazy"></p>
<ul>
<li>通过手机 <strong>前后翻转控制角色</strong></li>
<li>人物只要 <strong>发生碰撞(翻车 / 头部触地)就会立即失败</strong></li>
<li>没有明显的数值判定,属于典型的 <strong>物理 + 碰撞触发死亡</strong></li>
</ul>
<p>本文完整记录了我 <strong>从 APK 分析 → 判断 Unity 架构 → Hook Mono Runtime → 精准拦截死亡函数</strong> 的全过程。</p>
<p><strong>最终效果:</strong></p>
<blockquote>
<p>人物发生碰撞也不会死亡,游戏可正常继续运行。</p>
</blockquote>
<hr>
<h2 id="一实验环境">一、实验环境</h2>
<h3 id="我使用的设备--工具版本">我使用的设备 &amp; 工具版本</h3>
<ul>
<li><strong>Android 10 真机(arm64)</strong></li>
<li><strong>Frida版本</strong>:17.5.2</li>
<li><strong>Frida Server版本</strong>:<code>frida-server-17.5.1-android-arm64</code></li>
<li><strong>Python版本</strong>:3.10.0</li>
<li><em>手机不方便可以使用手机模拟器</em></li>
</ul>
<h3 id="frida-server-启动">Frida Server 启动</h3>
<p>上传到手机:</p>
<pre><code class="language-bash">adb push frida-server-17.5.1-android-arm64 /data/local/tmp/
</code></pre>
<pre><code class="language-bash">adb shell chmod +x /data/local/tmp/frida-server-17.5.1-android-arm64
</code></pre>
<p>启动frida-server-17.5.1-android-arm64</p>
<pre><code class="language-bash">adb shell /data/local/tmp/frida-server-17.5.1-android-arm64
</code></pre>
<hr>
<h2 id="二从-apk-入手判断游戏架构">二、从 APK 入手:判断游戏架构</h2>
<h3 id="1️⃣-解包-apk">1️⃣ 解包 APK</h3>
<pre><code class="language-bash">apktool d trial.apk
</code></pre>
<p>重点关注:</p>
<ul>
<li><code>AndroidManifest.xml</code></li>
<li><code>lib/armeabi-v7a</code> / <code>lib/arm64-v8a</code></li>
<li><code>assets/</code></li>
</ul>
<hr>
<h3 id="2️⃣-查看-so-文件关键判断点">2️⃣ 查看 so 文件(关键判断点)</h3>
<p>在 <code>lib/arm64-v8a/</code> 目录下发现:</p>
<pre><code>libmono.so
libu.so
</code></pre>
<p>同时:</p>
<ul>
<li>❌ 不存在 <code>libil2cpp.so</code></li>
<li>❌ 不存在 <code>libunity.so</code>(部分 Mono 游戏本来就没有)</li>
</ul>
<h3 id="-结论">✅ 结论</h3>
<blockquote>
<p><strong>这是一个 Unity Mono 架构游戏(非 IL2CPP)</strong></p>
</blockquote>
<hr>
<h2 id="三确认-c-脚本存在">三、确认 C# 脚本存在</h2>
<p>在:</p>
<pre><code>assets/bin/Data/Managed/
</code></pre>
<p>中可以看到:</p>
<pre><code>Assembly-CSharp.dll
UnityEngine.dll
</code></pre>
<h4 id="assembly-csharpdll-可以使用ilspy工具打开可以直接看到关键代码boneoncollisionenter进行分析">Assembly-CSharp.dll 可以使用ILSpy工具打开,可以直接看到关键代码Bone::OnCollisionEnter,进行分析</h4>
<p>说明:</p>
<ul>
<li>游戏逻辑由 <strong>C# 编写</strong></li>
<li>运行在 <strong>Mono VM</strong></li>
<li>实际执行发生在 <strong>libmono.so</strong></li>
</ul>
<hr>
<h2 id="四为什么选择-hook-libmonoso">四、为什么选择 Hook libmono.so</h2>
<h3 id="-不直接修改-dll-的原因">❌ 不直接修改 DLL 的原因</h3>
<ul>
<li>需要重打包 APK</li>
<li>可能触发签名 / 完整性校验</li>
<li>不利于动态调试</li>
</ul>
<h3 id="-hook-mono-runtime-的优势">✅ Hook Mono Runtime 的优势</h3>
<ul>
<li>不修改 APK</li>
<li>可实时观察 C# 函数调用</li>
<li>能拦截 <strong>Unity 生命周期 / 碰撞函数</strong></li>
</ul>
<hr>
<h2 id="五锁定关键函数mono_runtime_invoke">五、锁定关键函数:mono_runtime_invoke</h2>
<h3 id="mono-执行模型简化图">Mono 执行模型简化图</h3>
<pre><code>Unity 物理 / 碰撞
      ↓
C# 脚本 (Update / OnCollisionEnter)
      ↓
IL Code
      ↓
mono_runtime_invoke
      ↓
Native 执行
</code></pre>
<blockquote>
<p><strong>所有 C# 方法最终都会经过 <code>mono_runtime_invoke</code></strong></p>
</blockquote>
<p>这意味着:</p>
<blockquote>
<p>Hook 一个函数,就能观察并控制所有 C# 方法调用。</p>
</blockquote>
<hr>
<h2 id="六附加进程并-hook">六、附加进程并 Hook</h2>
<h3 id="1️⃣-查找游戏-pid">1️⃣ 查找游戏 PID</h3>
<pre><code class="language-bash">adb shell ps -A | grep com.galapagossoft.trial
</code></pre>
<p>输出示例:</p>
<pre><code>u0_a236   19480   ...   com.galapagossoft.trial
</code></pre>
<hr>
<h3 id="2️⃣-frida-attach">2️⃣ Frida Attach</h3>
<pre><code class="language-bash">frida -U -p 19480 -l mono_base.js
</code></pre>
<hr>
<h2 id="七hook-脚本核心代码">七、Hook 脚本(核心代码)</h2>
<pre><code class="language-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" &amp;&amp; methodName === "OnCollisionEnter") {
            console.log(" " + className + "::" + methodName);
            return ptr(0);
      }

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

    }, "pointer", ["pointer", "pointer", "pointer", "pointer"])
);
</code></pre>
<hr>
<h2 id="八分析过程与关键突破">八、分析过程与关键突破</h2>
<h3 id="1️⃣-先打印观察调用">1️⃣ 先打印观察调用</h3>
<p>日志中依次出现:</p>
<pre><code>Fork::Update
Fork::OnCollisionStay
FailController::OnGUI
Bone::OnCollisionEnter
</code></pre>
<hr>
<h3 id="2️⃣-排除错误目标">2️⃣ 排除错误目标</h3>
<ul>
<li>
<p><code>FailController::OnGUI</code><br>
👉 只是 UI 显示失败画面,不是死亡原因</p>
</li>
<li>
<p><code>FailController::Start</code><br>
👉 失败后的初始化逻辑</p>
</li>
</ul>
<hr>
<h3 id="3️⃣-真正的死亡触发点">3️⃣ 真正的死亡触发点</h3>
<pre><code class="language-text">Bone::OnCollisionEnter
</code></pre>
<p>这正好符合游戏机制:</p>
<blockquote>
<p><strong>人物因重力翻转发生碰撞 → 立即失败</strong></p>
</blockquote>
<hr>
<h2 id="九最终效果">九、最终效果</h2>
<p>成功拦截后:</p>
<ul>
<li>人物发生碰撞 <strong>不再死亡</strong></li>
<li>物理系统正常</li>
<li>游戏流程可继续</li>
</ul>
<p>✅ <strong>无需修改 APK</strong><br>
✅ <strong>无需重打包</strong><br>
✅ <strong>精准绕过失败判定</strong></p>
<hr>
<h2 id="十总结">十、总结</h2>
<p>本文完整展示了一条 <strong>Unity Mono 游戏逆向的通用思路</strong>:</p>
<ol>
<li>从 APK 判断 Unity 架构</li>
<li>确认 Mono 而非 IL2CPP</li>
<li>锁定 libmono.so</li>
<li>Hook <code>mono_runtime_invoke</code></li>
<li>通过 Runtime 级分析定位关键 C# 方法</li>
<li>精准拦截 <code>OnCollisionEnter</code> 实现逻辑绕过</li>
</ol>
<blockquote>
<p><strong>理解引擎执行模型,比盲目改代码更重要。</strong></p>
</blockquote>
<hr>
<h2 id="后记">后记</h2>
<p>这套方法同样适用于:</p>
<ul>
<li>Unity Mono 手游</li>
<li>碰撞 / 判定 / 失败逻辑分析</li>
<li>无源码、无符号环境下的动态逆向</li>
</ul>
<p>后续我会继续分享更多 Unity / Mono / Frida 实战分析。</p><br><br>
来源:https://www.cnblogs.com/zimingxiayi/p/19411751
頁: [1]
查看完整版本: Unity Mono 安卓游戏逆向实战:APK 分析 + Frida Hook 绕过死亡判定