
Ghidra ServerリポジトリとJavaブリッジサブプロセスを介して解析を双方向に同期するBinary Ninjaプラグイン。
Ghidra Server リポジトリに接続し、その解析結果 — シンボル、関数名、コメント — を開いている Binary Ninja のバイナリビューに直接インポートする Binary Ninja プラグインです。
Ghidra と Binary Ninja にはそれぞれ強みがあります。このプラグインを使えば、名前やコメントを手動でコピーすることなく、同じバイナリで両方を使用できます。実行中の Ghidra Server に接続し、そのリポジトリを閲覧し、任意のプロジェクトファイルをダブルクリックしてその解析結果を現在開いている BN ビューに取り込めます。
同期は双方向です。
| BN ← Ghidra (インポート) | BN → Ghidra (チェックイン) |
|---|
| シンボル (ラベル、関数名) | ✓ | ✓ |
| コメント (EOL/PRE/POST/PLATE/REP) | ✓ | ✓ |
| 関数シグネチャ (戻り値型、呼び出し規約) | ✓ | ✓ |
| 関数パラメータ (名前変更、型変更、追加) | ✓ | ✓ |
| データ型 (struct/union/enum/typedef + ポインタ/配列) | ✓ | ✓ |
| 等価定数 (定数名 + 参照) | ✓ | ✓ |
| ブックマーク | ✓ | ✓ |
| 型付きデータ項目 | ✓ | ✓ |
| 関数フラグ (thunk、no-return、inline) | ✓ BN タグとして | — |
| ローカル変数 (ストレージ対応) | ✓ | 部分的 — レジスタストレージのマッピングは未実装 |
Binary Ninja (C++ plugin)
│ TCP / newline-delimited JSON
▼
ghidra-bridge-*.jar (Java, runs as a subprocess)
│ Java RMI / SSL
▼
Ghidra Server (ghidraSvr, running on the network)
プラグインはロード時に Java サブプロセス (「ブリッジ」) を起動します。ブリッジは Ghidra Server への RMI 接続を保持し、ローカル TCP ソケットを介してプラグインとシンプルな JSON プロトコルで通信します。これにより、すべての Java/RMI コードが C++ プロセスから分離され、BN のロードが完了する間にバックグラウンドで JVM を起動できます。
ブリッジ JVM は起動時に Ghidra の Application フレームワークも初期化するため、書き込みパスでは生の db.Table.putRecord() 書き込みではなく、Ghidra の高レベルなプログラムモデル API (ProgramDB、DataTypeManager、SymbolTable、FunctionManager) を使用できます — 以下 チェックイン書き込みパス を参照してください。
| パス | 言語 | 役割 |
|---|---|---|
plugin/ | C++ / Qt6 | Binary Ninja サイドバープラグイン |
bridge/ | Java 17 | Ghidra RMI クライアント + JSON ブリッジサーバー |
プラグイン (C++):
plugin.cpp — 設定とサイドバーウィジェットを登録; ロード時にブリッジ JVM を即座に起動GhidraConnection.cpp — シングルトン; ブリッジのライフサイクルとすべての RMI ベースの操作を管理BridgeProcess.cpp — ブリッジ JAR を stdout/stderr パイプ付きのサブプロセスとして起動; READY port=N ハンドシェイク行を読み取るBridgeClient.cpp — TCP クライアント; JSON リクエストを送信し、レスポンスを受信し、非同期イベントをディスパッチSyncEngine.cpp — GhidraDbExport を BinaryView に適用 (シンボル、コメント、フラグ)ui/ProjectPanel.cpp — サイドバーウィジェット: リポジトリツリー、接続ダイアログ、アクティビティログui/ConnectDialog.cpp — ホスト/ポート/ユーザー/パスワードのダイアログブリッジ (Java):
BridgeMain.java — 引数の解析; UniversalIdGenerator と Ghidra Application フレームワークを初期化; TCP サーバーを起動; stdout に READY port=N を出力BridgeServer.java — 1 つの TCP クライアント接続を受け入れ、BridgeConnection を渡すBridgeConnection.java — JSON リクエストディスパッチャ; Ghidra API レスポンスを JSON にシリアライズ; opCheckin を処理 (サーバー上に新しいプログラムバージョンを作成)GhidraSession.java — 認証済み RMI セッション; RemoteRepositoryServerHandle をラップEventStreamer.java — 開いているリポジトリごとのバックグラウンドスレッド; RepositoryChangeEvent を非同期 JSON イベントとしてプラグインにプッシュDatabaseExporter.java — 読み取りパス: 生の db.jar アクセスを介して ManagedBufferFileHandle (Ghidra のリモート DB バッファ) からシンボル/コメント/関数フラグ/データ型/等価定数/ブックマークのテーブルを抽出ProgramApplier.java — 書き込みパス: バッファファイルを実際の ProgramDB として開き、Ghidra の高レベル API を介してすべての BN 側の変更を適用 (以下 チェックイン書き込みパス を参照)DatabaseImporter.java — テストシームとしてのみ保持されているレガシーな生書き込みヘルパー; 本番の apply(...) は ProgramApplier に委譲opCheckin はプログラムの管理バッファファイルを書き込みモードで開き、その上に ProgramDB を構築し、Ghidra のプログラムモデル API を介して BN 側の変更を適用します。生の db.Table.putRecord() 書き込みは回避されています — これらはこれまでに遭遇したすべてのチェックイン破損バグの原因でした:
| 誤った階層の書き込み | 障害モード |
|---|---|
Function Data テーブルへの setIntValue(col, longTypeId) | IntField.setLongValue が暗黙的に l2i 切り捨て → シグネチャ更新のたびに StackPurge が破損 |
V5V6 Composite Data Types への setByteValue(col, isUnion) | Ghidra 12.x では列が BooleanField → IllegalFieldAccessException (「不正なフィールドアクセス」) |
V2 Typedef Flags 列への setIntValue(col, 0) | 列が ShortField → 同じクラッシュ、異なるスキーマ |
| コンポーネント設定行なしで composite ヘッダーを書き込み | Ghidra で struct を開くと CompositeEditorModel.cloneAllComponentSettings が ArrayIndexOutOfBoundsException をスロー |
SYM_ADDR_COL = RAM アドレスで PARAMETER シンボルを書き込み | 任意の関数アクセスで FunctionDB.loadSymbolBasedVariables が Address is not a VariableAddress をスロー |
DBHandle.save() に null の DBChangeSet を渡す | サーバーが 0 バイトの変更データファイルを書き込み → 次のチェックアウトが ProgramContentHandler.loadProgramChangeSet で EOFException で失敗 |
ProgramApplier にはこれらの罠がありません。DataTypeManager.addDataType、SymbolTable.createLabel、Listing.setComment、Function.setReturnType などを経由するためです — これらの API は Ghidra の相互に連携するテーブルの不変条件を自動的に維持します。また、すべてのチェックインの開始時に cleanupBadVariableSymbols パスを実行し、古いブリッジバージョンによってデータベースに残された破損をパージします。
server-package/CleanupBadVariableSymbols.java は、analyzeHeadless を介して同じクリーンアップを実行するスタンドアロンの GhidraScript です — ファイルが破損しすぎて Ghidra GUI で開けない場合に便利です。
./test.sh # macOS / Linux: tiers 0-3 (C++ unit + BN-headless + Java)
test.bat # Windows equivalent
test.bat --parity # cross-DB parity tier only (C++ BN tests + gradlew parityTest)
test.bat --e2e # live Ghidra-server E2E (starts a local ghidraSvr)
スイートは 5 つの階層で構成されています。階層 2〜4 は 1 つの特性を証明するために存在します: 同じ互換データが .bndb と Ghidra プログラムデータベースの両方に格納されること (この README の冒頭にある互換性マトリクス)。
| 階層 | 内容 | 場所 | ゲート |
|---|---|---|---|
| 0 | 純粋なユニットテスト | plugin/test/*.cpp (binja-ghidra-tests)、ブリッジ *Test.java | 常時 |
| 1 | Ghidra-DB ラウンドトリップ | ブリッジ *RoundTripTest.java (実際の ProgramDB に対する ProgramApplier) | ghidra.home / GHIDRA_HOME が必要 |
| 2 | BN BinaryView/.bndb ラウンドトリップ | plugin/test/bn/ (binja-ghidra-bn-tests; ヘッドレス binaryninjacore) | ヘッドレス対応の BN ライセンスがない場合はクリーンに SKIP (BN_LICENSE 環境変数を尊重) |
| 3 | クロス DB パリティ | testdata/parity/fixtures/ の共有ゴールデンに対する CanonicalParityTest (C++ および Java) | 階層 1+2 と共に |
| 4 | ライブサーバー E2E | ブリッジ LiveServerE2ETest — 一時ディレクトリで実際の ghidraSvr を起動し、analyzeHeadless でシードし、RMI 経由でチェックアウト → エクスポート → チェックイン → 再エクスポートを実行 | test.bat --e2e (GHIDRA_E2E=1 を設定) |
パリティオラクル (階層 3)。 両側が、チェックインされた同じ正規 JSON (ブリッジ DatabaseExporter の形状) に対して独立に検証します。インポート方向: ゴールデンが ProgramDB (Java) に、そして SyncEngine (C++) を介して BinaryView にロードされ、各再エクスポートがゴールデンと等しくなければなりません。チェックイン方向: スクリプト化された BN 編集が正確に fixtures/checkin/*/expected-preview.json (C++) を生成しなければならず、そのプレビューを ProgramApplier を介して適用すると expected-after.json (Java) として再エクスポートされなければなりません。両側が共有ゴールデンと一致すれば、2 つのデータベースは推移的に一致します。フィールド比較モードと型名正規化テーブルは testdata/parity/RULES.md にあり、テストバイナリは testdata/bin/parity_x64.bin です (レイアウトは parity_x64.md)。
Java 側の長年の回帰ピン:
DataTypesRoundTripTest.struct_cloneSettings_doesNotThrow — composite 設定はヘッダーと一致し続けなければならない (cloneAllComponentSettings クラッシュ)FunctionSignaturesRoundTripTest.returnType_doesNotCorruptStackPurge — IntField 切り捨てParametersRoundTripTest.noParameterSymbol_endsUpAtRamAddress — VariableAddress 不変条件ラウンドトリップテストとパリティテストには Ghidra のインストールが必要です (実行時に言語サービスに使用されます)。パスは ghidra.home Gradle システムプロパティまたは GHIDRA_HOME 環境変数から読み取られます; build.gradle はデフォルトで ghidraHome を渡します。階層 2/3 の C++ テストはさらに binaryninjacore がロード可能である必要があります (スクリプトは BN インストールディレクトリを PATH に追加します)。
| 依存関係 | 備考 |
|---|---|
| Binary Ninja (商用) | BN インストール内の api_REVISION.txt に一致するバージョンに対してテスト済み |
| Ghidra Server | Ghidra 12.0.4 でテスト済み。実行中で RMI/SSL 経由で到達可能である必要があります |
| Java 17+ JDK | Eclipse Adoptium JDK 21 を推奨 |
| CMake 3.24+ | |
| Ninja | |
| C++ コンパイラ | Windows では MSVC 2022+; macOS では clang; Linux では gcc/clang |
| Qt 6.7+ | 以下 Qt セットアップ を参照; ビルド時に qmake が PATH にある必要があります |
| Gradle (ラッパー経由) | ブリッジは Gradle ラッパーを使用 — 別途インストールは不要 |
| Poetry (Qt ビルドのみ) | qt-build サブモジュールから Qt をビルドする場合にのみ必要。pip install poetry または pipx install poetry でインストール。 |
| libclang 19 (Qt ビルドのみ) | Qt のビルドシステムで必要。ダウンロード手順は qt-build/README.md を参照。 |
プラグインは Binary Ninja が使用するのと同じ Qt 6 ビルドに対してリンクします。2 つのオプションがあります:
オプション A — 既存の Qt インストールを使用する (すでに Qt がある場合に最速)
Qt の CMake ディレクトリを指す Qt6_DIR を渡します:
Qt6_DIR=/path/to/Qt/6.x.y/clang_64/lib/cmake/Qt6 ./build.sh
macOS では、Qt オンラインインストーラによって /usr/local/Qt* の下にインストールされた場合、ビルドスクリプトが Qt を自動検出します。
オプション B — qt-build サブモジュールから Qt をビルドする (マシンごとに 1 回、約 1〜2 時間)
qt-build サブモジュール (Vector35 の Qt ビルドスクリプト) は、Binary Ninja のパッチを適用して Qt 6 をコンパイルします。Poetry と libclang 19 が必要です (上記の前提条件と qt-build/README.md を参照)。
Qt はリポジトリ内の qt/<version>/<compiler>/ にインストールされます:
| プラットフォーム | インストールパス |
|---|---|
| macOS | qt/6.10.1/clang_64/ |
| Linux x86-64 | qt/6.10.1/gcc_64/ |
| Windows | qt/6.10.1/msvc2022_64/ |
# First time on a new machine:
./build.sh qt # compiles Qt — takes 1-2 hours
# All subsequent builds (Qt cached in qt/, reused automatically):
./build.sh
qt ステップは 1 回だけ必要です。CMake とビルドスクリプトは、以降の実行で qt/ 内のビルド済み Qt を検出し、サブモジュールを完全にスキップします。qt/ ディレクトリは gitignore されています。
git clone https://github.com/mutinylaboratories/ghidra_svr_bridge.git
cd ghidra_svr_bridge
git submodule update --init # populates binaryninja-api and qt-build (~seconds)
次に上記の Qt セットアップ (オプション A または B) に従い、以下を実行します:
./build.sh install
# Incremental build of both components
./build.sh
# Full clean rebuild + install into BN plugins folder
./build.sh clean install
# Build only the C++ plugin
./build.sh plugin
# Build only the Java bridge
./build.sh bridge
# Build Qt once on a machine without Qt installed
./build.sh qt
環境変数 (すべてオプション — スクリプトが適切なデフォルトを設定します):
BN_INSTALL=/Applications/Binary\ Ninja.app/Contents/MacOS
Qt6_DIR=/usr/local/Qt-6.7.2/lib/cmake/Qt6
初回使用前に、build.bat の先頭にあるパスを環境に合わせて編集してください:
set "JAVA_HOME=C:\Program Files\Eclipse Adoptium\jdk-21.0.11.10-hotspot"
set "VSDEVCMD=C:\Program Files\Microsoft Visual Studio\2022\Professional\Common7\Tools\VsDevCmd.bat"
set "Qt6_DIR=C:\qt\v6.7.2\lib\cmake\Qt6"
set "BN_INSTALL=C:\Program Files\Vector35\BinaryNinja"
rem Incremental build of both components
build.bat
rem Full clean rebuild + install into BN plugins folder
build.bat clean install
rem Build only the C++ plugin
build.bat plugin
rem Build only the Java bridge
build.bat bridge
rem Build Qt once on a machine without Qt installed
build.bat qt
C++ ビルドは CMake FetchContent を使用して、api_REVISION.txt に記録された正確なコミットで binaryninja-api をクローンするため、プラグイン ABI は常にインストールされている BN バージョンと一致します。GHIDRA_HOME が設定されていない場合、Ghidra は最初の configure 時に CMake によって自動的にダウンロードされます。
インストール後、Binary Ninja の設定 (Edit → Preferences → Settings で「Ghidra」を検索) で以下を設定します:
| 設定 | 説明 |
|---|---|
ghidra.javaExe | java.exe へのフルパス |
ghidra.ghidraHome | Ghidra インストールのルート (Ghidra/Framework/… を含む) |
ghidra.trustAllCerts | Ghidra Server が自己署名証明書を使用している場合は true に設定 |
ghidra.defaultHost | 接続ダイアログを事前入力 |
ghidra.defaultPort | デフォルト: 13100 |
ghidra.defaultUser | 接続ダイアログを事前入力 |
ステップ 5 の前提条件: プログラムファイルが Ghidra Server リポジトリにコミットされている必要があります (Ghidra でローカルに開いているだけでは不十分)。Ghidra で: Project ウィンドウでファイルを右クリック → Version Control → Add to Version Control…。
プラグインとブリッジは、改行区切り JSON を使用してローカル TCP ソケットを介して通信します。すべてのリクエストは整数の id と文字列の op を持ち、すべてのレスポンスは id をエコーします。非同期イベント (サーバー側のリポジトリ変更) は代わりに "event" キーを持ちます。
| Op | 方向 | 目的 |
|---|---|---|
ping, status, connect, disconnect | request/response | セッションライフサイクル |
list_repos, open_repo, close_repo | request/response | リポジトリの列挙 |
list_items, get_subfolders | request/response | リポジトリの閲覧 |
get_versions, get_checkouts | request/response | バージョン管理の状態 |
checkout, terminate_checkout | request/response | 排他的書き込みロック |
open_db | request/response | Ghidra DB 全体を読み取り → JSON (重い) |
checkin | request/response | BN 側の変更を適用 → 新しいリポジトリバージョン (重い、ProgramApplier 経由) |
download_binary, upload_binary | request/response | 元のバイナリの入出力 |
delete_item | request/response | リポジトリからファイルを削除 |
repo_changed | event (async) | サーバー側の RepositoryChangeEvent プッシュ |
リポジトリには、ゼロから再ビルドするために必要なすべてが含まれています。git に含まれない開発者ごとのセットアップ:
git clone https://github.com/mutinylaboratories/ghidra_svr_bridge.git
cd ghidra_svr_bridge
git submodule update --init --recursive
bridge/gradle.properties を作成します:
ghidraHome=C:/Users/<you>/ghidra/ghidra_12.0.4_PUBLIC
Qt6_DIR を設定するか、./build.sh qt (Windows: build.bat qt) を 1 回実行します。binaryninja-api コミットを取得します:
./build.sh --channel stable # default — latest stable release (from GitHub)
./build.sh --channel dev # latest dev (dev branch head, from GitHub)
./build.sh --bn-api <commit> # explicit commit, no GitHub lookup (escape hatch)
--channel と --bn-api は相互に排他的です; どちらも指定しない場合は stable チャネルが使用されます。--channel は Vector35/binaryninja-api GitHub にクエリを実行するため (最新の stable/* リリース、または dev ブランチの先頭)、ネットワークアクセスが必要です。インストールされている BN が最新リリースより遅れている場合は、そのインストールの api_REVISION.txt から正確な SHA を指定して --bn-api を渡してください。新しい Claude Code セッションを開く際の最適なオンボーディングの手がかりは、この README と dev の現在の状態です:
bridge/src/main/java/com/ghidra_svr/bridge/ProgramApplier.javabridge/src/test/java/com/ghidra_svr/bridge/ProgramTestBase.javabridge/src/test/java/com/ghidra_svr/bridge/*RoundTripTest.javagit log --oneline — 各件名行に何が変更され、なぜかが書かれていますProgramApplier は is_local パラメータエントリをスキップします。BN のレジスタインデックスを Ghidra のストレージにマッピングするには、アーキテクチャごとのレジスタテーブル変換が必要だからです。パラメータは機能しますが、ローカル変数はまだ同期されません。DatabaseExporter は単一の RAM アドレス空間を想定しています。オーバーレイ空間やハーバードアーキテクチャでは、不正確なアドレスが生成される可能性があります。DBChangeSet を書き込みます。そのため、Ghidra のチェックアウト時マージ機構は、BN ユーザーと Ghidra ユーザー間の同時編集を自動解決できません — 最後の書き込みが優先されます。