ラック・セキュリティごった煮ブログ

セキュリティエンジニアがエンジニアの方に向けて、 セキュリティやIT技術に関する情報を発信していくアカウントです。

【お知らせ】2021年5月10日~リニューアルオープン!今後はこちらで新しい記事を公開します。

株式会社ラックのセキュリティエンジニアが、 エンジニアの方向けにセキュリティやIT技術に関する情報を発信するブログです。(編集:株式会社ラック・デジタルペンテスト部)
当ウェブサイトをご利用の際には、こちらの「サイトのご利用条件」をご確認ください。

デジタルペンテスト部提供サービス:ペネトレーションテスト

Android 12上のFridaでエラーが発生する事象とその解消法

どうも、デジタルペンテストサービス部の魚脳です。

今回は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}

自分の環境以下のようになります:

原因

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を見つけることはできないと推測できます

対策

ARTバージョン330443060がインストール済みの環境上Frida(ver 15.2.2)がエラーを吐く

Fridaのアップデート

執筆時点(2022/10/13)有志によるPRがマージされたfrida-java-bridgeのバージョンは6.2.3になります。これをもとにしたバージョン16.0.0と16.0.1のFridaとfrida-serverにアップデートすればエラーを吐かずに済むはずです。

アップデート後のFrida(ver 16.0.1)が正常に動く

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

ARTロールバック後のFrida(ver 15.2.2)が正常に動く

最後

今回Android 12におけるFridaがエラーを発生する事象とその解消法をまとめてみました。今後こういうエラーを自分で解決できるように、次はFridaのコンパイルデバッグを挑戦したいと思います。