
React NativeアプリのHermesバイトコード(HBC)を検査、逆コンパイル、および構造的にパッチするツール。GDBスタイルのREPL、CFG、逆コンパイラ、再シリアライズパッチャーを備えています。
Hermes リバースエンジニアリング & 操作インタラクティブツールキット。
hermit は、React Native アプリが JavaScript を格納する形式である Hermes バイトコード (HBC) を検査、デコンパイル、および構造的にパッチします。.hbc ファイルを開き、その関数を閲覧し、デコンパイルされた JavaScript を読み、インタラクティブエディタで命令、文字列、リテラルを編集し、Hermes VM が依然としてロードして実行できるファイルを書き戻します。
バイトコードを分析するだけのツール、あるいはサイズを変更せずにその場でパッチするツールとは異なり、hermit はファイル全体を再シリアライズします。命令の挿入と削除、ジャンプと例外ハンドラの再配置、文字列テーブルとリテラルテーブルの再構築、新しい関数の追加、SHA-1 フッターの再計算が可能です。未変更のファイルはバイト単位でラウンドトリップします。
.hbc ファイルの入手| バージョン | 役割 |
|---|---|
| 96 | 主要ターゲット。出荷中の React Native アプリが使用するもの: RN 0.73 から 0.83、および Hermes V1 を無効にした RN 0.84+。 |
| 94, 95 | 検証済み。フォーマットは 96 と同一で、オペコードテーブルのみが異なります。 |
Hermes V1 (RN 0.84 以降のデフォルト) でビルドされたアプリは HBC 98 を出力しますが、これはまだサポートされていません。フォーマットのメモは doc/hbc-format/ にあります。
各 リリース には、macOS および Linux 用のアーカイブが arm64 と x86_64 で用意されています。Python やコンパイラは不要です:
| アーカイブ | 内容 |
|---|---|
hermit-<version>-<platform>.tar.gz | インタラクティブ REPL: ./hermit file.hbc。 |
hermit-tools-<version>-<platform>.tar.gz | スタンドアロンの hbc_* コマンドラインツール。 |
SHA256SUMS | チェックサム: shasum -a 256 -c SHA256SUMS。 |
tar -xzf hermit-0.1.0-macos-arm64.tar.gz
./hermit-0.1.0-macos-arm64/hermit --version
macOS バイナリは署名されていません。ダウンロード後、隔離フラグを一度クリアしてください: xattr -dr com.apple.quarantine hermit-0.1.0-macos-arm64。Linux ビルドには glibc 2.35 以降が必要です。cfg コマンドには依然として graph-easy のインストールが必要です。
リリースは v* タグがプッシュされたときに .github/workflows/release.yml によって生成され、packaging/build-release.sh を使用します。これはローカルでも実行できます。
FetchContent で取得されます。cfg コマンド用の graph-easy (cpanm Graph::Easy)、そのページャーとしての less。すべてをこのディレクトリ (CMakeLists.txt を含むディレクトリ) から実行してください:
cmake -B build
cmake --build build -j
これにより、libhbc 静的ライブラリ、Python モジュール _libhbc (build/bindings/ 内)、コマンドラインツール (build/tools/ 内)、およびユニットテストがビルドされます。REPL には Python モジュールのみが必要です:
cmake --build build --target _libhbc
ツールとテストは、たとえばリリース用にビルドから除外できます:
cmake -B build -DCMAKE_BUILD_TYPE=Release -DHERMIT_BUILD_TOOLS=OFF -DHERMIT_BUILD_TESTS=OFF
core/ または bindings/ を変更するたびに、同じコマンドで再ビルドしてください。
macOS: 不正な SDK に関するエラーでリンクが失敗する場合は、ビルドを完全な SDK に向けてください。たとえば
SDKROOT=/Library/Developer/CommandLineTools/SDKs/MacOSX26.5.sdk cmake --build build。
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
モジュール _libhbc は仮想環境にインストールされません。hermit を実行するたびに、そのビルドディレクトリを PYTHONPATH に追加してください:
export PYTHONPATH=$PWD/build/bindings:$PYTHONPATH
.hbc ファイルの入手React Native アプリはバンドルをプレーンファイルとして格納します:
| プラットフォーム | パッケージ内の場所 |
|---|---|
Android (.apk、zip) | assets/index.android.bundle |
iOS (.ipa、zip) | Payload/<App>.app/main.jsbundle |
ファイルがバイト C6 1F BC 03 C1 03 19 1F で始まる場合、それは Hermes バイトコードです。REPL は .hbc 拡張子のファイルのみを開くので、名前を変更してください:
unzip -p app.apk assets/index.android.bundle > app.hbc
xxd app.hbc | head -1 # c61f bc03 c103 191f ...
分析するのは、研究が許可されているアプリのみにしてください。たとえば自分のアプリ、または研究用のオープンソースアプリなどです。
小さく制御された入力を得るには、HBC 96 を出力する Hermes コンパイラ (たとえば react-native 0.73 から 0.83 の hermesc、node_modules/react-native/sdks/hermesc/ 以下) で JavaScript または TypeScript をコンパイルします:
hermes -emit-binary -g0 -out hello.hbc hello.js # -g0: no debug info
seeder/ パッケージは、seeder/samples/ 内のサンプルプログラムに対してこれを自動化し、結果を seeder/corpus/ に書き込みます:
.venv/bin/python -m seeder.compile # all samples
.venv/bin/python -m seeder.compile --g0 seeder/samples/loop_sum.js
.venv/bin/python -m seeder.compile --debug seeder/samples/greeting.js
--g0 はデバッグ情報なしで <name>_g0.hbc を書き込み、--debug は完全なデバッグ情報付きで <name>_g.hbc を書き込みます。.ts サンプルには PATH 上の tsc が必要です。コンパイラは --hermes から取得されます (デフォルトは hermes)。seeder/build-real-bundle.sh は実際のオープンソース React Native アプリを .hbc にビルドします。
export PYTHONPATH=$PWD/build/bindings:$PYTHONPATH
.venv/bin/python -m cli.main app.hbc
ファイル引数なしでは REPL は空の状態で起動します。open <file.hbc> を使用してください。プロンプトには HBC バージョンとファイル名が表示され、未保存の変更がある場合は + が付きます: [hbc v96 | app+] >。履歴は ~/.hermit_history に保持され、コマンドはタブ補完されます。
> info
> strings --identifiers filter:fetch
> functions
> disasm func:12
> decompile func:isPremium
> xrefs string:"api.example.com"
> edit func:isPremium
> patch patched.hbc
関数はインデックスまたは名前で指定します (func:12、func:isPremium)、文字列はインデックスまたは値で指定します (string:42、string:"hello")、リテラルサイトはインデックスで指定します (array:7)。
| コマンド | 動作 |
|---|---|
open <file.hbc> | ファイルを開きます。 |
info | ヘッダー概要: バージョン、サイズ、カウント、グローバル関数、ソースハッシュ。 |
strings [--identifiers|--strings] [filter:<text>] | 種類付きの文字列テーブル。 |
arrays [--arrays|--objects] [filter:<text>] [func:<N|name>] | デコードされた配列およびオブジェクトリテラルサイト。 |
functions | パラメータ、レジスタ、フラグ付きのすべての関数。Metro モジュール ID。 |
disasm func:<N|name> | ソース行付きの逆アセンブリ。s でデバッグソーステーブルを切り替えます。 |
src [func:<N|name>] | デバッグソーステーブル。すべての関数または 1 つ。 |
cfg func:<N|name> [--compact=true|false] | graph-easy でレンダリングされた制御フローグラフ。 |
xrefs | 文字列クロスリファレンスの概要。 |
xrefs string:<value|N> | 文字列を使用するすべての命令。 |
xrefs func:<N|name> [--callers] [--callees] | 関数周辺のコールグラフ。 |
decompile func:<N|name> | デコンパイルされた JavaScript。 |
decompile all [--output=<dir>] | すべての関数を .js ファイルにデコンパイルします。 |
edit func:<N|name|new> | バイトコードエディタ。func:new は新しい関数を構築します。 |
edit string:<idx|value|new> | 文字列エディタ。string:new は文字列を追加します。 |
edit array:<N> | 配列およびオブジェクトリテラルエディタ。 |
patch [file.hbc] | 編集済みイメージを新しいファイルに書き込みます。 |
help、exit | ヘルプと終了。 |
長いリストはスクロール可能なビューアで開きます: 矢印キー、PgUp/PgDn、先頭と末尾には g/G、入力しながら検索するには /、検索をクリアするには Esc、終了するには q。
編集はメモリ内で行われます。edit ... は全画面エディタを開きます。Ctrl+X の後に Y で確定すると、整合性チェック (ファイルが再パースされ、期待された内容と比較されます) の後に変更がメモリ内イメージに適用されます。patch までディスクには何も書き込まれず、patch は元のファイルを上書きしません。
命令エディタ (edit func:):
| キー | 動作 |
|---|---|
| Left / Right | ニーモニックとオペランドの間を移動します。 |
| Up / Down、PgUp / PgDn | 命令間を移動します。 |
Enter | 選択したトークンを編集します。ニーモニックは同じオペランドシグネチャを持つ任意のオペコードに変更できます。オペランドはその型に対して検証されます。 |
o / O | 下 / 上に命令を挿入します (オペコードピッカー)。ジャンプの場合、そのアドレスオペランドで Enter を押すとターゲットを選択できます。 |
Ctrl+K | 現在の命令を削除します。 |
R | 現在の行を復元します。 |
Ctrl+Z / Ctrl+R | 元に戻す / やり直す。 |
Ctrl+X | 保存して終了 (Y で適用、N で破棄、Esc でキャンセル)。 |
同じサイズの編集はその場でスプライスされます。命令の挿入または削除は構造的シリアライザを経由し、必要に応じて短いジャンプを長いものに緩和し、例外ハンドラの範囲を再マップし、移動したすべてのオフセットを書き換えます。
文字列エディタ (edit string:): 1 行エディタです。同じ長さの値はその場で書き込まれます。それ以外の長さでは、新しいバイトが文字列ストレージに追加され、エントリが再ポイントされます。これにより、ストレージを共有する文字列も切り離されます。Ctrl+N で新しい文字列の追加に切り替わります。識別子はハッシュテーブルに結び付けられているため、読み取り専用です。
リテラルエディタ (edit array:): Up と Down で要素を選択し、Enter を押して再入力します。Ctrl+T でその型を変更し (null、true、false を含む)、a で要素またはキーと値のペアを追加し、d で最後のものを削除します。