
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 時間)