どうも、デジタルペンテストサービス部の魚脳です。
今回はAndroidアプリ診断中実際遭遇したAndroid 12環境下におけるFridaの不具合とその解消法を紹介したいと思います。
事象
9月某日、通常の診断中にFridaを使おうとしたら、以下のエラーを吐きました。しかも診断用端末3台のうち2台が同じ事象が起こしましたので、自分も少しびっくりしました。
{"type":"error","description":"Error: Unable to determine ClassLinker field offsets", "stack":"Error: Unable to determine ClassLinker field offsets\n at Ye (frida/node_modules/frida-java-bridge/lib/android.js:400:1)\n at frida/node_modules/frida-java-bridge/lib/memoize.js:4:1\n at ze (frida/node_modules/frida-java-bridge/lib/android.js:193:1)\n at Oe (frida/node_modules/frida-java-bridge/lib/android.js:16:1)\n at _tryInitialize (frida/node_modules/frida-java-bridge/index.js:29:1)\n at new _ (frida/node_modules/frida-java-bridge/index.js:21:1)\n at Object.4../lib/android (frida/node_modules/frida-java-bridge/index.js:332:1)\n at o (frida/node_modules/browser-pack/_prelude.js:1:1)\n at frida/node_modules/browser-pack/_prelude.js:1:1\n at Object.22.frida-java-bridge (frida/runtime/java.js:1:1)", "fileName":"frida/node_modules/frida-java-bridge/lib/android.js", "lineNumber":400, "columnNumber":1}
自分の環境以下のようになります:
- Frida 15.2.2
- Android 12
原因
Issue内の情報整理
急いで検索をかけてみたら、Fridaのリポジトリに同じエラー情報を含むIssueを発見しました、しかもかなり最近なものになります、 github.com
ざっと情報に目を通したら、まず↓のリプライ内容はどうやら今回エラーになる理由となるらしいです。
Actually the issue is not while getting the ClassLinker field offsets (getArtClassLinkerSpec), rather the issue happens when getting the ClassLinker offset of the Runtime class (at getArtRuntimeSpec).
簡単に要約すると、本来Fridaの一部であるfrida-java-bridgeではART(Android RunTime)のRuntime
構造体内にあるClassLinkerポインターを取得するため、位置的に少し離れたJavaVMポインターからを逆算するアプローチを取りましたが、何らかの原因でそれができなくなり、エラーを吐くことに繋がりました。
次に今回の事象が発生する条件についてなんですが、自分の場合、エラーを発生した端末はPixel6になりますが、Issue内他の方の反応を見る限りPixel以外もGalaxyやXiaomiなど機種の報告があり、すくなくとも機種限定のエラーではなさそうことは判明しました。
さらに読み進むと、OSバージョンについてはほとんどはAndroid 12に集中したことが分かりましたが、全部が全部この事象が起こすわけではなさそうです。実際自分の場合3台のうち2台しか発生してないのもこの理由かもしれません。
This could be related to ROM, or apparently "Google Play System Update".
It feels like Google has been doing several changes on the art runtime lately and pushing them via these "Google Play Services updates
↑の書き込みによると、どうやら Google Playシステムアップデート(2022/7/1) によって、ARTのRuntime
に何かしらの変更が原因で結果的にエラーを吐くことになる可能性がありました。実際手元の端末を確認した結果確かにエラーを吐く2台の端末のGoogle Playシステムアップデートの日付には2022/7/1になっていました(調査時)。
さらに、Android S(12)から、Project MainlineによりARTはOSモジュール化され、独立で更新できるようになりました。ARTモジュールのバージョンは以下のコマンドにて確認ができます。
# com.google.android.artのパスを調べる pm path com.google.android.art
実際、手元にあるエラーを吐く2台の端末は同じくcom.android.art@330443060.apex
ファイルが存在することが確認できました。@
に続く9桁の数字はアップデートのバージョンを指し、のちほどのソースコード検索に役立ちます。
ソースコードから確認
まずIssueに言及されたfrida-java-bridgeのソースコードから確認します。
/* * class Runtime { * ... * gc::Heap* heap_; <-- we need to find this * std::unique_ptr<ArenaPool> jit_arena_pool_; <----- API level >= 24 * std::unique_ptr<ArenaPool> arena_pool_; __ * std::unique_ptr<ArenaPool> low_4gb_arena_pool_; <--|__ API level >= 23 * std::unique_ptr<LinearAlloc> linear_alloc_; \_ * size_t max_spins_before_thin_lock_inflation_; * MonitorList* monitor_list_; * MonitorPool* monitor_pool_; * ThreadList* thread_list_; <--- and these * InternTable* intern_table_; <--/ * ClassLinker* class_linker_; <-/ * SignalCatcher* signal_catcher_; * SmallIrtAllocator* small_irt_allocator_; <------------ API level >= 33 or Android Tiramisu Developer Preview * std::unique_ptr<jni::JniIdManager> jni_id_manager_; <- API level >= 30 or Android R Developer Preview * bool use_tombstoned_traces_; <-------------------- API level 27/28 * std::string stack_trace_file_; <-------------------- API level <= 28 * JavaVMExt* java_vm_; <-- so we find this then calculate our way backwards * ... * } */ ... if (apiLevel >= 33 || getAndroidCodename() === 'Tiramisu') { classLinkerOffset = offset - (4 * pointerSize); jniIdManagerOffset = offset - pointerSize; } else if (apiLevel >= 30 || getAndroidCodename() === 'R') { classLinkerOffset = offset - (3 * pointerSize); jniIdManagerOffset = offset - pointerSize; }
↑はlib/android.js
内_getArtRuntimeSpec
関数から抜粋した内容となります。
コメントの部分からAndroid 11、12にはSmallIrtAllocatorポインターが存在せず、JavaVMのポインターのオフセットからポインターサイズ3個分離れたところにClassLinkerポインターがあります。一方Android 13におけるClassLinkerのオフセットはSmallIrtAllocatorポインターがある分Android 12のときとポインター1個分異なることがわかりました。
次にAndroid API 30以上(Android 11,12,12L,13)のARTのソースコードを比較しながら確認します。
まずはAndroid 11、12と12LのRuntime
構造体を見てみる、どちらもJavaVMポインターからポインター3個分上にClassLinkerポインターがあるため、もちろんいままでのfrida-java-bridgeの通り、ClassLinkerが見つかるはずです。↓はandroid-12.1.0_r27
のソースコードになります。
ClassLinker* class_linker_; SignalCatcher* signal_catcher_; std::unique_ptr<jni::JniIdManager> jni_id_manager_; std::unique_ptr<JavaVMExt> java_vm_;
次にAndroid 13のRuntime
構造体を見てみたら、JavaVMポインターからポインター4個分上にClassLinkerポインターがあるため、こちらもfrida-java-bridge
の通り、ClassLinkerが見つかるはずです。↓はandroid-13.0.0_r8
のソースコードになります。
ClassLinker* class_linker_; SignalCatcher* signal_catcher_; SmallIrtAllocator* small_irt_allocator_; std::unique_ptr<jni::JniIdManager> jni_id_manager_; std::unique_ptr<JavaVMExt> java_vm_;
ではなぜ今回ClassLinkerが見つからないかというと、やはり前に言及した「Google Playシステムアップデート」になるじゃないかと思いましたので、引き続き調査を進みました。
そこで前節得られたARTモジュールのバージョン(330443060)を検索かけた結果、AndroidのGit上t_frc_art_330443060
というgitのタグが存在することがわかりました。手動でタグt_frc_art_330443060
のソースコード切り替え、runtime.h
を確認してみたら、runtime
構造体はまさに上記のAndroid 13と一致することを確認できました。
よって、t_frc_art_330443060
をもとにしたアップデートデータがインストールされたAndroid12端末上では、現行のFridaが正確にClassLinkerを見つけることはできないと推測できます。
対策
Fridaのアップデート
執筆時点(2022/10/13)有志によるPRがマージされたfrida-java-bridgeのバージョンは6.2.3になります。これをもとにしたバージョン16.0.0と16.0.1のFridaとfrida-serverにアップデートすればエラーを吐かずに済むはずです。
ARTモジュールのロールバック
下記のコマンドにより(おそらく)初期のARTモジュールに戻すことが可能です
$ pm uninstall com.google.android.art
実行前と後ARTの変化
$ pm path com.google.android.art package:/data/apex/active/com.android.art@330443060.apex $ pm path com.google.android.art package:/system/apex/com.google.android.art.apex
最後
今回Android 12におけるFridaがエラーを発生する事象とその解消法をまとめてみました。今後こういうエラーを自分で解決できるように、次はFridaのコンパイルやデバッグを挑戦したいと思います。