
Complemento de Binary Ninja que sincroniza el análisis de forma bidireccional con un repositorio de Ghidra Server, mediante un subproceso de puente Java.
Un plugin de Binary Ninja que se conecta a un repositorio de Ghidra Server e importa su análisis —símbolos, nombres de funciones y comentarios— directamente en una vista de binario abierta de Binary Ninja.
Ghidra y Binary Ninja tienen cada uno sus fortalezas. Este plugin te permite usar ambos sobre el mismo binario sin copiar manualmente nombres o comentarios entre ellos. Conéctate a un Ghidra Server en ejecución, explora sus repositorios y haz doble clic en cualquier archivo de proyecto para traer su análisis a la vista de BN abierta actualmente.
La sincronización va en ambas direcciones.
| BN ← Ghidra (importación) | BN → Ghidra (checkin) |
|---|
| Símbolos (etiquetas, nombres de funciones) | ✓ | ✓ |
| Comentarios (EOL/PRE/POST/PLATE/REP) | ✓ | ✓ |
| Firmas de funciones (tipo de retorno, convención de llamada) | ✓ | ✓ |
| Parámetros de funciones (renombrar, retipar, añadir) | ✓ | ✓ |
| Tipos de datos (struct/union/enum/typedef + puntero/array) | ✓ | ✓ |
| Ecuaciones (nombres de constantes + referencias) | ✓ | ✓ |
| Marcadores | ✓ | ✓ |
| Elementos de datos tipados | ✓ | ✓ |
| Flags de funciones (thunk, no-return, inline) | ✓ como etiquetas de BN | — |
| Variables locales (con reconocimiento de almacenamiento) | ✓ | parcial — el mapeo de almacenamiento en registros no está implementado |
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)
El plugin lanza un subproceso Java (el "bridge") al cargarse. El bridge mantiene la conexión RMI con el Ghidra Server y habla un protocolo JSON simple de vuelta al plugin a través de un socket TCP local. Esto mantiene todo el código Java/RMI fuera del proceso C++ y permite que la JVM arranque en segundo plano mientras BN termina de cargarse.
La JVM del bridge también inicializa el framework Application de Ghidra al arrancar, para que la ruta de escritura pueda usar las APIs de alto nivel del modelo de programa de Ghidra (ProgramDB, DataTypeManager, SymbolTable, FunctionManager) en lugar de escrituras directas con db.Table.putRecord() — consulta Checkin write path más abajo.
| Ruta | Lenguaje | Rol |
|---|---|---|
plugin/ | C++ / Qt6 | Plugin de barra lateral de Binary Ninja |
bridge/ | Java 17 | Cliente RMI de Ghidra + servidor puente JSON |
Plugin (C++):
plugin.cpp — registra los ajustes y el widget de barra lateral; inicia de forma anticipada la JVM del bridge al cargarGhidraConnection.cpp — singleton; gestiona el ciclo de vida del bridge y todas las operaciones respaldadas por RMIBridgeProcess.cpp — lanza el JAR del bridge como subproceso con tuberías stdout/stderr; lee la línea de handshake READY port=NBridgeClient.cpp — cliente TCP; envía peticiones JSON, recibe respuestas, despacha eventos asíncronosSyncEngine.cpp — aplica un GhidraDbExport a un BinaryView (símbolos, comentarios, flags)ui/ProjectPanel.cpp — widget de barra lateral: árbol de repositorios, diálogo de conexión, registro de actividadui/ConnectDialog.cpp — diálogo de host/puerto/usuario/contraseñaBridge (Java):
BridgeMain.java — análisis de argumentos; inicializa UniversalIdGenerator y el framework Application de Ghidra; inicia el servidor TCP; imprime READY port=N en stdoutBridgeServer.java — acepta una conexión de cliente TCP y le entrega una BridgeConnectionBridgeConnection.java — despachador de peticiones JSON; serializa las respuestas de la API de Ghidra a JSON; gestiona opCheckin (crea una nueva versión del programa en el servidor)GhidraSession.java — sesión RMI autenticada; envuelve RemoteRepositoryServerHandleEventStreamer.java — hilo en segundo plano por cada repositorio abierto; envía RepositoryChangeEvents al plugin como eventos JSON asíncronosDatabaseExporter.java — ruta de lectura: extrae las tablas de símbolos/comentarios/flags de funciones/tipos de datos/ecuaciones/marcadores desde un ManagedBufferFileHandle (el búfer de BD remoto de Ghidra) mediante acceso directo a db.jarProgramApplier.java — ruta de escritura: abre el archivo de búfer como un ProgramDB real y aplica todos los cambios del lado de BN a través de las APIs de alto nivel de Ghidra (consulta Checkin write path más abajo)DatabaseImporter.java — helpers de escritura directa heredados, conservados solo como punto de prueba; el apply(...) de producción delega en ProgramApplieropCheckin abre el archivo de búfer gestionado del programa en modo escritura, construye un ProgramDB sobre él y aplica los cambios del lado de BN a través de las APIs del modelo de programa de Ghidra. Se evitan las escrituras directas con db.Table.putRecord() — fueron la fuente de todos los bugs de corrupción en el checkin que hemos encontrado:
| Escritura en el nivel incorrecto | Modo de fallo |
|---|---|
setIntValue(col, longTypeId) en la tabla Function Data | IntField.setLongValue trunca silenciosamente con l2i → StackPurge se corrompe en cada actualización de firma |
setByteValue(col, isUnion) en V5V6 Composite Data Types | la columna es BooleanField en Ghidra 12.x → IllegalFieldAccessException ("Illegal field access") |
setIntValue(col, 0) en la columna V2 Typedef Flags | la columna es ShortField → el mismo fallo, esquema distinto |
| Escribir la cabecera de un compuesto sin las filas de configuración de componentes | CompositeEditorModel.cloneAllComponentSettings lanza ArrayIndexOutOfBoundsException cuando el struct se abre en Ghidra |
Escribir un símbolo PARAMETER con SYM_ADDR_COL = dirección RAM | Address is not a VariableAddress lanzado por FunctionDB.loadSymbolBasedVariables en cualquier acceso a la función |
Pasar null como DBChangeSet a DBHandle.save() | el servidor escribe un archivo de datos de cambios de 0 bytes → el siguiente checkout falla con EOFException en ProgramContentHandler.loadProgramChangeSet |
ProgramApplier no tiene estas trampas porque enruta a través de DataTypeManager.addDataType, SymbolTable.createLabel, Listing.setComment, Function.setReturnType, etc. — APIs que mantienen automáticamente las invariantes de las tablas entrelazadas de Ghidra. También ejecuta una pasada cleanupBadVariableSymbols al inicio de cada checkin para purgar la corrupción dejada en la base de datos por versiones anteriores del bridge.
server-package/CleanupBadVariableSymbols.java es un GhidraScript independiente que ejecuta la misma limpieza mediante analyzeHeadless — útil cuando un archivo está demasiado corrupto para abrirlo en la GUI 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á organizada en cinco niveles. Los niveles 2–4 existen para demostrar una propiedad: los mismos datos compatibles terminan almacenados tanto en el .bndb como en la base de datos del programa de Ghidra (la matriz de compatibilidad al inicio de este README).
| Nivel | Qué | Dónde | Condición |
|---|---|---|---|
| 0 | Pruebas unitarias puras | plugin/test/*.cpp (binja-ghidra-tests), bridge *Test.java | siempre |
| 1 | Round-trip de Ghidra-DB | bridge *RoundTripTest.java (ProgramApplier contra un ProgramDB real) | requiere ghidra.home / GHIDRA_HOME |
| 2 | Round-trip de BN BinaryView/.bndb | plugin/test/bn/ (binja-ghidra-bn-tests; binaryninjacore headless) | se OMITE limpiamente sin una licencia de BN con capacidad headless (se respeta la variable de entorno BN_LICENSE) |
| 3 | Paridad entre BD | CanonicalParityTest (C++ y Java) contra los goldens compartidos en testdata/parity/fixtures/ | con los niveles 1+2 |
| 4 | E2E con servidor en vivo | bridge LiveServerE2ETest — arranca un ghidraSvr real en un directorio temporal, siembra datos mediante analyzeHeadless, ejecuta checkout → export → checkin → re-export sobre RMI | test.bat --e2e (establece GHIDRA_E2E=1) |
Oráculo de paridad (nivel 3). Ambos lados verifican de forma independiente contra el mismo JSON canónico versionado (la forma del DatabaseExporter del bridge). Dirección de importación: el golden se carga en un ProgramDB (Java) y en un BinaryView mediante SyncEngine (C++), y cada re-exportación debe ser igual al golden. Dirección de checkin: las ediciones scriptadas de BN deben producir exactamente fixtures/checkin/*/expected-preview.json (C++), y aplicar ese preview mediante ProgramApplier debe re-exportarse como expected-after.json (Java). Si ambos lados coinciden con los goldens compartidos, las dos bases de datos coinciden por transitividad. Los modos de comparación de campos y la tabla de normalización de nombres de tipos están en testdata/parity/RULES.md; el binario de prueba es testdata/bin/parity_x64.bin (disposición en parity_x64.md).
Pines de regresión de larga data en el lado Java:
DataTypesRoundTripTest.struct_cloneSettings_doesNotThrow — la configuración de compuestos debe mantenerse consistente con la cabecera (fallo de cloneAllComponentSettings)FunctionSignaturesRoundTripTest.returnType_doesNotCorruptStackPurge — truncamiento de IntFieldParametersRoundTripTest.noParameterSymbol_endsUpAtRamAddress — invariante de VariableAddressLas pruebas de round-trip y paridad requieren una instalación de Ghidra (usada en tiempo de ejecución para los servicios de lenguaje). La ruta se lee de la propiedad de sistema de Gradle ghidra.home o de la variable de entorno GHIDRA_HOME; build.gradle pasa ghidraHome por defecto. Las pruebas C++ de nivel 2/3 además necesitan que binaryninjacore sea cargable (los scripts ponen el directorio de instalación de BN en el PATH).
| Dependencia | Notas |
|---|---|
| Binary Ninja (comercial) | Probado contra la versión que coincide con api_REVISION.txt en la instalación de BN |
| Ghidra Server | Probado con Ghidra 12.0.4. Debe estar en ejecución y ser accesible por RMI/SSL |
| JDK de Java 17+ | Se recomienda Eclipse Adoptium JDK 21 |
| CMake 3.24+ | |
| Ninja | |
| Compilador de C++ | MSVC 2022+ en Windows; clang en macOS; gcc/clang en Linux |
| Qt 6.7+ | Consulta Qt setup más abajo; qmake debe estar en el PATH en tiempo de compilación |
| Gradle (mediante wrapper) | El bridge usa el wrapper de Gradle — no se necesita instalación aparte |
| Poetry (solo para compilar Qt) | Requerido solo al compilar Qt desde el submódulo qt-build. Instálalo con pip install poetry o pipx install poetry. |
| libclang 19 (solo para compilar Qt) | Requerido por el sistema de compilación de Qt. Consulta qt-build/README.md para instrucciones de descarga. |
El plugin enlaza contra la misma compilación de Qt 6 que usa Binary Ninja. Tienes dos opciones:
Opción A — Usar una instalación de Qt existente (la más rápida si ya tienes Qt)
Pasa Qt6_DIR apuntando a tu directorio CMake de Qt:
Qt6_DIR=/path/to/Qt/6.x.y/clang_64/lib/cmake/Qt6 ./build.sh
En macOS el script de compilación autodetecta Qt si fue instalado por el instalador en línea de Qt bajo /usr/local/Qt*.
Opción B — Compilar Qt desde el submódulo qt-build (~1-2 horas, una vez por máquina)
El submódulo qt-build (los scripts de compilación de Qt de Vector35) compila Qt 6 con los parches de Binary Ninja. Requiere Poetry y libclang 19 (consulta Requisitos previos arriba y qt-build/README.md).
Qt se instala en qt/<version>/<compiler>/ dentro del repositorio:
| Plataforma | Ruta de instalación |
|---|---|
| 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
El paso qt solo es necesario una vez. CMake y los scripts de compilación detectan el Qt compilado en qt/ en cada ejecución posterior y omiten el submódulo por completo. El directorio qt/ está en 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)
Luego sigue la configuración de Qt de arriba (Opción A o B) y ejecuta:
./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 de entorno (todas opcionales — el script establece valores predeterminados razonables):
BN_INSTALL=/Applications/Binary\ Ninja.app/Contents/MacOS
Qt6_DIR=/usr/local/Qt-6.7.2/lib/cmake/Qt6
Edita las rutas al inicio de build.bat para que coincidan con tu entorno antes del primer uso:
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 compilación de C++ usa CMake FetchContent para clonar binaryninja-api en el commit exacto registrado en api_REVISION.txt, de modo que la ABI del plugin siempre coincide con la versión de BN instalada. Ghidra se descarga automáticamente por CMake en la primera configuración si GHIDRA_HOME no está establecido.
Tras la instalación, establece estos valores en los ajustes de Binary Ninja (Edit → Preferences → Settings, busca "Ghidra"):
| Ajuste | Descripción |
|---|---|
ghidra.javaExe | Ruta completa a java.exe |
ghidra.ghidraHome | Raíz de tu instalación de Ghidra (contiene Ghidra/Framework/…) |
ghidra.trustAllCerts | Establece true si tu Ghidra Server usa un certificado autofirmado |
ghidra.defaultHost | Prellena el diálogo de conexión |
ghidra.defaultPort | Predeterminado: 13100 |
ghidra.defaultUser | Prellena el diálogo de conexión |
Requisito previo para el paso 5: el archivo del programa debe estar confirmado en el repositorio del Ghidra Server (no solo abierto localmente en Ghidra). En Ghidra: haz clic derecho en el archivo en la ventana Project → Version Control → Add to Version Control….
El plugin y el bridge se comunican a través de un socket TCP local usando JSON delimitado por saltos de línea. Cada petición lleva un id entero y un op de tipo string; cada respuesta repite el id. Los eventos asíncronos (cambios en el repositorio del lado del servidor) llevan en su lugar una clave "event".
| Op | Dirección | Propósito |
|---|---|---|
ping, status, connect, disconnect | petición/respuesta | ciclo de vida de la sesión |
list_repos, open_repo, close_repo | petición/respuesta | enumeración de repositorios |
list_items, get_subfolders | petición/respuesta | navegación por el repositorio |
get_versions, get_checkouts | petición/respuesta | estado del control de versiones |
checkout, terminate_checkout | petición/respuesta | bloqueo de escritura exclusivo |
open_db | petición/respuesta | leer la BD completa de Ghidra → JSON (pesado) |
checkin | petición/respuesta | aplicar cambios del lado de BN → nueva versión en el repositorio (pesado, mediante ProgramApplier) |
download_binary, upload_binary | petición/respuesta | mover el binario original hacia/desde |
delete_item | petición/respuesta | eliminar un archivo del repositorio |
repo_changed | evento (asíncrono) | push de RepositoryChangeEvent del lado del servidor |
El repositorio contiene todo lo necesario para recompilar desde cero. Configuración por desarrollador que no está en 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 una instalación existente o ejecuta ./build.sh qt (Windows: build.bat qt) una vez.binaryninja-api desde 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 y --bn-api son mutuamente excluyentes; sin ninguno de los dos, se usa el canal stable. --channel consulta el GitHub de Vector35/binaryninja-api (última release stable/*, o la cabeza de la rama dev), por lo que necesita acceso a la red. Si tu BN instalada va por detrás de la última release, pasa --bn-api con el SHA exacto del api_REVISION.txt de esa instalación.Al abrir una sesión nueva de Claude Code, los mejores puntos de incorporación son este README más el estado actual en 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 — cada línea de asunto dice qué cambió y por quéProgramApplier omite las entradas de parámetros is_local porque mapear los índices de registro de BN al almacenamiento de Ghidra requiere una traducción de tabla de registros por arquitectura. Los parámetros funcionan; las locales aún no se sincronizan.DatabaseExporter asume un único espacio de direcciones RAM. Los espacios overlay o las arquitecturas Harvard pueden producir direcciones incorrectas.DBChangeSet vacío para mantener los checkouts funcionando. Por lo tanto, la maquinaria de fusión al hacer checkout de Ghidra no puede resolver automáticamente ediciones concurrentes entre usuarios de BN y de Ghidra — gana el último que escribe.