re-hybrid-app について
re-hybrid-appは、FlutterとReact Nativeアプリを調べるリバースエンジニアリングスキルです。アプリのロジック復元やJavaScriptとネイティブコード間の通信分析に向け、静的解析と実行時観察でブリッジ上のデータ交換を特定します。
主な機能
- 01FlutterとReact Nativeアプリの識別
- 02DartスナップショットとJSバンドルの復元
- 03Hermesバイトコードの解析
- 04JavaScriptとネイティブ間の呼び出し特定
- 05プラットフォームチャネルの実行時観察
活用シーン
- 01FlutterアプリのDartロジック復元
- 02React NativeのHermesバンドル逆アセンブル
- 03MethodChannelやNativeModules経由の追跡
- 04ハイブリッドアプリの機密データ交換の調査
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ dslsdzc/ rev-skills --skill re-hybrid-appインストール後、Claude Code で/re-hybrid-appを入力して読み込まれたか確認します。
詳細
ハイブリッドアプリのリバースエンジニアリング(Flutter / React Native)
使用する場合 / 使用しない場合
- 使用する場合:アプリ内で libflutter.so / libapp.so(Flutter)または assets/index.android.bundle(React Native)が見つかった場合
- 使用する場合:ハイブリッドフレームワークのビジネスロジック(Dart スナップショット / JS bundle)を復元する必要がある場合
- 使用する場合:JS↔ネイティブブリッジ(MethodChannel / NativeModules)でやり取りされる機密データの場所を特定する必要がある場合
- 使用しない場合:純粋な Java/Kotlin ネイティブアプリ([[re-apk]] を使用)
- 使用しない場合:純粋なネイティブ so のロジック([[re-binary-core]] を使用)
- 使用しない場合:ランタイムフックが必要な場合——まず静的に特定してから [[re-frida]] を使用
ツールの準備
静的解析はサンドボックスなしで実施できます([[re-analyze/platform-tips]] の最優先原則)。動的解析(reFlutter による再パッケージ化 / 実行)は、管理されたデバイス / エミュレーターのスナップショット内で行います。すべてのツールは、使用前に検証してください。
エンジンの識別 —— jadx / unzip([[re-apk]] を再利用)
unzip -l app.apkで assets/ と lib/ の構造を確認すれば、エンジンを判別できます。OS ごとのインストール方法は [[re-apk]] のツールの準備を参照してください
blutter —— Flutter AOT スナップショットの静的復元(libapp.so)
- インストール:
git clone https://github.com/worawit/blutter && cd blutter——C++20 プロジェクトです。比較的新しいコンパイラー(g++>=13 / Clang>=16)と cmake/ninja/pkg-config/libicu/libcapstone + pyelftools/requests が必要です- Debian/Ubuntu:
apt install python3-pyelftools python3-requests git cmake ninja-build build-essential pkg-config libicu-dev libcapstone-dev - Fedora:
dnf install gcc-c++ clang cmake ninja-build pkgconf-pkg-config libicu-devel capstone-devel python3-pyelftools python3-requests git - Arch:
pacman -S gcc cmake ninja pkgconf icu capstone python-pyelftools python-requests git - macOS:
brew install cmake ninja pkg-config icu4c capstone+pip3 install pyelftools requests
- Debian/Ubuntu:
- 使い方:
python3 blutter.py libapp.so <输出目录>(Dart のバージョンを自動検出し、必要に応じて Dart ソースコードを自動ダウンロードしてエンジンをコンパイルします)——現在は Android arm64 の libapp.so と比較的新しい Dart バージョンに対応しています - 成果物:
asm/(シンボル付き逆アセンブル)、objs.txt(オブジェクトプール内のオブジェクトの完全なダンプ)、pp.txt(オブジェクトプール内のすべての Dart オブジェクト)、blutter_frida.js(Frida スクリプトのテンプレート) - 検証:出力ディレクトリに
pp.txtが生成され、対象アプリのクラス名が含まれていること
reFlutter —— Flutter の動的解析(再パッケージ化 + トラフィックの傍受)
- インストール:
pip3 install reflutter(Python 3、frida-tools に依存) - 使い方:
reflutter main.apk→ Burp プロキシの IP を入力 →release.RE.apkを生成 → uber-apk-signer で再署名(java -jar uber-apk-signer.jar --allowResign -a release.RE.apk)→ インストールして実行 - 成果物:実行後、
adb -d shell "cat /data/data/<包名>/dump.dart" > dump.dartを実行します(クラス / 関数の一覧 + コードのオフセット) - 検証:
reflutter -hでバージョンと使い方が表示されること。再パッケージ化後にインストールして実行でき、dump.dart が生成されること - 注意:Flutter >= 3.24 では、デバイス上でプロキシを手動設定する必要があります(
adb shell settings put global http_proxy <ip:port>)
dart ツールチェーン(スナップショット構造の理解用、任意)
- Arch:
pacman -S dart(extra 公式リポジトリ) - Debian/Ubuntu / Fedora:公式リポジトリに dart パッケージがありません → Flutter SDK に付属するものを使用します(
snap install flutterまたは公式サイトの SDK) - macOS:
brew install dart(またはbrew install --cask flutter);Windows:Flutter SDK zip - 検証:
dart --version
RN bundle の復元 —— hermes-dec(Hermes バイトコード)
- インストール:
pip3 install hermes-dec(Python 3、標準ライブラリのみ);Ubuntu ではsnap install hermes-decも利用できます(snap 版のコマンドにはhermes-dec.の接頭辞が付きます。例:hermes-dec.hbc-disassembler。pip 版には接頭辞がありません);Arch AUR ではyay -S hermes-dec - コマンド:
hbc-file-parser(HBC ヘッダー / バージョン / 関数テーブルを解析)、hbc-disassembler(バイトコード → .hasm アセンブリ)、hbc-decompiler(→ 疑似 JS) - 検証:
hbc-decompiler --helpが使用できること。サンプルファイルで 3 つの手順を実行できること - プレーンテキストの JS bundle の整形:
pip install jsbeautifier(Python 3、js-beautify bundle.js)
操作手順
順番に実行し、各ステップの成果物(so / bundle / hasm / 逆コンパイル出力 + sha256)を保存します。
-
エンジンの識別:
unzip -l app.apk | grep -E 'libapp|libflutter|assets/|bundle' file lib/arm64-v8a/libapp.so file assets/index.android.bundle # Hermes: "Hermes JavaScript bytecode, version XX"- Flutter:
lib/<abi>/libapp.so(AOT スナップショット)+libflutter.so(エンジン)+assets/flutter_assets/ - RN:
assets/index.android.bundle(プレーンテキストの JS または Hermes HBC);RN 0.70 以降はデフォルトで Hermes - その他:WebView シェル(assets/*.html)/ Cordova / uni-app——WebView の手順で処理
- 識別結果に応じて分岐します:Flutter → ステップ 2;RN → ステップ 3
- Flutter:
-
Flutter:libapp.so スナップショットの分析(blutter):
python3 blutter.py lib/arm64-v8a/libapp.so ./flutter_out grep -r "目标类名" ./flutter_out/pp.txt # 类/函数名在快照字符串表里 grep -rn "https://\|api\|token\|secret" ./flutter_out/asm/ | head- blutter の成果物から Dart クラス/関数とオブジェクトプールの構造がわかります——クラス名を手がかりにビジネスロジックを追跡します(release AOT では Dart 層の名前が保持されます。落とし穴 1 を参照)
- 動的な情報の補足:reFlutter で再パッケージ化 → dump.dart で実行時のクラス/関数とオフセットを取得 →
frida -U -f <包名> -l frida.jsを_kDartIsolateSnapshotInstructions(readelf -Ws libapp.so)と組み合わせて hook ポイントを特定します
-
RN:bundle.js の抽出と復元:
# 明文 JS unzip -p app.apk assets/index.android.bundle > bundle.js js-beautify bundle.js > bundle.pretty.js # Hermes 字节码 hbc-file-parser index.android.bundle > meta.txt hbc-disassembler index.android.bundle out.hasm hbc-decompiler index.android.bundle out.js # 线索提取 grep -aoE 'https?://[^"'"'"' ]+' out.js | sort -u # 端点半程 grep -aoE '"[A-Za-z0-9_/+=]{16,}"' out.js | sort -u # 疑似密钥 / base64- hasm レベルで関数の呼び出し関係を確認します(
LoadConstStringとCallNの組み合わせ);擬似 JS のループ/条件分岐が不完全な場合は、hasm + 文字列テーブルを基準にします(落とし穴 2 を参照)
- hasm レベルで関数の呼び出し関係を確認します(
-
ネイティブ部分は [[re-binary-core]] に従って処理:
libflutter.so/libapp.so(ELF)→ [[re-format-elf]] で解析 → [[re-ghidra]] でエンジン層の C++(dart:: VM、チャネル登録、Skia)と JNI ブリッジ(FlutterEngine / PlatformChannel)を逆コンパイル- RN:
libhermes.so(エンジン、任意)、JNI ブリッジ(com.facebook.reactパッケージ)→ ネイティブモジュールのロジック - デバッグ対策 / 暗号化 / 証明書検証は、この層にあることがよくあります(例:BoringSSL の verify 関数は libflutter.so 内)——hook ポイントはステップ 2 を参照
-
ロジックの特定(ブリッジ境界):
- Flutter:MethodChannel のチャネル名を全体検索します(
flutter_プレフィックスがよく使われます);PlatformChannel の送受信箇所が機密データ(token / 設定)の交換ポイントです - RN:NativeModules の登録表と
requireNativeComponent/NativeModules.xxx——JS 側で見つからない暗号化/復号処理はネイティブモジュール内にあります(ステップ 4 へ) - UI テキスト / ネットワーク文字列から逆引きします:文字列テーブル(Flutter スナップショット / JS bundle)→ 対応する関数 → ロジック
- Flutter:MethodChannel のチャネル名を全体検索します(
Flutter MethodChannel の動的なインターセプト
Flutter のプラットフォームチャネル(PlatformChannel)は Dart ↔ native 通信の中核です。インターセプトすることで、すべてのネイティブ機能の呼び出しを観察できます。
- Java/Kotlin 側:
io.flutter.plugin.common.MethodChannelの MethodCallHandler を hook します——チャネル名 / メソッド名 / 引数 JSON を記録します - チャネル名の取得:
MethodChannel.setMethodCallHandlerを hook します(onEnter でチャネルインスタンスを捕捉し、名前を取得)。またはMethodChannelコンストラクターから直接チャネル名を取得します。より低レベルではBinaryMessenger.sendを hook できます(第 1 引数がチャネル名です) - エンジンの messenger 層:
io.flutter.embedding.engine.FlutterEngineの messenger メッセージ(低レベルのフォールバック) - Dart 側の静的な観察を補完:静的解析でチャネル名と呼び出し箇所(文字列リテラル)を特定し、動的解析で実際のトラフィックを確認します
- 出力:構造化 JSON(channel / method / args)。[[re-analyze/analysis-contract]] のデータ契約で利用します(証拠を保存)
Flutter の詳細については [[re-flutter]] を参照してください
React Native / Hermes(RN バイトコードとブリッジ)
識別:HBC と RN の成果物
.hbc/ Hermes バイトコード:ファイルヘッダーの先頭 4 バイトはc61fbc03などのマジック値です(リトルエンディアン)。fileの出力は "Hermes JavaScript bytecode, version XX" のような形式です。マジック値がなく、var __BUNDLE_START_TIME__などの JS ソースで始まる場合は、プレーンテキストの bundle です- RN アプリの構造:
assets/index.android.bundle(Metro でパッケージ化されたビジネス JS / HBC。RN 0.70 以降はデフォルトで Hermes によるコンパイル)、lib/<abi>/libhermes.so(Hermes エンジン)、libreactnativejni.so(JNI ブリッジ) - Hermes と標準的な JS エンジンの違い:Hermes は AOT で事前コンパイルされたバイトコードです(VM 命令を使用し、実行時に JIT コンパイルを行いません)。標準的なエンジン(JSC / V8 系)の成果物はプレーンテキストの JS ソースパッケージです。この違いによって、以降の処理がバイトコードの逆アセンブルか、純粋な JS の復元かに分かれます
Hermes バイトコード:逆アセンブルの考え方
- 構造:ファイルヘッダー(マジック値 + バージョン番号)→ バイトコードセクション → 関数テーブル / 文字列テーブル;バージョン番号はファイルヘッダーにあり、使用できるツールを直接左右します
- 逆アセンブル:hermesc 系のツール(
-dump-bytecode系)または HBC 逆アセンブラー系の Python 実装(「ツールの準備」を参照)→ hasm アセンブリを出力します;文字列テーブルはまとめて抽出できます(セクションをまたいで印字可能な文字列を取得)ので、まず手がかりを得ます - 関数テーブルの特徴:各関数には id / オフセット / 命令数 / 引数の数が含まれます——呼び出し関係(LoadConstString と CallN の組み合わせ)をたどってビジネスロジックの入口を追跡します
JS bundle の抽出:Metro の構造と復元
- Metro のパッケージ構造:モジュールファクトリーは
__d(factory, moduleId, deps)によってグローバル登録表に登録され、__r(moduleId)が実行時に require します;bundle の先頭には__BUNDLE_START_TIME__と polyfill のプレフィックスがあります - ビジネス JS の復元:整形(jsbeautify 系のツール)→
__d(でモジュール境界を分割 → 各モジュールを個別に復元(ファクトリー本体 + 依存関係配列)→ 難読化 / 文字列暗号化の処理は [[re-script-deob]] に続きます - モジュールの対応付け:サイズの大きなモジュール(文字列が長い / 依存関係が多い)はビジネスロジックのメインパッケージであることが多いため、画面機能と照合して対象を絞り込めます
ネイティブブリッジ:NativeModules / TurboModule
- JS 側の特定:
NativeModules.xxxのアクセス箇所 /TurboModuleRegistry.get('Xxx')——ブリッジ名がそのままモジュール名です - ネイティブ側の対応付け:JNI ブリッジの
com.facebook.reactパッケージ配下にあるモジュール登録表(@ReactMethodアノテーション付きのメソッド /getNativeModules()の一覧);TurboModule のインターフェースメソッドのシグネチャは JS の呼び出し名と 1 対 1 で対応します - ブリッジ境界が機密データの交換ポイントです:JS 側で見つからない暗号化/復号処理はネイティブモジュール内にあります([[re-binary-core]] へ)。Flutter MethodChannel と同様です
動的解析:JS ランタイムの hook([[re-frida]])
- モジュールのロード箇所:
__d/__rを hook すると、ロード済みの全モジュールと id を列挙し、モジュールファクトリーのソースを dump できます。実行時に追加登録されたモジュールは、リモート配信(ホットアップデート)の特徴です - console 出力:console.log / warn の実装を hook し、ビジネスログとエラースタックを取得します
- ブリッジ層のフォールバック:NativeModules のメソッド呼び出しを hook し(引数 / 戻り値)、JS↔ネイティブ間で交換されるデータを観察します
坑与陷阱
- Hermes 字节码不跨版本:现象——同一样本在一种环境下反汇编正常,换版本或换工具输出乱码、直接报错;原因——HBC 格式随引擎版本演进、不保证向后兼容,反汇编类工具只覆盖特定版本区间;对策——先取版本(
file输出 / libhermes.so 内版本字符串 / 构建时间),选对应版本的 hermesc 类工具或更新反汇编工具,版本不匹配时优先换工具而非手动修字节 - bundle 加密 / 混淆(Metro 加密层):现象——bundle 解出后是密文 / 乱码,或模块函数名不可读;原因——构建管线在 Metro 输出层加密(bundle 内嵌解密器,运行期解密)或启用混淆插件;对策——在 bundle 内找解密器特征(XOR 循环 / 自解密函数 / 硬编码密钥),静态还原解密器整体解密,或 [[re-frida]] 在解密函数返回处 dump 明文,混淆部分衔接 [[re-script-deob]]
- 字节码与明文 JS 混合(部分页面走远程 bundle):现象——本地 bundle 是明文 JS 且内容正常,但关键页面逻辑缺失,或运行期出现本地没有的模块;原因——混合交付:部分页面打包进 assets,敏感 / 更新页走远程下发 bundle(热更新、灰度),本地包只含壳与加载逻辑;对策——先核对本地 bundle 完整性(
__d模块数与页面功能对比),抓包 / 文件系统快照归档远程 bundle,再按明文或 HBC 路径处理
跨域联合
- [[re-mobile]]: 混合应用分支固定调用本技能(引擎识别后按框架分流)
- [[re-apk]] / [[re-ios]]: 容器侧静态(manifest、签名、iOS 等价物)
- [[re-binary-core]]: 原生部分——[[re-format-elf]] + [[re-ghidra]] 反编译 libapp.so / libflutter.so 与 RN JNI 桥
- [[re-frida]]: 运行时 hook(桥接层、证书校验、反检测)
- [[re-analyze/platform-tips]]: 默认沙箱、工具解析 ≠ 加载器视图(快照偏移 / 工具版本差异)
- 本技能被 [[re-analyze]] 的 triage「移动 App 分析」路径引用(re-mobile → re-hybrid-app)
常见坑与陷阱
- Flutter AOT スナップショットにはソースレベルのシンボルがない:現象——IDA/Ghidra で libapp.so を直接見ると dart:: 内部関数と生のオフセットしか表示されず、ビジネス関数名が見えません。原因——AOT スナップショットには DWARF シンボルがありませんが、Dart のクラス名/関数名は実際にはスナップショットの文字列テーブルに残っています。対策——blutter で復元(pp.txt / asm/)し、文字列テーブルを grep してクラス名を特定したうえで asm と照合して分析します。ELF シンボルテーブルでビジネスシンボルを探さないでください
- RN Hermes のバイトコードは復元が困難:現象——hbc-decompiler が出力する疑似 JS ではループ/条件がすべて欠落し、変数名がレジスタ番号になるため、ロジックを読み解けません。原因——HBC は VM 命令であり、ソースレベルへの逆コンパイルは不完全です。対策——hbc-disassembler の hasm で呼び出し関係を確認し(LoadConstString + CallN で文字列の使用箇所を特定)、文字列テーブル/bundle 内の平文(URL、エラーメッセージ)から意味を逆引きします。必要に応じて、動的に取得した平文の通信と照合します
- エンジンのバージョン差異:現象——blutter が Dart バージョン非対応と報告する/hermes-dec の解析が失敗する、またはずれます。原因——ツールが対応するのは特定のバージョン範囲のみで、アプリが新しいエンジン(Dart 3.x の新しいスナップショット形式、Hermes の新しい bytecode バージョン)を使用しています。対策——まずバージョンを取得し(libflutter.so 内のバージョン文字列/
fileの出力する HBC version/ビルド日時)、ツールを更新する(blutter は必要に応じて対応する Dart ソースをダウンロードして再コンパイルします)か、そのバージョンに対応した専用ツールに切り替えます - JS とネイティブのブリッジ境界:現象——JS/bundle 内に暗号化・復号や鍵のロジックが見つかりません。原因——機密性の高いロジックがネイティブモジュール(RN NativeModules / Flutter MethodChannel のネイティブ側)にあります。対策——手順 5 に沿ってブリッジの登録(チャネル名/ネイティブモジュールの一覧)を探し、[[re-binary-core]] に進んで対応する so を分析します。JS 層にこだわり続けないでください
- 難読化されたビルド:現象——関数名がすべて a/b/c または hash です。原因——Flutter
--obfuscate/RN Hermes + 難読化オプションでビルドされています。対策——利用可能な文字列(エラーメッセージ/API パス)と動作観察を軸に分析し、動的な [[re-frida]] hook でブリッジ層の特定を組み合わせます - Flutter の SSL 検証によりパケットを取得できない:現象——パケットキャプチャツールに Flutter アプリの通信が表示されない、または証明書エラーが出ます。原因——Flutter は独自の SSL 検証を備えており(システムプロキシ/証明書の信頼チェーンを経由しません)。あらゆるアプリに対応するソリューションのオープンソースコードが github にあります。対策——まず Flutter の SSL 検証に対応するソリューションを使うか、frida で回避してパケットを取得します。blutter で復元すると libapp.so のシンボルが見えるため、アドレスを基に対象関数を直接 hook できます(たとえば署名関数 generateMD5 の引数/戻り値からソルト値と平文を取得します)。アルゴリズム全体を復元する必要はありません
- Dart 文字列のメモリレイアウト(frida で文字列を読み取る際の要点):現象——libapp.so の関数を hook してポインターを取得しても、文字列を読み取れません。原因——Dart 文字列は C 文字列ではありません。ポインター +7 の位置に 4 バイトの Smi エンコードされた長さ(右に 1 ビットシフトすると実際の長さ)、+15 の位置に UTF-8 データがあります。対策——このレイアウトに従って readDartStringExact ユーティリティ関数を作成します(誤読防止のため長さの上限を設定します)。hook した引数/戻り値のどちらからも平文を復元できます
- Hermes の関数テーブルヘッダーは構造体ではなく、ビットフィールドで圧縮されています:現象——関数テーブルを通常の構造体として読むと、関数の境界/名前/引数の数が乱れたり、オーバーフローした関数の大きなヘッダーのオフセットを通常のフィールドとして読み取ったりします。原因——HBC の関数ヘッダーは 10 個のフィールドを 1 つの 128 ビットワード(legacy は 16 バイト、バージョン <97)または 96 ビットワード(modern は 12 バイト、バージョン >=97、RN 0.75+ のデフォルト形式)に格納しており、ビット幅の割り当てはバージョンによって異なります。対策——まずバージョン番号を取得してからビットフィールドの配置を選択します。flags ビットは意味の推定にも役立ちます(bit0 strict、bit3 hasExceptionHandler、bit5 overflowed)。overflowed が設定されている場合、インラインヘッダーフィールドは信頼できないため、大きなヘッダーに基づいて組み立てます(legacy は (info_offset<<16)|offset、modern は (function_name<<24)|(offset&0xffffff))。関数の境界 = ヘッダー内のオフセット + bytecode_size、グローバルコードは関数 0 です(出典:hermes-decomp(SymbioticSec)、MIT)
- Hermes の小文字列テーブル:u32 ビットフィールド + センチネルによるオーバーフローテーブル + run-length エンコード + packed 共有ストレージ:現象——文字列の長さ/内容がずれたり、xref の逆引き先が誤った関数になったりします。原因——各項目は 1 個の u32 で、bit0 は UTF-16 フラグ、bits1-23 はストレージオフセット、bits24-31 は長さ(最大 255)を格納します。長さまたはオフセットがセンチネル値(0xff / 0x800000)に達した場合、実際の値はオーバーフローテーブルに (offset,length) のペアで補足されています。文字列種別テーブルは run-length エンコード(最上位ビット 0=通常の String、1=Identifier、下位 31 ビット=同じ種別が連続する個数)です。対策——ビットフィールドに従って解析し、オーバーフローテーブルを参照します。項目は必ずテーブル項目のオフセットで特定し、部分文字列検索は絶対に行わないでください(複数の項目が同じストレージ領域を共有している場合があります)。書き戻し時も同様です。run を展開すると、コード文字列と識別子を区別できます(識別子はプロパティアクセスの解析と名前の推定に使われます)(出典:hermes-decomp(SymbioticSec)、MIT)
- opcode の定義はバージョンごとに変わりますが、builtin インデックスが変わるのはリリース境界のみです:現象——同じバイトコードでも逆アセンブル/逆コンパイルツールやバージョンを変えると出力がずれたり、未知の opcode に遭遇してクラッシュしたりします。原因——HBC のバージョンごと(40-99)に命令テーブルがあり、opcode 番号とオペランド型の並び(Reg8/Reg32/UInt8/Addr8 など)はバージョンごとに異なります。対策——バージョンごとにテーブルを取得します(バージョンが欠けている場合は「対象バージョン以下で最も近い」テーブルにフォールバックし、実際のバージョンを報告します)。未知の opcode は範囲外アクセスではなく にフォールバックします。逆アセンブル層ではまず意味上のバリエーションを抽象定義に正規化します(2 種類の Mov エンコード、3 種類の GetById バリエーションを同じ抽象操作に統合します)。IR 層では抽象的な意味だけを処理します。ジャンプ先は Addr8/Addr32 に従って自身のオフセットからの相対位置として計算します(出典:hermes-decomp(SymbioticSec)、MIT)
- 変数の復元に完全な SSA+Phi は使わないでください:現象——逆コンパイル出力に一時変数とエッジコピーが大量に含まれ、読みやすさが低下します。原因——対象言語の JS に Phi はなく、完全な SSA の De-SSA 段階はかえってノイズを生みます。対策——生存区間分割方式で復元します。到達定義 + Union-Find を使い、2 つの定義が同じ使用箇所(if/else の合流点、ループのバックエッジ)に集まる場合は 1 つのクラスに統合し、それ以外は別々の変数にします。Phi は使いません。名前付けではまずレジスターの役割を推定します(プロパティ/メソッドの語彙から型を推測します:length/push/map/filter→Array、then/catch/finally→Promise、next/done/value→Iterator、toString/split/trim→String)。名前の優先順位は、デバッグセクションのスコープ名 > 分割代入のキー名 > 名前付き関数名 > 引数 > 型によるフォールバック > 番号です。関数名は「有意性」フィルターに通し、純粋な数字/生成名/汎用的なコア名は除外します(出典:hermes-decomp(SymbioticSec)、MIT)
- Metro のモジュールファクトリーの引数の数はバージョンの手掛かりです:現象——__d 登録の走査でファクトリーを認識できず、ファクトリー引数の役割を取り違え、モジュールの骨格を復元できません。原因——classic bundle のファクトリーは 4 引数(require/module/exports/deps)、modern bundle は 7 引数(importDefault/importAll を含む)です。対策——param_count-1(this を除いた数)を役割判定の根拠にします。__d(undefined, factory, moduleId, deps) のパターンに一致させます。引数がレジスター経由で転送される場合は、「レジスター→関数/整数/配列」の関数内 map を維持して値を追跡します。エクスポート分析では、直接代入/一括代入/Object.defineProperty の 3 パターンに分けます。モジュール名は、単一箇所の推定よりも伝播チェーン(require の名前を逆引き→再エクスポートする薄いラッパーが継承→単一依存チェーン)に沿って推定するほうが信頼性が高くなります(出典:hermes-decomp(SymbioticSec)、MIT)
- バイトコードの書き戻しパッチ:同じ長さならインプレース、長さが変わるなら移動、重複がある場合は拒否:現象——.hbc のバイトを直接変更すると bundle の実行がクラッシュしたり、構造テーブルがずれたりします。原因——文字列ストレージは packed に対応しており(複数の項目が同じ領域を共有)、オフセットは複数箇所から参照されています。長さが変わる変更では、後続のすべての領域を連動して修正する必要があります。対策——JS を再コンパイルせず、バイトコードだけを変更します(HASM の往復:emit-hasm で出力し、編集してから asm に戻します)。文字列パッチは長さに応じて振り分けます。同じ長さならテーブルを変更せずインプレースで上書きし、長さが変わる場合は文字列テーブルとストレージを再構築して末尾のすべての領域を移動します(オフセットも連動して修正します)。UTF-16 文字列と packed で重複している文字列へのパッチは必ず拒否します。modern(>=97)では同じ長さの文字列パッチのみをサポートし、長さが変わる文字列/関数の resize/stub の挿入による拡張/ゼロからの create はすべて明示的にエラーを報告して、破損した出力を書き込みません。パッチ後に再解析して構造の整合性を検証し、整合しなければ出力を拒否します(出典:hermes-decomp(SymbioticSec)、MIT)
- Hermes の逆コンパイルの能力範囲:現象——逆コンパイル結果に変数名がなかったり、制御フローが不正確だったり、元のソースコードをそのまま取得できると期待したりします。原因——逆コンパイラーには難読化解除の機能がなく、難読化後のコードから得られるのは難読化後の意味だけです。リリースパッケージではデバッグセクションが削除されていることが多く(offset=0 または u32::MAX)、変数名/行番号が完全に失われています。逆アセンブル≠逆コンパイルです。命令ストリームから正確な制御フローとオペランドは得られますが、変数/構造/モジュールの意味は IR + プログラム全体の分析に依存します。対策——「デバッグ情報なし」自体も分析結果です。名前はヒューリスティックで推定するしかありません。まず文字列テーブルでコンパイル時に埋め込まれたソース/JSON/マッピングを探します(例:RN Reanimated の Babel プラグインは worklet の元ソースを通常の文字列として .hbc の文字列テーブルにコンパイルするため、関数名で join すれば実際のソースコードを取得できます)。逆コンパイルよりも手間が少なく、忠実度も高くなります(出典:hermes-decomp(SymbioticSec)、MIT)
コメント
Markdown 対応