
Plugin Binary Ninja qui synchronise l'analyse de manière bidirectionnelle avec un dépôt Ghidra Server, via un sous-processus de pont Java.
Un plugin Binary Ninja qui se connecte à un dépôt Ghidra Server et importe son analyse — symboles, noms de fonctions et commentaires — directement dans une vue binaire Binary Ninja ouverte.
Ghidra et Binary Ninja ont chacun leurs forces. Ce plugin vous permet d'utiliser les deux sur le même binaire sans copier manuellement les noms ou les commentaires entre eux. Connectez-vous à un Ghidra Server en cours d'exécution, parcourez ses dépôts, et double-cliquez sur n'importe quel fichier de projet pour extraire son analyse dans la vue BN actuellement ouverte.
La synchronisation se fait dans les deux sens.
| BN ← Ghidra (import) | BN → Ghidra (checkin) |
|---|
| Symboles (labels, noms de fonctions) | ✓ | ✓ |
| Commentaires (EOL/PRE/POST/PLATE/REP) | ✓ | ✓ |
| Signatures de fonctions (type de retour, convention d'appel) | ✓ | ✓ |
| Paramètres de fonctions (renommer, retyper, ajouter) | ✓ | ✓ |
| Types de données (struct/union/enum/typedef + pointeur/tableau) | ✓ | ✓ |
| Équates (noms de constantes + références) | ✓ | ✓ |
| Signets | ✓ | ✓ |
| Éléments de données typés | ✓ | ✓ |
| Indicateurs de fonctions (thunk, no-return, inline) | ✓ en tant que tags BN | — |
| Variables locales (sensibles au stockage) | ✓ | partiel — le mapping du stockage par registre n'est pas implémenté |
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)
Le plugin lance un sous-processus Java (le « bridge ») au chargement. Le bridge maintient la connexion RMI vers le Ghidra Server et parle un protocole JSON simple au plugin via un socket TCP local. Cela garde tout le code Java/RMI hors du processus C++ et permet à la JVM de démarrer en arrière-plan pendant que BN termine son chargement.
La JVM du bridge initialise également le framework Application de Ghidra au démarrage, afin que le chemin d'écriture puisse utiliser les API de haut niveau du modèle de programme de Ghidra (ProgramDB, DataTypeManager, SymbolTable, FunctionManager) plutôt que des écritures brutes db.Table.putRecord() — voir Checkin write path ci-dessous.
| Chemin | Langage | Rôle |
|---|---|---|
plugin/ | C++ / Qt6 | Plugin de barre latérale Binary Ninja |
bridge/ | Java 17 | Client RMI Ghidra + serveur bridge JSON |
Plugin (C++) :
plugin.cpp — enregistre les paramètres et le widget de barre latérale ; démarre la JVM du bridge au chargementGhidraConnection.cpp — singleton ; gère le cycle de vie du bridge et toutes les opérations basées sur RMIBridgeProcess.cpp — lance le JAR du bridge comme sous-processus avec des pipes stdout/stderr ; lit la ligne de handshake READY port=NBridgeClient.cpp — client TCP ; envoie des requêtes JSON, reçoit des réponses, distribue les événements asynchronesSyncEngine.cpp — applique un GhidraDbExport à un BinaryView (symboles, commentaires, indicateurs)ui/ProjectPanel.cpp — widget de barre latérale : arborescence des dépôts, boîte de dialogue de connexion, journal d'activitéui/ConnectDialog.cpp — boîte de dialogue hôte/port/utilisateur/mot de passeBridge (Java) :
BridgeMain.java — analyse des arguments ; initialise UniversalIdGenerator et le framework Application de Ghidra ; démarre le serveur TCP ; affiche READY port=N sur stdoutBridgeServer.java — accepte une connexion client TCP et lui attribue une BridgeConnectionBridgeConnection.java — répartiteur de requêtes JSON ; sérialise les réponses de l'API Ghidra en JSON ; gère opCheckin (crée une nouvelle version du programme sur le serveur)GhidraSession.java — session RMI authentifiée ; encapsule RemoteRepositoryServerHandleEventStreamer.java — thread d'arrière-plan par dépôt ouvert ; pousse les RepositoryChangeEvent vers le plugin sous forme d'événements JSON asynchronesDatabaseExporter.java — chemin de lecture : extrait les tables symboles/commentaires/indicateurs de fonctions/types de données/équates/signets d'un ManagedBufferFileHandle (le buffer de base de données distant de Ghidra) via un accès brut à db.jarProgramApplier.java — chemin d'écriture : ouvre le fichier buffer comme un véritable ProgramDB et applique tous les changements côté BN via les API de haut niveau de Ghidra (voir Checkin write path ci-dessous)DatabaseImporter.java — helpers d'écriture brute hérités conservés uniquement comme point de test ; la méthode apply(...) de production délègue à ProgramApplieropCheckin ouvre le fichier buffer managé du programme en mode écriture, construit un ProgramDB par-dessus, et applique les changements côté BN via les API du modèle de programme de Ghidra. Les écritures brutes db.Table.putRecord() sont évitées — elles étaient la source de tous les bugs de corruption de checkin que nous avons rencontrés :
| Écriture au mauvais niveau | Mode de défaillance |
|---|---|
setIntValue(col, longTypeId) sur la table Function Data | IntField.setLongValue tronque silencieusement via l2i → StackPurge corrompu à chaque mise à jour de signature |
setByteValue(col, isUnion) sur V5V6 Composite Data Types | la colonne est un BooleanField dans Ghidra 12.x → IllegalFieldAccessException (« Illegal field access ») |
setIntValue(col, 0) sur la colonne V2 Typedef Flags | la colonne est un ShortField → même crash, schéma différent |
| Écriture d'un en-tête composite sans lignes de paramètres de composants | CompositeEditorModel.cloneAllComponentSettings lève une ArrayIndexOutOfBoundsException quand la struct est ouverte dans Ghidra |
Écriture d'un symbole PARAMETER avec SYM_ADDR_COL = adresse RAM | Address is not a VariableAddress levée par FunctionDB.loadSymbolBasedVariables à tout accès à la fonction |
Passage d'un DBChangeSet null à DBHandle.save() | le serveur écrit un fichier de données de changement de 0 octet → le prochain checkout échoue avec EOFException dans ProgramContentHandler.loadProgramChangeSet |
ProgramApplier n'a pas ces pièges car il passe par DataTypeManager.addDataType, SymbolTable.createLabel, Listing.setComment, Function.setReturnType, etc. — des API qui maintiennent automatiquement les invariants des tables imbriquées de Ghidra. Il exécute également une passe cleanupBadVariableSymbols au début de chaque checkin pour purger la corruption laissée dans la base de données par les anciennes versions du bridge.
server-package/CleanupBadVariableSymbols.java est un GhidraScript autonome qui exécute le même nettoyage via analyzeHeadless — utile quand un fichier est trop corrompu pour être ouvert dans l'interface graphique de 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 est organisée en cinq niveaux. Les niveaux 2 à 4 existent pour prouver une propriété : les mêmes données compatibles finissent stockées à la fois dans le .bndb et dans la base de données de programme Ghidra (la matrice de compatibilité en haut de ce README).
| Niveau | Quoi | Où | Condition |
|---|---|---|---|
| 0 | Tests unitaires purs | plugin/test/*.cpp (binja-ghidra-tests), bridge *Test.java | toujours |
| 1 | Aller-retour Ghidra-DB | bridge *RoundTripTest.java (ProgramApplier contre un vrai ProgramDB) | nécessite ghidra.home / GHIDRA_HOME |
| 2 | Aller-retour BN BinaryView/.bndb | plugin/test/bn/ (binja-ghidra-bn-tests ; binaryninjacore headless) | SKIP proprement sans licence BN compatible headless (variable d'env BN_LICENSE honorée) |
| 3 | Parité inter-DB | CanonicalParityTest (C++ et Java) contre les goldens partagés dans testdata/parity/fixtures/ | avec les niveaux 1+2 |
| 4 | E2E serveur live | bridge LiveServerE2ETest — démarre un vrai ghidraSvr dans un répertoire temporaire, initialise via analyzeHeadless, pilote checkout → export → checkin → re-export via RMI | test.bat --e2e (définit GHIDRA_E2E=1) |
Oracle de parité (niveau 3). Les deux côtés vérifient indépendamment contre le même JSON canonique versionné (la forme du DatabaseExporter du bridge). Sens de l'import : le golden se charge dans un ProgramDB (Java) et dans un BinaryView via SyncEngine (C++), et chaque ré-export doit être égal au golden. Sens du checkin : des modifications BN scriptées doivent produire exactement fixtures/checkin/*/expected-preview.json (C++), et l'application de cet aperçu via ProgramApplier doit se ré-exporter en expected-after.json (Java). Si les deux côtés correspondent aux goldens partagés, les deux bases de données concordent par transitivité. Les modes de comparaison de champs et la table de normalisation des noms de types se trouvent dans testdata/parity/RULES.md ; le binaire de test est testdata/bin/parity_x64.bin (disposition dans parity_x64.md).
Pins de régression de longue date côté Java :
DataTypesRoundTripTest.struct_cloneSettings_doesNotThrow — les paramètres composites doivent rester cohérents avec l'en-tête (crash cloneAllComponentSettings)FunctionSignaturesRoundTripTest.returnType_doesNotCorruptStackPurge — troncature IntFieldParametersRoundTripTest.noParameterSymbol_endsUpAtRamAddress — invariant VariableAddressLes tests d'aller-retour et de parité nécessitent une installation de Ghidra (utilisée à l'exécution pour les services de langage). Le chemin est lu depuis la propriété système Gradle ghidra.home ou la variable d'env GHIDRA_HOME ; build.gradle transmet ghidraHome par défaut. Les tests C++ des niveaux 2/3 nécessitent en plus que binaryninjacore soit chargeable (les scripts placent le répertoire d'installation de BN dans le PATH).
| Dépendance | Notes |
|---|---|
| Binary Ninja (commercial) | Testé contre la version correspondant à api_REVISION.txt dans l'installation BN |
| Ghidra Server | Testé avec Ghidra 12.0.4. Doit être en cours d'exécution et joignable via RMI/SSL |
| Java 17+ JDK | Eclipse Adoptium JDK 21 recommandé |
| CMake 3.24+ | |
| Ninja | |
| Compilateur C++ | MSVC 2022+ sous Windows ; clang sous macOS ; gcc/clang sous Linux |
| Qt 6.7+ | Voir Qt setup ci-dessous ; qmake doit être dans le PATH au moment de la compilation |
| Gradle (via wrapper) | Le bridge utilise le wrapper Gradle — aucune installation séparée nécessaire |
| Poetry (build Qt uniquement) | Requis uniquement lors de la compilation de Qt depuis le sous-module qt-build. Installer avec pip install poetry ou pipx install poetry. |
| libclang 19 (build Qt uniquement) | Requis par le système de build de Qt. Voir qt-build/README.md pour les instructions de téléchargement. |
Le plugin se lie à la même version de Qt 6 que celle utilisée par Binary Ninja. Vous avez deux options :
Option A — Utiliser une installation Qt existante (le plus rapide si vous avez déjà Qt)
Passez Qt6_DIR pointant vers votre répertoire CMake Qt :
Qt6_DIR=/path/to/Qt/6.x.y/clang_64/lib/cmake/Qt6 ./build.sh
Sous macOS, le script de build détecte automatiquement Qt s'il a été installé par l'installateur en ligne de Qt sous /usr/local/Qt*.
Option B — Compiler Qt depuis le sous-module qt-build (~1-2 heures, une fois par machine)
Le sous-module qt-build (scripts de build Qt de Vector35) compile Qt 6 avec les patchs de Binary Ninja. Il nécessite Poetry et libclang 19 (voir Prérequis ci-dessus et qt-build/README.md).
Qt est installé dans qt/<version>/<compiler>/ à l'intérieur du dépôt :
| Plateforme | Chemin d'installation |
|---|---|
| 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
L'étape qt n'est nécessaire qu'une seule fois. CMake et les scripts de build détectent le Qt compilé dans qt/ à chaque exécution suivante et ignorent complètement le sous-module. Le répertoire qt/ est ignoré par git.
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)
Suivez ensuite la configuration Qt ci-dessus (Option A ou B), puis exécutez :
./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
Variables d'environnement (toutes optionnelles — le script définit des valeurs par défaut raisonnables) :
BN_INSTALL=/Applications/Binary\ Ninja.app/Contents/MacOS
Qt6_DIR=/usr/local/Qt-6.7.2/lib/cmake/Qt6
Modifiez les chemins en haut de build.bat pour qu'ils correspondent à votre environnement avant la première utilisation :
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 compilation C++ utilise CMake FetchContent pour cloner binaryninja-api au commit exact enregistré dans api_REVISION.txt, de sorte que l'ABI du plugin corresponde toujours à la version BN installée. Ghidra est téléchargé automatiquement par CMake lors de la première configuration si GHIDRA_HOME n'est pas défini.
Après l'installation, définissez ces paramètres dans les réglages de Binary Ninja (Edit → Preferences → Settings, recherchez « Ghidra ») :
| Paramètre | Description |
|---|---|
ghidra.javaExe | Chemin complet vers java.exe |
ghidra.ghidraHome | Racine de votre installation Ghidra (contient Ghidra/Framework/…) |
ghidra.trustAllCerts | Mettez true si votre Ghidra Server utilise un certificat auto-signé |
ghidra.defaultHost | Pré-remplit la boîte de dialogue Connect |
ghidra.defaultPort | Par défaut : 13100 |
ghidra.defaultUser | Pré-remplit la boîte de dialogue Connect |
Prérequis pour l'étape 5 : le fichier de programme doit être commité dans le dépôt du Ghidra Server (pas seulement ouvert localement dans Ghidra). Dans Ghidra : clic droit sur le fichier dans la fenêtre Project → Version Control → Add to Version Control….
Le plugin et le bridge communiquent via un socket TCP local en utilisant du JSON délimité par des retours à la ligne. Chaque requête porte un id entier et un op chaîne ; chaque réponse renvoie l'id. Les événements asynchrones (changements de dépôt côté serveur) portent une clé "event" à la place.
| Op | Direction | Objectif |
|---|---|---|
ping, status, connect, disconnect | requête/réponse | cycle de vie de la session |
list_repos, open_repo, close_repo | requête/réponse | énumération des dépôts |
list_items, get_subfolders | requête/réponse | navigation dans les dépôts |
get_versions, get_checkouts | requête/réponse | état du contrôle de version |
checkout, terminate_checkout | requête/réponse | verrou d'écriture exclusif |
open_db | requête/réponse | lecture complète de la DB Ghidra → JSON (lourd) |
checkin | requête/réponse | applique les changements côté BN → nouvelle version du dépôt (lourd, via ProgramApplier) |
download_binary, upload_binary | requête/réponse | déplace le binaire original en entrée/sortie |
delete_item | requête/réponse | supprime un fichier du dépôt |
repo_changed | événement (async) | push RepositoryChangeEvent côté serveur |
Le dépôt contient tout le nécessaire pour reconstruire à partir de zéro. Configuration propre à chaque développeur qui n'est pas dans 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 vers une installation existante, soit exécutez ./build.sh qt (Windows : build.bat qt) une fois.binaryninja-api correspondant depuis 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 et --bn-api sont mutuellement exclusifs ; sans aucun des deux, le canal stable est utilisé. --channel interroge le GitHub Vector35/binaryninja-api (dernière release stable/*, ou la tête de branche dev) et nécessite donc un accès réseau. Si votre BN installé est en retard sur la dernière release, passez --bn-api avec le SHA exact issu du api_REVISION.txt de cette installation.Lors de l'ouverture d'une nouvelle session Claude Code, les meilleurs points d'entrée sont ce README plus l'état actuel sur 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 — chaque ligne de sujet indique ce qui a changé et pourquoiProgramApplier ignore les entrées de paramètres is_local car mapper les indices de registres BN vers le stockage Ghidra nécessite une traduction de table de registres par architecture. Les paramètres fonctionnent ; les locales ne se synchronisent pas encore.DatabaseExporter suppose un seul espace d'adressage RAM. Les espaces overlay ou les architectures Harvard peuvent produire des adresses incorrectes.DBChangeSet vide pour maintenir le fonctionnement des checkouts. La machinerie de fusion-au-checkout de Ghidra ne peut donc pas résoudre automatiquement les modifications concurrentes entre utilisateurs BN et Ghidra — le dernier écrivain gagne.