
Binary Ninja plugin that syncs analysis bidirectionally with a Ghidra Server repository, via a Java bridge subprocess.
A Binary Ninja plugin that connects to a Ghidra Server repository and imports its analysis — symbols, function names, and comments — directly into an open Binary Ninja binary view.
Ghidra and Binary Ninja each have strengths. This plugin lets you use both on the same binary without manually copying names or comments between them. Connect to a running Ghidra Server, browse its repositories, and double-click any project file to pull its analysis into the currently open BN view.
Sync goes both directions.
| BN ← Ghidra (import) | BN → Ghidra (checkin) | |
|---|---|---|
| Symbols (labels, function names) | ✓ | ✓ |
| Comments (EOL/PRE/POST/PLATE/REP) | ✓ | ✓ |
| Function signatures (return type, calling convention) | ✓ | ✓ |
| Function parameters (rename, retype, add) | ✓ | ✓ |
| Data types (struct/union/enum/typedef + pointer/array) | ✓ | ✓ |
| Equates (constant names + references) | ✓ | ✓ |
| Bookmarks | ✓ | ✓ |
| Typed data items | ✓ | ✓ |
| Function flags (thunk, no-return, inline) | ✓ as BN tags | — |
| Local variables (storage-aware) | ✓ | partial — register-storage mapping not implemented |
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)
The plugin spawns a Java subprocess (the "bridge") on load. The bridge holds the RMI connection to the Ghidra Server and speaks a simple JSON protocol back to the plugin over a local TCP socket. This keeps all Java/RMI code out of the C++ process and lets the JVM start in the background while BN finishes loading.
The bridge JVM also initialises Ghidra's Application framework at startup so the write path can use Ghidra's high-level program-model APIs (ProgramDB, DataTypeManager, SymbolTable, FunctionManager) rather than raw db.Table.putRecord() writes — see Checkin write path below.
| Path | Language | Role |
|---|---|---|
plugin/ | C++ / Qt6 | Binary Ninja sidebar plugin |
bridge/ | Java 17 | Ghidra RMI client + JSON bridge server |
Plugin (C++):
plugin.cpp — registers settings and the sidebar widget; eagerly starts the bridge JVM on loadGhidraConnection.cpp — singleton; manages bridge lifecycle and all RMI-backed operationsBridgeProcess.cpp — launches the bridge JAR as a subprocess with stdout/stderr pipes; reads the READY port=N handshake lineBridgeClient.cpp — TCP client; sends JSON requests, receives responses, dispatches async eventsSyncEngine.cpp — applies a GhidraDbExport to a BinaryView (symbols, comments, flags)ui/ProjectPanel.cpp — sidebar widget: repo tree, connect dialog, activity logui/ConnectDialog.cpp — host/port/user/password dialogBridge (Java):
BridgeMain.java — argument parsing; initialises UniversalIdGenerator and the Ghidra Application framework; starts the TCP server; prints READY port=N to stdoutBridgeServer.java — accepts one TCP client connection and hands it a BridgeConnectionBridgeConnection.java — JSON request dispatcher; serialises Ghidra API responses to JSON; handles opCheckin (creates a new program version on the server)GhidraSession.java — authenticated RMI session; wraps RemoteRepositoryServerHandleEventStreamer.java — background thread per open repo; pushes RepositoryChangeEvents to the plugin as async JSON eventsDatabaseExporter.java — read path: extracts symbol/comment/function-flag/data-type/equate/bookmark tables from a ManagedBufferFileHandle (Ghidra's remote DB buffer) via raw db.jar accessProgramApplier.java — write path: opens the buffer file as a real ProgramDB and applies all BN-side changes through Ghidra's high-level APIs (see Checkin write path below)DatabaseImporter.java — legacy raw-write helpers kept only as a test seam; production apply(...) delegates to ProgramApplieropCheckin opens the program's managed buffer file in write mode, constructs a ProgramDB over it, and applies BN-side changes through Ghidra's program-model APIs. Raw db.Table.putRecord() writes are avoided — they were the source of every checkin-corruption bug we ever hit:
| Wrong-tier write | Failure mode |
|---|---|
setIntValue(col, longTypeId) on Function Data table | IntField.setLongValue silently l2i-truncates → StackPurge corrupted on every signature update |
setByteValue(col, isUnion) on V5V6 Composite Data Types | column is BooleanField in Ghidra 12.x → IllegalFieldAccessException ("Illegal field access") |
setIntValue(col, 0) on V2 Typedef Flags column | column is ShortField → same crash, different schema |
| Writing composite header without component-settings rows | CompositeEditorModel.cloneAllComponentSettings throws ArrayIndexOutOfBoundsException when struct is opened in Ghidra |
Writing PARAMETER symbol with SYM_ADDR_COL = RAM address | Address is not a VariableAddress thrown by FunctionDB.loadSymbolBasedVariables on any function access |
Passing null DBChangeSet to DBHandle.save() | server writes a 0-byte change-data file → next checkout fails with EOFException in ProgramContentHandler.loadProgramChangeSet |
ProgramApplier doesn't have these traps because it routes through DataTypeManager.addDataType, SymbolTable.createLabel, Listing.setComment, Function.setReturnType, etc. — APIs that maintain Ghidra's interlocking-table invariants automatically. It also runs a cleanupBadVariableSymbols pass at the start of every checkin to purge corruption left in the database by older bridge versions.
server-package/CleanupBadVariableSymbols.java is a standalone GhidraScript that runs the same cleanup via analyzeHeadless — useful when a file is too corrupted to open in the 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)
The suite is organised in five tiers. Tiers 2–4 exist to prove one property: the same compatible data ends up stored in both the .bndb and the Ghidra program database (the compatibility matrix at the top of this README).
| Tier | What | Where | Gate |
|---|---|---|---|
| 0 | Pure unit tests | plugin/test/*.cpp (binja-ghidra-tests), bridge *Test.java | always |
| 1 | Ghidra-DB round-trip | bridge *RoundTripTest.java (ProgramApplier against a real ProgramDB) | needs ghidra.home / GHIDRA_HOME |
| 2 | BN BinaryView/.bndb round-trip | plugin/test/bn/ (binja-ghidra-bn-tests; headless binaryninjacore) | SKIPs cleanly without a headless-capable BN license (BN_LICENSE env honoured) |
| 3 | Cross-DB parity | CanonicalParityTest (C++ and Java) against the shared goldens in testdata/parity/fixtures/ | with tiers 1+2 |
| 4 | Live-server E2E | bridge LiveServerE2ETest — boots a real ghidraSvr in a temp dir, seeds via analyzeHeadless, drives checkout → export → checkin → re-export over RMI | test.bat --e2e (sets GHIDRA_E2E=1) |