
Plugin do Binary Ninja que sincroniza a análise bidirecionalmente com um repositório do Ghidra Server, por meio de um subprocesso de ponte Java.
Um plugin do Binary Ninja que se conecta a um repositório do Ghidra Server e importa sua análise — símbolos, nomes de funções e comentários — diretamente para uma binary view aberta no Binary Ninja.
Ghidra e Binary Ninja têm pontos fortes distintos. Este plugin permite usar ambos no mesmo binário sem copiar nomes ou comentários manualmente entre eles. Conecte-se a um Ghidra Server em execução, navegue por seus repositórios e dê um duplo clique em qualquer arquivo de projeto para trazer sua análise para a view do BN atualmente aberta.
A sincronização é bidirecional.
| BN ← Ghidra (importação) | BN → Ghidra (checkin) |
|---|
| Símbolos (rótulos, nomes de funções) | ✓ | ✓ |
| Comentários (EOL/PRE/POST/PLATE/REP) | ✓ | ✓ |
| Assinaturas de funções (tipo de retorno, convenção de chamada) | ✓ | ✓ |
| Parâmetros de funções (renomear, retipar, adicionar) | ✓ | ✓ |
| Tipos de dados (struct/union/enum/typedef + ponteiro/array) | ✓ | ✓ |
| Equates (nomes de constantes + referências) | ✓ | ✓ |
| Bookmarks | ✓ | ✓ |
| Itens de dados tipados | ✓ | ✓ |
| Flags de funções (thunk, no-return, inline) | ✓ como tags do BN | — |
| Variáveis locais (cientes de storage) | ✓ | parcial — mapeamento de register-storage não 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)
O plugin inicia um subprocesso Java (a "bridge") ao carregar. A bridge mantém a conexão RMI com o Ghidra Server e fala um protocolo JSON simples de volta ao plugin através de um socket TCP local. Isso mantém todo o código Java/RMI fora do processo C++ e permite que a JVM inicie em segundo plano enquanto o BN termina de carregar.
A JVM da bridge também inicializa o framework Application do Ghidra na inicialização, para que o caminho de escrita possa usar as APIs de alto nível do modelo de programa do Ghidra (ProgramDB, DataTypeManager, SymbolTable, FunctionManager) em vez de escritas cruas via db.Table.putRecord() — veja Checkin write path abaixo.
| Caminho | Linguagem | Papel |
|---|---|---|
plugin/ | C++ / Qt6 | Plugin de sidebar do Binary Ninja |
bridge/ | Java 17 | Cliente RMI do Ghidra + servidor bridge JSON |
Plugin (C++):
plugin.cpp — registra configurações e o widget da sidebar; inicia a JVM da bridge antecipadamente ao carregarGhidraConnection.cpp — singleton; gerencia o ciclo de vida da bridge e todas as operações baseadas em RMIBridgeProcess.cpp — lança o JAR da bridge como subprocesso com pipes de stdout/stderr; lê a linha de handshake READY port=NBridgeClient.cpp — cliente TCP; envia requisições JSON, recebe respostas, despacha eventos assíncronosSyncEngine.cpp — aplica um GhidraDbExport a uma BinaryView (símbolos, comentários, flags)ui/ProjectPanel.cpp — widget da sidebar: árvore de repositórios, diálogo de conexão, log de atividadesui/ConnectDialog.cpp — diálogo de host/porta/usuário/senhaBridge (Java):
BridgeMain.java — parsing de argumentos; inicializa UniversalIdGenerator e o framework Application do Ghidra; inicia o servidor TCP; imprime READY port=N em stdoutBridgeServer.java — aceita uma conexão de cliente TCP e entrega a ela uma BridgeConnectionBridgeConnection.java — despachante de requisições JSON; serializa respostas da API do Ghidra para JSON; trata opCheckin (cria uma nova versão do programa no servidor)GhidraSession.java — sessão RMI autenticada; encapsula RemoteRepositoryServerHandleEventStreamer.java — thread em segundo plano por repositório aberto; envia RepositoryChangeEvents ao plugin como eventos JSON assíncronosDatabaseExporter.java — caminho de leitura: extrai tabelas de símbolos/comentários/flags de funções/tipos de dados/equates/bookmarks de um ManagedBufferFileHandle (buffer de DB remoto do Ghidra) via acesso cru ao db.jarProgramApplier.java — caminho de escrita: abre o arquivo de buffer como um ProgramDB real e aplica todas as mudanças do lado do BN através das APIs de alto nível do Ghidra (veja Checkin write path abaixo)DatabaseImporter.java — helpers legados de escrita crua mantidos apenas como ponto de teste; o apply(...) de produção delega para ProgramApplieropCheckin abre o arquivo de buffer gerenciado do programa em modo de escrita, constrói um ProgramDB sobre ele e aplica as mudanças do lado do BN através das APIs de modelo de programa do Ghidra. Escritas cruas via db.Table.putRecord() são evitadas — elas foram a fonte de todos os bugs de corrupção de checkin que já encontramos:
| Escrita em camada errada | Modo de falha |
|---|---|
setIntValue(col, longTypeId) na tabela Function Data | IntField.setLongValue trunca silenciosamente com l2i → StackPurge corrompido em toda atualização de assinatura |
setByteValue(col, isUnion) em V5V6 Composite Data Types | a coluna é BooleanField no Ghidra 12.x → IllegalFieldAccessException ("Illegal field access") |
setIntValue(col, 0) na coluna V2 Typedef Flags | a coluna é ShortField → mesmo crash, schema diferente |
| Escrever cabeçalho de composite sem linhas de component-settings | CompositeEditorModel.cloneAllComponentSettings lança ArrayIndexOutOfBoundsException quando a struct é aberta no Ghidra |
Escrever símbolo PARAMETER com SYM_ADDR_COL = endereço RAM | Address is not a VariableAddress lançado por FunctionDB.loadSymbolBasedVariables em qualquer acesso à função |
Passar DBChangeSet null para DBHandle.save() | o servidor grava um arquivo de change-data de 0 bytes → o próximo checkout falha com EOFException em ProgramContentHandler.loadProgramChangeSet |
ProgramApplier não tem essas armadilhas porque roteia através de DataTypeManager.addDataType, SymbolTable.createLabel, Listing.setComment, Function.setReturnType, etc. — APIs que mantêm automaticamente as invariantes das tabelas interligadas do Ghidra. Ele também executa uma passagem cleanupBadVariableSymbols no início de cada checkin para limpar corrupções deixadas no banco de dados por versões mais antigas da bridge.
server-package/CleanupBadVariableSymbols.java é um GhidraScript autônomo que executa a mesma limpeza via analyzeHeadless — útil quando um arquivo está corrompido demais para abrir na GUI do 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)
A suíte está organizada em cinco tiers. Os tiers 2–4 existem para provar uma propriedade: os mesmos dados compatíveis acabam armazenados tanto no .bndb quanto no banco de dados de programa do Ghidra (a matriz de compatibilidade no topo deste README).
| Tier | O quê | Onde | Gate |
|---|---|---|---|
| 0 | Testes unitários puros | plugin/test/*.cpp (binja-ghidra-tests), bridge *Test.java | sempre |
| 1 | Round-trip do Ghidra-DB | bridge *RoundTripTest.java (ProgramApplier contra um ProgramDB real) | requer ghidra.home / GHIDRA_HOME |
| 2 | Round-trip de BN BinaryView/.bndb | plugin/test/bn/ (binja-ghidra-bn-tests; binaryninjacore headless) | SKIPa de forma limpa sem uma licença BN compatível com headless (env BN_LICENSE respeitada) |
| 3 | Paridade cross-DB | CanonicalParityTest (C++ e Java) contra os goldens compartilhados em testdata/parity/fixtures/ | com tiers 1+2 |
| 4 | E2E com servidor ao vivo | bridge LiveServerE2ETest — inicializa um ghidraSvr real em um diretório temporário, semeia via analyzeHeadless, conduz checkout → export → checkin → re-export via RMI | test.bat --e2e (define GHIDRA_E2E=1) |
Oráculo de paridade (tier 3). Ambos os lados verificam independentemente contra o mesmo JSON canônico versionado (o formato do DatabaseExporter da bridge). Direção de importação: o golden carrega em um ProgramDB (Java) e em uma BinaryView via SyncEngine (C++), e cada re-export deve ser igual ao golden. Direção de checkin: edições roteirizadas no BN devem produzir exatamente fixtures/checkin/*/expected-preview.json (C++), e aplicar esse preview via ProgramApplier deve re-exportar como expected-after.json (Java). Se ambos os lados correspondem aos goldens compartilhados, os dois bancos de dados concordam por transitividade. Os modos de comparação de campos e a tabela de normalização de nomes de tipos estão em testdata/parity/RULES.md; o binário de teste é testdata/bin/parity_x64.bin (layout em parity_x64.md).
Pins de regressão de longa data no lado Java:
DataTypesRoundTripTest.struct_cloneSettings_doesNotThrow — as configurações de composite devem permanecer consistentes com o cabeçalho (crash do cloneAllComponentSettings)FunctionSignaturesRoundTripTest.returnType_doesNotCorruptStackPurge — truncamento de IntFieldParametersRoundTripTest.noParameterSymbol_endsUpAtRamAddress — invariante de VariableAddressTestes de round-trip e paridade requerem uma instalação do Ghidra (usada em tempo de execução para serviços de linguagem). O caminho é lido da propriedade de sistema Gradle ghidra.home ou da variável de ambiente GHIDRA_HOME; build.gradle repassa ghidraHome por padrão. Testes C++ dos tiers 2/3 também precisam que binaryninjacore seja carregável (os scripts colocam o diretório de instalação do BN no PATH).
| Dependência | Notas |
|---|---|
| Binary Ninja (comercial) | Testado contra a versão correspondente ao api_REVISION.txt na instalação do BN |
| Ghidra Server | Testado com Ghidra 12.0.4. Deve estar em execução e acessível via RMI/SSL |
| Java 17+ JDK | Eclipse Adoptium JDK 21 recomendado |
| CMake 3.24+ | |
| Ninja | |
| Compilador C++ | MSVC 2022+ no Windows; clang no macOS; gcc/clang no Linux |
| Qt 6.7+ | Veja Qt setup abaixo; qmake deve estar no PATH em tempo de build |
| Gradle (via wrapper) | A bridge usa o Gradle wrapper — nenhuma instalação separada necessária |
| Poetry (somente build do Qt) | Necessário apenas ao compilar o Qt a partir do submódulo qt-build. Instale com pip install poetry ou pipx install poetry. |
| libclang 19 (somente build do Qt) | Requerido pelo sistema de build do Qt. Veja qt-build/README.md para instruções de download. |
O plugin faz link contra a mesma build do Qt 6 que o Binary Ninja usa. Você tem duas opções:
Opção A — Usar uma instalação existente do Qt (mais rápido se você já tem o Qt)
Passe Qt6_DIR apontando para o diretório CMake do seu Qt:
Qt6_DIR=/path/to/Qt/6.x.y/clang_64/lib/cmake/Qt6 ./build.sh
No macOS o script de build detecta automaticamente o Qt se ele foi instalado pelo instalador online do Qt em /usr/local/Qt*.
Opção B — Compilar o Qt a partir do submódulo qt-build (~1-2 horas, uma vez por máquina)
O submódulo qt-build (scripts de build do Qt da Vector35) compila o Qt 6 com os patches do Binary Ninja. Ele requer Poetry e libclang 19 (veja Pré-requisitos acima e qt-build/README.md).
O Qt é instalado em qt/<version>/<compiler>/ dentro do repositório:
| Plataforma | Caminho de instalação |
|---|---|
| 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
O passo qt só é necessário uma vez. O CMake e os scripts de build detectam o Qt compilado em qt/ em toda execução subsequente e pulam o submódulo inteiramente. O diretório qt/ está no 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)
Depois siga a configuração do Qt acima (Opção A ou B) e execute:
./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
Variáveis de ambiente (todas opcionais — o script define padrões sensatos):
BN_INSTALL=/Applications/Binary\ Ninja.app/Contents/MacOS
Qt6_DIR=/usr/local/Qt-6.7.2/lib/cmake/Qt6
Edite os caminhos no topo de build.bat para corresponder ao seu ambiente antes do primeiro 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
A build C++ usa CMake FetchContent para clonar binaryninja-api no commit exato registrado em api_REVISION.txt, então a ABI do plugin sempre corresponde à versão do BN instalada. O Ghidra é baixado automaticamente pelo CMake na primeira configuração se GHIDRA_HOME não estiver definido.
Após instalar, defina estas opções nas configurações do Binary Ninja (Edit → Preferences → Settings, pesquise por "Ghidra"):
| Configuração | Descrição |
|---|---|
ghidra.javaExe | Caminho completo para java.exe |
ghidra.ghidraHome | Raiz da sua instalação do Ghidra (contém Ghidra/Framework/…) |
ghidra.trustAllCerts | Defina true se seu Ghidra Server usa um certificado autoassinado |
ghidra.defaultHost | Pré-preenche o diálogo de conexão |
ghidra.defaultPort | Padrão: 13100 |
ghidra.defaultUser | Pré-preenche o diálogo de conexão |
Pré-requisito para o passo 5: o arquivo de programa deve estar commitado no repositório do Ghidra Server (não apenas aberto localmente no Ghidra). No Ghidra: clique com o botão direito no arquivo na janela Project → Version Control → Add to Version Control….
O plugin e a bridge se comunicam através de um socket TCP local usando JSON delimitado por quebras de linha. Toda requisição carrega um id inteiro e uma string op; toda resposta ecoa o id. Eventos assíncronos (mudanças de repositório do lado do servidor) carregam uma chave "event" em vez disso.
| Op | Direção | Propósito |
|---|---|---|
ping, status, connect, disconnect | requisição/resposta | ciclo de vida da sessão |
list_repos, open_repo, close_repo | requisição/resposta | enumeração de repositórios |
list_items, get_subfolders | requisição/resposta | navegação no repositório |
get_versions, get_checkouts | requisição/resposta | estado do controle de versão |
checkout, terminate_checkout | requisição/resposta | lock de escrita exclusivo |
open_db | requisição/resposta | lê o DB completo do Ghidra → JSON (pesado) |
checkin | requisição/resposta | aplica mudanças do lado do BN → nova versão no repositório (pesado, via ProgramApplier) |
download_binary, upload_binary | requisição/resposta | move o binário original para dentro/fora |
delete_item | requisição/resposta | remove arquivo do repositório |
repo_changed | evento (assíncrono) | push de RepositoryChangeEvent do lado do servidor |
O repositório contém tudo o que é necessário para recompilar do zero. Configuração por desenvolvedor que não está no 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 para uma instalação existente ou execute ./build.sh qt (Windows: build.bat qt) uma vez.binaryninja-api no 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 são mutuamente exclusivos; sem nenhum dos dois, o canal stable é usado. --channel consulta o GitHub do Vector35/binaryninja-api (último release stable/*, ou o head da branch dev), então precisa de acesso à rede. Se o seu BN instalado está atrás do último release, passe --bn-api com o SHA exato do api_REVISION.txt daquela instalação.Ao abrir uma sessão nova do Claude Code, os melhores pontos de onboarding são este README mais o estado atual na branch 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 linha de assunto diz o que mudou e por quêProgramApplier pula entradas de parâmetros is_local porque mapear índices de registradores do BN para storage do Ghidra requer uma tradução de tabela de registradores por arquitetura. Parâmetros funcionam; locais ainda não sincronizam.DatabaseExporter assume um único espaço de endereçamento RAM. Espaços overlay ou arquiteturas Harvard podem produzir endereços incorretos.DBChangeSet vazio para manter os checkouts funcionando. Portanto, a maquinaria de merge-on-checkout do Ghidra não consegue resolver automaticamente edições concorrentes entre usuários do BN e do Ghidra — o último a escrever vence.