reversing-flutter-apps について
Flutter モバイルアプリの調査手順をまとめたスキルです。Dart AOT の構造復元、通信の傍受、実行時検査を扱い、リリースビルドのロジックや API の特定に役立ちます。
主な機能
- 01Flutter のビルド形式の識別
- 02blutter による Dart シンボル復元
- 03オブジェクトプールと逆アセンブル結果の確認
- 04Flutter 通信の転送と傍受
- 05Frida による Dart 関数のフック
活用シーン
- 01Flutter リリースビルドでのエンドポイント特定
- 02Burp や mitmproxy に通信が出ない場合の調査
- 03リクエスト構築関数やトークン処理の検査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ trilwu/ secskills --skill reversing-flutter-appsインストール後、Claude Code で/reversing-flutter-appsを入力して読み込まれたか確認します。
詳細
Flutter アプリのリバースエンジニアリング
Flutter は、標準的なモバイルワークフローの両方を一度に崩します。Dart は AOT コンパイルされてネイティブスナップショットになるため、jadx で逆コンパイルする dex がありません。また、Flutter エンジンは独自の BoringSSL と信頼ストアを同梱しているため、システムプロキシとユーザー CA ストアの両方を無視し、通信を傍受するプロキシには何も表示されません。どちらの問題も症状からはすぐに分からないため、何が起きているのか気づくまでに何時間も費やしてしまうことがあります。
使用する場合
- APK を
jadxで確認すると、ほとんど空のMainActivityしかなく、ほかにもほとんど何もない - APK に
lib/arm64-v8a/libflutter.soとlibapp.so、またはassets/flutter_assets/が含まれている - IPA に
Frameworks/App.frameworkとFrameworks/Flutter.frameworkが含まれている - アプリが明らかにネットワーク通信を行っているのに、Burp、mitmproxy、または Charles に通信がまったく表示されない
- Dart のクラス名やメソッド名を復元する、またはリリースビルドから API レイヤーを見つける必要がある
使用しない場合
- 一般的な Java/Kotlin Android アプリ —
testing-mobile-applicationsを使用してください - React Native(
index.android.bundle、Hermes)—reversing-react-native-appsを使用してください - Unity(
global-metadata.dat、libil2cpp.so)—reversing-unity-il2cppを使用してください - アプリが通信するバックエンド — 通信を確認できるようになったら
testing-apisを使用してください - 一般的なネイティブ
.soのリバースエンジニアリング —analyzing-binariesを使用してください
まずビルドを特定する
ビルドモードによって、この後の手順がすべて決まります。ツールをインストールする前に確認してください。
unzip -l target.apk | rg 'libflutter|libapp|flutter_assets|kernel_blob'
| 確認できるもの | ビルド | 意味 |
|---|---|---|
libapp.so + libflutter.so | AOT リリース | 一般的なケースです。Dart がネイティブスナップショットにコンパイルされています。このスキルの主な対象です |
assets/flutter_assets/kernel_blob.bin | デバッグ / JIT | はるかに簡単です。Dart カーネルバイトコードがあり、VM サービスに接続できる可能性があります |
App.framework/App(iOS) | AOT リリース | libapp.so と同様です。復号済み IPA から抽出してください |
arm64-v8a のみ存在 | — | 想定どおりです。blutter は arm64 を対象としています。armeabi-v7a のサポートは弱めです |
# Confirm the engine version — it gates which tools work
strings lib/arm64-v8a/libflutter.so | rg -m5 'Flutter|Dart VM version|dart-sdk'
kernel_blob.bin がある場合は、そこで作業を止め、代わりにそれを展開してください。デバッグビルドにははるかに多くの情報が残っており、Dart VM サービスが有効ならライブ検査もできます。リリースビルドは難しいケースであり、以降はリリースビルドを前提としています。
blutter で Dart の構造を復元する
blutter は Dart AOT スナップショットを解析し、クラス名、メソッド名、フィールド名、およびオブジェクトプールを復元します。これにより、解読不能な arm64 の逆アセンブル結果が、アプリ内をたどれる形になります。
# blutter needs BOTH libraries: libapp.so is the snapshot, libflutter.so
# identifies the exact Dart version used to compile it
unzip -j target.apk 'lib/arm64-v8a/*' -d ./libs
python3 blutter.py ./libs ./out
実際に使用する出力:
| 成果物 | 用途 |
|---|---|
pp.txt | オブジェクトプールのダンプ。文字列定数、URL、キー、エンドポイントが含まれます。まずこれを読んでください |
objs.txt | 復元された Dart オブジェクトとクラス |
asm/ | Dart シンボルを適用したライブラリごとの逆アセンブル結果 |
ida_script/ | IDA にシンボルを読み込みます。Ghidra 用の同等のものもあります |
blutter_frida.js | 復元された Dart 関数を対象とする、生成済みの Frida フック |
直面する問題は、Dart のバージョン不一致です。 blutter は特定の Dart SDK に対してコンパイルされており、スナップショットの形式はバージョンごとに変わります。解析時にエラーが発生した場合は、別のスナップショットを試すのではなく、アプリのエンジンに一致するバージョンの Dart SDK に対して blutter をビルドしてください。まず libflutter.so からバージョンを確認してください。また、blutter が対応するまで、新しい Flutter リリースでの最初の実行は失敗することを想定してください。
難読化されたビルド(flutter build --obfuscate --split-debug-info=...)では、Dart の名前が削除されます。blutter は呼び出しグラフ、オブジェクトプール、文字列定数などの構造を引き続き復元できますが、名前は残りません。pp.txt を手掛かりにしてください。URL、エンドポイントパス、エラー文字列は難読化後も残り、通常は API レイヤーを見つけるのに十分です。そもそも必要だったのは、API レイヤーの特定です。
トラフィックを確認する
別々の問題が2つあり、どちらも解決する必要があります。片方だけを解決しても、プロキシは空のままで、原因も分かりません。
問題1 — Flutterはシステムプロキシを無視します。 DartのHTTPクライアントはAndroid/iOSのプロキシ設定を読み取らないため、Wi-Fiプロキシを設定しても何も起こりません。代わりにネットワーク層でトラフィックを強制的に経由させます。
# Redirect transparently on the device/emulator
adb shell su -c 'iptables -t nat -A OUTPUT -p tcp --dport 443 -j DNAT --to <proxy-ip>:8080'
# or use a VPN-based tunnel (tun2socks / a VPN-mode proxy app)
問題2 — Flutterには独自のCAストアがあります。 CAをユーザー証明書として、あるいはシステム証明書としてインストールしても、Flutterがそれを信頼するわけではありません。機能する方法は2つあります。
# A. reFlutter — patches the Flutter engine to trust your proxy and to
# reroute traffic. Repackage and re-sign afterwards.
reflutter target.apk # prompts for the proxy IP
# outputs a patched APK; then:
apksigner sign --ks debug.keystore release.RE.apk
# B. Frida — hook the certificate check inside libflutter.so at runtime.
# ssl_verify_peer_cert is not exported, so scripts locate it by pattern
# scanning; the pattern is engine-version specific.
frida -U -f com.target.app -l flutter-ssl-bypass.js
再パッケージできる場合はreFlutterを優先してください(スナップショットのダンプとソケットレベルのログ取得も可能になります)。アプリに再パッケージを検知する整合性チェックがある場合や、再署名できない場合はFridaを優先してください。どちらも失敗した場合は、blutterが復元したDart HTTPクライアントの関数をフックするのが代替策です。プロトコルレベルの忠実性は失われますが、リクエストとレスポンスは確認できます。
アプリを調査する
シンボルとトラフィックを取得したら、次の手順に進みます。
pp.txtから始めます。 ベースURL、エンドポイントのパス、ヘッダー名、埋め込みキー素材はオブジェクトプールにあります。通常は数分でAPIの構成を把握できます。- 復元した関数をフックします。
blutter_frida.jsを出発点に、リクエストビルダー、認証トークンのハンドラー、ローカル暗号処理の周辺で引数と戻り値をダンプします。 - プラットフォームチャネルを確認します。 Flutterアプリは生体認証、キーストア、決済をネイティブコードに委ねます。これらは通常のJava/KotlinまたはSwiftなので、
testing-mobile-applicationsに引き継ぎます。 - 続いて、
testing-apisを使ってAPIを適切にテストします。 ここまでの手順はすべてリクエストを確認するためのものです。脆弱性は通常、その先にあります。
受け入れてはいけない言い訳
- 「プロキシに何も表示されない。だからアプリはネットワークを使っていないはずだ。」 ネットワークは使っています。あなたのプロキシを使っていないだけです。
- 「CAをシステム証明書としてインストールした。これでTLSインターセプトが機能するはずだ。」 Flutterはシステムストアを参照しません。
- 「jadxに何も表示されない。だからアプリは高度に難読化されているはずだ。」 dex内に確認できるものはありません。アプリ本体は
libapp.soにあります。 - 「blutterが失敗した。だからこのビルドは解析できない。」 ほとんどの場合、Dartのバージョンが一致していないことが原因です。エンジンのバージョンを確認し、それに合わせてblutterを再ビルドしてください。
- 「難読化されているから、文字列は消えているはずだ。」 名前は消えていますが、オブジェクトプールは残っています。
pp.txtを確認してください。 - 「arm64を手作業でリバースすればいい。」 シンボルのないDart AOT出力を読むには、非常に時間がかかります。まずシンボルを復元してください。それが作業のすべてです。
参考資料
testing-mobile-applications— より広範なモバイルアセスメントとネイティブチャネルtesting-apis— トラフィックを確認できるようになった後のバックエンドのテストanalyzing-binaries— エンジン自体のより詳細なネイティブ解析- blutter、reFlutter、Frida/objection、jadx、apktool、apksigner