
Plugin per Binary Ninja che sincronizza l'analisi in modo bidirezionale con un repository Ghidra Server, tramite un sottoprocesso bridge Java.
Un plugin Binary Ninja che si connette a un repository Ghidra Server e importa la sua analisi — simboli, nomi di funzione e commenti — direttamente in una binary view Binary Ninja aperta.
Ghidra e Binary Ninja hanno ciascuno i propri punti di forza. Questo plugin ti permette di usarli entrambi sullo stesso binario senza copiare manualmente nomi o commenti tra i due. Connettiti a un Ghidra Server in esecuzione, esplora i suoi repository e fai doppio clic su qualsiasi file di progetto per estrarre la sua analisi nella view BN attualmente aperta.
La sincronizzazione va in entrambe le direzioni.
| BN ← Ghidra (import) | BN → Ghidra (checkin) |
|---|
| Simboli (etichette, nomi di funzione) | ✓ | ✓ |
| Commenti (EOL/PRE/POST/PLATE/REP) | ✓ | ✓ |
| Firme di funzione (tipo di ritorno, calling convention) | ✓ | ✓ |
| Parametri di funzione (rinomina, retype, aggiunta) | ✓ | ✓ |
| Tipi di dato (struct/union/enum/typedef + pointer/array) | ✓ | ✓ |
| Equate (nomi di costanti + riferimenti) | ✓ | ✓ |
| Segnalibri | ✓ | ✓ |
| Elementi di dato tipizzati | ✓ | ✓ |
| Flag di funzione (thunk, no-return, inline) | ✓ come tag BN | — |
| Variabili locali (storage-aware) | ✓ | parziale — mappatura dello storage dei registri non implementata |
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)
Il plugin avvia un sottoprocesso Java (il "bridge") al caricamento. Il bridge mantiene la connessione RMI al Ghidra Server e parla un semplice protocollo JSON verso il plugin tramite un socket TCP locale. Questo tiene tutto il codice Java/RMI fuori dal processo C++ e permette alla JVM di avviarsi in background mentre BN termina il caricamento.
La JVM del bridge inizializza anche il framework Application di Ghidra all'avvio, così il percorso di scrittura può usare le API di alto livello del program-model di Ghidra (ProgramDB, DataTypeManager, SymbolTable, FunctionManager) invece delle scritture grezze db.Table.putRecord() — vedi Checkin write path sotto.
| Path | Linguaggio | Ruolo |
|---|---|---|
plugin/ | C++ / Qt6 | Plugin sidebar di Binary Ninja |
bridge/ | Java 17 | Client RMI Ghidra + server bridge JSON |
Plugin (C++):
plugin.cpp — registra le impostazioni e il widget della sidebar; avvia subito la JVM del bridge al caricamentoGhidraConnection.cpp — singleton; gestisce il ciclo di vita del bridge e tutte le operazioni basate su RMIBridgeProcess.cpp — lancia il JAR del bridge come sottoprocesso con pipe stdout/stderr; legge la riga di handshake READY port=NBridgeClient.cpp — client TCP; invia richieste JSON, riceve risposte, smista eventi asincroniSyncEngine.cpp — applica un GhidraDbExport a una BinaryView (simboli, commenti, flag)ui/ProjectPanel.cpp — widget della sidebar: albero dei repo, dialog di connessione, log delle attivitàui/ConnectDialog.cpp — dialog host/porta/utente/passwordBridge (Java):
BridgeMain.java — parsing degli argomenti; inizializza UniversalIdGenerator e il framework Application di Ghidra; avvia il server TCP; stampa READY port=N su stdoutBridgeServer.java — accetta una connessione client TCP e le assegna una BridgeConnectionBridgeConnection.java — dispatcher delle richieste JSON; serializza le risposte delle API Ghidra in JSON; gestisce opCheckin (crea una nuova versione del programma sul server)GhidraSession.java — sessione RMI autenticata; incapsula RemoteRepositoryServerHandleEventStreamer.java — thread in background per ogni repo aperto; invia RepositoryChangeEvent al plugin come eventi JSON asincroniDatabaseExporter.java — percorso di lettura: estrae le tabelle di simboli/commenti/flag di funzione/tipi di dato/equate/segnalibri da un ManagedBufferFileHandle (il buffer DB remoto di Ghidra) tramite accesso grezzo a db.jarProgramApplier.java — percorso di scrittura: apre il file buffer come un vero ProgramDB e applica tutte le modifiche lato BN tramite le API di alto livello di Ghidra (vedi Checkin write path sotto)DatabaseImporter.java — helper legacy di scrittura grezza mantenuti solo come seam di test; la apply(...) di produzione delega a ProgramApplieropCheckin apre il file buffer gestito del programma in modalità scrittura, costruisce un ProgramDB sopra di esso e applica le modifiche lato BN tramite le API del program-model di Ghidra. Le scritture grezze db.Table.putRecord() sono evitate — erano la fonte di ogni bug di corruzione al checkin che abbiamo mai incontrato:
| Scrittura di tier errato | Modalità di fallimento |
|---|---|
setIntValue(col, longTypeId) sulla tabella Function Data | IntField.setLongValue tronca silenziosamente con l2i → StackPurge corrotto a ogni aggiornamento di firma |
setByteValue(col, isUnion) su V5V6 Composite Data Types | la colonna è BooleanField in Ghidra 12.x → IllegalFieldAccessException ("Illegal field access") |
setIntValue(col, 0) sulla colonna V2 Typedef Flags | la colonna è ShortField → stesso crash, schema diverso |
| Scrittura dell'header composite senza righe di component-settings | CompositeEditorModel.cloneAllComponentSettings lancia ArrayIndexOutOfBoundsException quando la struct viene aperta in Ghidra |
Scrittura di un simbolo PARAMETER con SYM_ADDR_COL = indirizzo RAM | Address is not a VariableAddress lanciato da FunctionDB.loadSymbolBasedVariables a qualsiasi accesso alla funzione |
Passaggio di un DBChangeSet null a DBHandle.save() | il server scrive un file change-data da 0 byte → il checkout successivo fallisce con EOFException in ProgramContentHandler.loadProgramChangeSet |
ProgramApplier non ha queste trappole perché passa attraverso DataTypeManager.addDataType, SymbolTable.createLabel, Listing.setComment, Function.setReturnType, ecc. — API che mantengono automaticamente gli invarianti delle tabelle interconnesse di Ghidra. Esegue anche un passaggio cleanupBadVariableSymbols all'inizio di ogni checkin per eliminare la corruzione lasciata nel database dalle versioni precedenti del bridge.
server-package/CleanupBadVariableSymbols.java è un GhidraScript standalone che esegue la stessa pulizia tramite analyzeHeadless — utile quando un file è troppo corrotto per essere aperto nella GUI di Ghidra.
./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)
La suite è organizzata in cinque tier. I tier 2–4 esistono per dimostrare una proprietà: gli stessi dati compatibili finiscono per essere memorizzati sia nel .bndb sia nel program database di Ghidra (la matrice di compatibilità in cima a questo README).
| Tier | Cosa | Dove | Gate |
|---|---|---|---|
| 0 | Test unitari puri | plugin/test/*.cpp (binja-ghidra-tests), bridge *Test.java | sempre |
| 1 | Round-trip Ghidra-DB | bridge *RoundTripTest.java (ProgramApplier contro un vero ProgramDB) | richiede ghidra.home / GHIDRA_HOME |
| 2 | Round-trip BN BinaryView/.bndb | plugin/test/bn/ (binja-ghidra-bn-tests; headless binaryninjacore) | SKIP pulito senza una licenza BN headless-capable (env BN_LICENSE rispettata) |
| 3 | Parità cross-DB | CanonicalParityTest (C++ e Java) contro i golden condivisi in testdata/parity/fixtures/ | con i tier 1+2 |
| 4 | E2E live-server | bridge LiveServerE2ETest — avvia un vero ghidraSvr in una dir temporanea, popola tramite analyzeHeadless, guida checkout → export → checkin → re-export via RMI | test.bat --e2e (imposta GHIDRA_E2E=1) |
Parity oracle (tier 3). Entrambi i lati verificano indipendentemente contro lo stesso
JSON canonico versionato (la forma del DatabaseExporter del bridge). Direzione di import:
il golden viene caricato in un ProgramDB (Java) e in una BinaryView
tramite SyncEngine (C++), e ogni re-export deve essere uguale al golden. Direzione di checkin:
le modifiche BN scriptate devono produrre esattamente
fixtures/checkin/*/expected-preview.json (C++), e l'applicazione di quella preview tramite
ProgramApplier deve re-esportare come expected-after.json (Java). Se entrambi i lati
corrispondono ai golden condivisi, i due database concordano per transitività. Le modalità di confronto dei campi e la tabella di normalizzazione dei nomi di tipo si trovano in
testdata/parity/RULES.md; il binario di test è
testdata/bin/parity_x64.bin (layout in parity_x64.md).
Regression pin di lunga data sul lato Java:
DataTypesRoundTripTest.struct_cloneSettings_doesNotThrow — le impostazioni composite devono rimanere coerenti con l'header (crash di cloneAllComponentSettings)FunctionSignaturesRoundTripTest.returnType_doesNotCorruptStackPurge — troncamento di IntFieldParametersRoundTripTest.noParameterSymbol_endsUpAtRamAddress — invariante VariableAddressI test di round-trip e parità richiedono un'installazione di Ghidra (usata a runtime per
i language services). Il percorso viene letto dalla proprietà di sistema Gradle ghidra.home o dalla variabile d'ambiente GHIDRA_HOME; build.gradle passa ghidraHome per
default. I test C++ dei tier 2/3 richiedono inoltre che binaryninjacore sia caricabile
(gli script mettono la dir di installazione di BN nel PATH).
| Dipendenza | Note |
|---|---|
| Binary Ninja (commerciale) | Testato contro la versione corrispondente a api_REVISION.txt nell'installazione BN |
| Ghidra Server | Testato con Ghidra 12.0.4. Deve essere in esecuzione e raggiungibile via RMI/SSL |
| Java 17+ JDK | Consigliato Eclipse Adoptium JDK 21 |
| CMake 3.24+ | |
| Ninja | |
| Compilatore C++ | MSVC 2022+ su Windows; clang su macOS; gcc/clang su Linux |
| Qt 6.7+ | Vedi Qt setup sotto; qmake deve essere nel PATH al momento della build |
| Gradle (tramite wrapper) | Il bridge usa il Gradle wrapper — nessuna installazione separata necessaria |
| Poetry (solo build Qt) | Necessario solo quando si compila Qt dal submodule qt-build. Installa con pip install poetry o pipx install poetry. |
| libclang 19 (solo build Qt) | Richiesto dal build system di Qt. Vedi qt-build/README.md per le istruzioni di download. |
Il plugin si collega alla stessa build di Qt 6 usata da Binary Ninja. Hai due opzioni:
Opzione A — Usa un'installazione Qt esistente (la più veloce se hai già Qt)
Passa Qt6_DIR puntando alla tua directory CMake di Qt:
Qt6_DIR=/path/to/Qt/6.x.y/clang_64/lib/cmake/Qt6 ./build.sh
Su macOS lo script di build rileva automaticamente Qt se è stato installato dal Qt online installer sotto /usr/local/Qt*.
Opzione B — Compila Qt dal submodule qt-build (~1-2 ore, una volta per macchina)
Il submodule qt-build (gli script di build di Qt di Vector35) compila Qt 6 con le patch di Binary Ninja. Richiede Poetry e libclang 19 (vedi Prerequisiti sopra e qt-build/README.md).
Qt viene installato in qt/<version>/<compiler>/ all'interno del repo:
| Piattaforma | Percorso di installazione |
|---|---|
| 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
Il passaggio qt è necessario solo una volta. CMake e gli script di build rilevano il Qt compilato in qt/ a ogni esecuzione successiva e saltano completamente il submodule. La directory qt/ è in 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)
Poi segui il Qt setup sopra (Opzione A o B), ed esegui:
./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
Variabili d'ambiente (tutte opzionali — lo script imposta default sensati):
BN_INSTALL=/Applications/Binary\ Ninja.app/Contents/MacOS
Qt6_DIR=/usr/local/Qt-6.7.2/lib/cmake/Qt6
Modifica i percorsi in cima a build.bat per adattarli al tuo ambiente prima del primo utilizzo:
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
La build C++ usa CMake FetchContent per clonare binaryninja-api al commit esatto registrato in api_REVISION.txt, così l'ABI del plugin corrisponde sempre alla versione BN installata. Ghidra viene scaricato automaticamente da CMake alla prima configurazione se GHIDRA_HOME non è impostata.
Dopo l'installazione, imposta questi valori nelle impostazioni di Binary Ninja (Edit → Preferences → Settings, cerca "Ghidra"):
| Impostazione | Descrizione |
|---|---|
ghidra.javaExe | Percorso completo di java.exe |
ghidra.ghidraHome | Radice della tua installazione Ghidra (contiene Ghidra/Framework/…) |
ghidra.trustAllCerts | Imposta true se il tuo Ghidra Server usa un certificato self-signed |
ghidra.defaultHost | Precompila il dialog Connect |
ghidra.defaultPort | Default: 13100 |
ghidra.defaultUser | Precompila il dialog Connect |
Prerequisito per il passaggio 5: il file del programma deve essere committato nel repository del Ghidra Server (non solo aperto localmente in Ghidra). In Ghidra: fai clic destro sul file nella finestra Project → Version Control → Add to Version Control….
Il plugin e il bridge comunicano tramite un socket TCP locale usando JSON delimitato da newline. Ogni richiesta porta un id intero e una stringa op; ogni risposta ripete l'id. Gli eventi asincroni (modifiche ai repository lato server) portano invece una chiave "event".
| Op | Direzione | Scopo |
|---|---|---|
ping, status, connect, disconnect | request/response | ciclo di vita della sessione |
list_repos, open_repo, close_repo | request/response | enumerazione dei repo |
list_items, get_subfolders | request/response | navigazione dei repo |
get_versions, get_checkouts | request/response | stato del version control |
checkout, terminate_checkout | request/response | lock di scrittura esclusivo |
open_db | request/response | legge l'intero DB Ghidra → JSON (pesante) |
checkin | request/response | applica le modifiche lato BN → nuova versione del repo (pesante, tramite ProgramApplier) |
download_binary, upload_binary | request/response | sposta il binario originale dentro/fuori |
delete_item | request/response | rimuove un file dal repo |
repo_changed | evento (async) | push di RepositoryChangeEvent lato server |
Il repository contiene tutto il necessario per ricostruire da zero. Setup per sviluppatore che non è in 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 a un'installazione esistente oppure esegui ./build.sh qt (Windows: build.bat qt) una volta.binaryninja-api corrispondente da GitHub:
./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 e --bn-api sono mutuamente esclusivi; senza nessuno dei due, viene usato il canale stable. --channel interroga GitHub Vector35/binaryninja-api (l'ultima release stable/*, o la testa del branch dev) quindi richiede accesso alla rete. Se la tua BN installata è indietro rispetto all'ultima release, passa --bn-api con lo SHA esatto dal api_REVISION.txt di quell'installazione.Quando apri una nuova sessione di Claude Code, i migliori punti di onboarding sono questo README più lo stato attuale su 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 — ogni riga di oggetto dice cosa è cambiato e perchéProgramApplier salta le voci di parametro is_local perché mappare gli indici dei registri BN allo storage di Ghidra richiede una traduzione della register-table per architettura. I parametri funzionano; le locali non si sincronizzano ancora.DatabaseExporter assume un singolo spazio di indirizzamento RAM. Gli spazi overlay o le architetture Harvard possono produrre indirizzi errati.DBChangeSet vuoto per mantenere funzionanti i checkout. La macchina di merge-on-checkout di Ghidra quindi non può risolvere automaticamente le modifiche concorrenti tra utenti BN e Ghidra — vince l'ultimo che scrive.