
Binary-Ninja-Plugin, das die Analyse bidirektional mit einem Ghidra-Server-Repository über einen Java-Bridge-Subprozess synchronisiert.
Ein Binary-Ninja-Plugin, das sich mit einem Ghidra-Server-Repository verbindet und dessen Analyse – Symbole, Funktionsnamen und Kommentare – direkt in eine geöffnete Binary-Ninja-Binary-View importiert.
Ghidra und Binary Ninja haben jeweils ihre Stärken. Dieses Plugin ermöglicht es, beide auf derselben Binary zu verwenden, ohne Namen oder Kommentare manuell zwischen ihnen kopieren zu müssen. Verbinde dich mit einem laufenden Ghidra-Server, durchsuche dessen Repositories und doppelklicke auf eine beliebige Projektdatei, um deren Analyse in die aktuell geöffnete BN-View zu übernehmen.
Die Synchronisierung läuft in beide Richtungen.
| BN ← Ghidra (Import) | BN → Ghidra (Checkin) | |
|---|---|---|
| Symbole (Labels, Funktionsnamen) | ✓ | ✓ |
| Kommentare (EOL/PRE/POST/PLATE/REP) | ✓ | ✓ |
| Funktionssignaturen (Rückgabetyp, Aufrufkonvention) | ✓ | ✓ |
| Funktionsparameter (Umbenennen, Retypisieren, Hinzufügen) | ✓ | ✓ |
| Datentypen (struct/union/enum/typedef + Zeiger/Array) | ✓ | ✓ |
| Equates (Konstantennamen + Referenzen) | ✓ | ✓ |
| Lesezeichen | ✓ | ✓ |
| Typisierte Datenelemente | ✓ | ✓ |
| Funktions-Flags (Thunk, No-Return, Inline) | ✓ als BN-Tags | — |
| Lokale Variablen (Storage-bewusst) | ✓ | teilweise — Register-Storage-Mapping nicht implementiert |
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)
Das Plugin startet beim Laden einen Java-Subprozess (die „Bridge"). Die Bridge hält die RMI-Verbindung zum Ghidra-Server und spricht ein einfaches JSON-Protokoll über einen lokalen TCP-Socket zurück zum Plugin. Dadurch bleibt jeglicher Java/RMI-Code aus dem C++-Prozess heraus, und die JVM kann im Hintergrund starten, während BN das Laden abschließt.
Die Bridge-JVM initialisiert beim Start außerdem das Application-Framework von Ghidra, damit der Schreibpfad die High-Level-Program-Model-APIs von Ghidra (ProgramDB, DataTypeManager, SymbolTable, FunctionManager) verwenden kann statt roher db.Table.putRecord()-Schreibvorgänge — siehe Checkin-Schreibpfad unten.
| Pfad | Sprache | Rolle |
|---|---|---|
plugin/ | C++ / Qt6 | Binary-Ninja-Sidebar-Plugin |
bridge/ | Java 17 | Ghidra-RMI-Client + JSON-Bridge-Server |
Plugin (C++):
plugin.cpp — registriert Einstellungen und das Sidebar-Widget; startet die Bridge-JVM beim Laden eifrigGhidraConnection.cpp — Singleton; verwaltet den Bridge-Lebenszyklus und alle RMI-gestützten OperationenBridgeProcess.cpp — startet das Bridge-JAR als Subprozess mit stdout/stderr-Pipes; liest die READY port=N-Handshake-ZeileBridgeClient.cpp — TCP-Client; sendet JSON-Anfragen, empfängt Antworten, verteilt asynchrone EventsSyncEngine.cpp — wendet ein GhidraDbExport auf eine BinaryView an (Symbole, Kommentare, Flags)ui/ProjectPanel.cpp — Sidebar-Widget: Repo-Baum, Verbindungsdialog, Aktivitätsprotokollui/ConnectDialog.cpp — Host/Port/Benutzer/Passwort-DialogBridge (Java):
BridgeMain.java — Argument-Parsing; initialisiert UniversalIdGenerator und das Ghidra-Application-Framework; startet den TCP-Server; gibt READY port=N auf stdout ausBridgeServer.java — akzeptiert eine TCP-Client-Verbindung und übergibt ihr eine BridgeConnectionBridgeConnection.java — JSON-Request-Dispatcher; serialisiert Ghidra-API-Antworten nach JSON; behandelt opCheckin (erstellt eine neue Programmversion auf dem Server)GhidraSession.java — authentifizierte RMI-Session; umschließt RemoteRepositoryServerHandleEventStreamer.java — Hintergrund-Thread pro geöffnetem Repo; schiebt RepositoryChangeEvents als asynchrone JSON-Events zum PluginDatabaseExporter.java — Lesepfad: extrahiert Symbol-/Kommentar-/Funktions-Flag-/Datentyp-/Equate-/Lesezeichen-Tabellen aus einem ManagedBufferFileHandle (Ghidras Remote-DB-Buffer) über rohen db.jar-ZugriffProgramApplier.java — Schreibpfad: öffnet die Buffer-Datei als echte ProgramDB und wendet alle BN-seitigen Änderungen über Ghidras High-Level-APIs an (siehe Checkin-Schreibpfad unten)DatabaseImporter.java — Legacy-Raw-Write-Helfer, nur als Test-Seam beibehalten; das produktive apply(...) delegiert an ProgramApplieropCheckin öffnet die Managed-Buffer-Datei des Programms im Schreibmodus, konstruiert eine ProgramDB darüber und wendet BN-seitige Änderungen über Ghidras Program-Model-APIs an. Rohe db.Table.putRecord()-Schreibvorgänge werden vermieden — sie waren die Quelle jedes Checkin-Korruptionsfehlers, den wir je hatten:
| Falsch-Tier-Schreibvorgang | Fehlermodus |
|---|---|
setIntValue(col, longTypeId) auf Function-Data-Tabelle | IntField.setLongValue schneidet still per l2i ab → StackPurge bei jedem Signatur-Update korrumpiert |
setByteValue(col, isUnion) auf V5V6 Composite Data Types | Spalte ist BooleanField in Ghidra 12.x → IllegalFieldAccessException („Illegal field access") |
setIntValue(col, 0) auf V2 Typedef-Flags-Spalte | Spalte ist ShortField → gleicher Absturz, anderes Schema |
| Schreiben des Composite-Headers ohne Component-Settings-Zeilen | CompositeEditorModel.cloneAllComponentSettings wirft ArrayIndexOutOfBoundsException, wenn das Struct in Ghidra geöffnet wird |
Schreiben eines PARAMETER-Symbols mit SYM_ADDR_COL = RAM-Adresse | Address is not a VariableAddress wird von FunctionDB.loadSymbolBasedVariables bei jedem Funktionszugriff geworfen |
Übergeben von null DBChangeSet an DBHandle.save() | Server schreibt eine 0-Byte-Change-Data-Datei → nächster Checkout schlägt mit EOFException in ProgramContentHandler.loadProgramChangeSet fehl |
ProgramApplier hat diese Fallen nicht, weil er über DataTypeManager.addDataType, SymbolTable.createLabel, Listing.setComment, Function.setReturnType usw. läuft — APIs, die Ghidras ineinandergreifende Tabelleninvarianten automatisch wahren. Er führt außerdem zu Beginn jedes Checkins einen cleanupBadVariableSymbols-Durchlauf aus, um Korruption zu bereinigen, die ältere Bridge-Versionen in der Datenbank hinterlassen haben.
server-package/CleanupBadVariableSymbols.java ist ein eigenständiges GhidraScript, das dieselbe Bereinigung über analyzeHeadless ausführt — nützlich, wenn eine Datei zu korrupt ist, um sie in der Ghidra-GUI zu öffnen.
./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)
Die Suite ist in fünf Tiers organisiert. Tiers 2–4 existieren, um eine Eigenschaft zu beweisen: dieselben kompatiblen Daten landen sowohl in der .bndb als auch in der Ghidra-Programmdatenbank (die Kompatibilitätsmatrix oben in dieser README).
| Tier | Was | Wo | Gate |
|---|---|---|---|
| 0 | Reine Unit-Tests | plugin/test/*.cpp (binja-ghidra-tests), Bridge *Test.java | immer |
| 1 | Ghidra-DB-Round-Trip | Bridge *RoundTripTest.java (ProgramApplier gegen eine echte ProgramDB) | benötigt ghidra.home / GHIDRA_HOME |
| 2 | BN BinaryView/.bndb-Round-Trip | plugin/test/bn/ (binja-ghidra-bn-tests; headless binaryninjacore) | SKIPt sauber ohne headless-fähige BN-Lizenz (BN_LICENSE-Env wird berücksichtigt) |
| 3 | Cross-DB-Parität | CanonicalParityTest (C++ und Java) gegen die gemeinsamen Goldens in testdata/parity/fixtures/ | mit Tiers 1+2 |
| 4 | Live-Server-E2E | Bridge LiveServerE2ETest — bootet einen echten ghidraSvr in einem Temp-Verzeichnis, seedet via analyzeHeadless, fährt Checkout → Export → Checkin → Re-Export über RMI | test.bat --e2e (setzt GHIDRA_E2E=1) |