
Плагин Binary Ninja, который двунаправленно синхронизирует анализ с репозиторием Ghidra Server через подпроцесс Java-моста.
Плагин для Binary Ninja, который подключается к репозиторию Ghidra Server и импортирует его результаты анализа — символы, имена функций и комментарии — напрямую в открытое представление бинарного файла Binary Ninja.
У Ghidra и Binary Ninja есть свои сильные стороны. Этот плагин позволяет использовать обе программы для одного и того же бинарного файла без ручного копирования имён или комментариев между ними. Подключитесь к запущенному Ghidra Server, просмотрите его репозитории и дважды щёлкните любой файл проекта, чтобы перенести его анализ в текущее открытое представление BN.
Синхронизация работает в обоих направлениях.
| BN ← Ghidra (импорт) | BN → Ghidra (checkin) |
|---|
| Символы (метки, имена функций) | ✓ | ✓ |
| Комментарии (EOL/PRE/POST/PLATE/REP) | ✓ | ✓ |
| Сигнатуры функций (тип возврата, соглашение о вызовах) | ✓ | ✓ |
| Параметры функций (переименование, смена типа, добавление) | ✓ | ✓ |
| Типы данных (struct/union/enum/typedef + указатель/массив) | ✓ | ✓ |
| Приравнивания (имена констант + ссылки) | ✓ | ✓ |
| Закладки | ✓ | ✓ |
| Типизированные элементы данных | ✓ | ✓ |
| Флаги функций (thunk, no-return, inline) | ✓ как теги BN | — |
| Локальные переменные (с учётом хранилища) | ✓ | частично — сопоставление регистрового хранилища не реализовано |
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)
Плагин при загрузке запускает подпроцесс Java (далее «мост»). Мост поддерживает RMI-соединение с Ghidra Server и общается с плагином по простому JSON-протоколу через локальный TCP-сокет. Это позволяет держать весь Java/RMI-код вне процесса C++ и даёт JVM запускаться в фоне, пока BN завершает загрузку.
JVM моста также инициализирует фреймворк Application из Ghidra при запуске, чтобы путь записи мог использовать высокоуровневые API программной модели Ghidra (ProgramDB, DataTypeManager, SymbolTable, FunctionManager), а не низкоуровневые записи db.Table.putRecord() — см. Путь записи при checkin ниже.
| Путь | Язык | Роль |
|---|---|---|
plugin/ | C++ / Qt6 | Плагин боковой панели Binary Ninja |
bridge/ | Java 17 | RMI-клиент Ghidra + JSON-сервер моста |
Плагин (C++):
plugin.cpp — регистрирует настройки и виджет боковой панели; сразу запускает JVM моста при загрузкеGhidraConnection.cpp — синглтон; управляет жизненным циклом моста и всеми операциями на базе RMIBridgeProcess.cpp — запускает JAR моста как подпроцесс с каналами stdout/stderr; читает строку рукопожатия READY port=NBridgeClient.cpp — TCP-клиент; отправляет JSON-запросы, принимает ответы, диспетчеризует асинхронные событияSyncEngine.cpp — применяет GhidraDbExport к BinaryView (символы, комментарии, флаги)ui/ProjectPanel.cpp — виджет боковой панели: дерево репозиториев, диалог подключения, журнал активностиui/ConnectDialog.cpp — диалог host/port/user/passwordМост (Java):
BridgeMain.java — разбор аргументов; инициализирует UniversalIdGenerator и фреймворк Application из Ghidra; запускает TCP-сервер; печатает READY port=N в stdoutBridgeServer.java — принимает одно TCP-соединение клиента и передаёт его в BridgeConnectionBridgeConnection.java — диспетчер JSON-запросов; сериализует ответы API Ghidra в JSON; обрабатывает opCheckin (создаёт новую версию программы на сервере)GhidraSession.java — аутентифицированная RMI-сессия; оборачивает RemoteRepositoryServerHandleEventStreamer.java — фоновый поток на каждый открытый репозиторий; отправляет RepositoryChangeEvent в плагин как асинхронные JSON-событияDatabaseExporter.java — путь чтения: извлекает таблицы символов/комментариев/флагов функций/типов данных/приравниваний/закладок из ManagedBufferFileHandle (буфер удалённой БД Ghidra) через прямой доступ к db.jarProgramApplier.java — путь записи: открывает файл буфера как настоящий ProgramDB и применяет все изменения со стороны BN через высокоуровневые API Ghidra (см. Путь записи при checkin ниже)DatabaseImporter.java — устаревшие низкоуровневые помощники записи, оставленные только как тестовый шов; производственный apply(...) делегирует в ProgramApplieropCheckin открывает управляемый буферный файл программы в режиме записи, конструирует поверх него ProgramDB и применяет изменения со стороны BN через API программной модели Ghidra. Низкоуровневые записи db.Table.putRecord() избегаются — они были источником всех багов повреждения при checkin, с которыми мы когда-либо сталкивались:
| Запись не того уровня | Режим отказа |
|---|---|
setIntValue(col, longTypeId) в таблице Function Data | IntField.setLongValue молча усекает через l2i → StackPurge повреждается при каждом обновлении сигнатуры |
setByteValue(col, isUnion) в V5V6 Composite Data Types | столбец является BooleanField в Ghidra 12.x → IllegalFieldAccessException ("Illegal field access") |
setIntValue(col, 0) в столбце V2 Typedef Flags | столбец является ShortField → тот же сбой, другая схема |
| Запись заголовка composite без строк настроек компонентов | CompositeEditorModel.cloneAllComponentSettings выбрасывает ArrayIndexOutOfBoundsException при открытии struct в Ghidra |
Запись символа PARAMETER с SYM_ADDR_COL = RAM-адрес | Address is not a VariableAddress выбрасывается FunctionDB.loadSymbolBasedVariables при любом доступе к функции |
Передача null DBChangeSet в DBHandle.save() | сервер записывает файл change-data размером 0 байт → следующий checkout падает с EOFException в ProgramContentHandler.loadProgramChangeSet |
У ProgramApplier этих ловушек нет, потому что он маршрутизирует через DataTypeManager.addDataType, SymbolTable.createLabel, Listing.setComment, Function.setReturnType и т. д. — API, которые автоматически поддерживают инварианты взаимосвязанных таблиц Ghidra. Он также запускает проход cleanupBadVariableSymbols в начале каждого checkin, чтобы вычистить повреждения, оставленные в базе старыми версиями моста.
server-package/CleanupBadVariableSymbols.java — это автономный GhidraScript, выполняющий ту же очистку через analyzeHeadless — полезно, когда файл слишком повреждён, чтобы открыться в GUI 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)
Набор организован в пять уровней. Уровни 2–4 существуют, чтобы доказать одно свойство: одни и те же совместимые данные оказываются сохранёнными как в .bndb, так и в базе программы Ghidra (матрица совместимости в начале этого README).
| Уровень | Что | Где | Условие |
|---|---|---|---|
| 0 | Чистые модульные тесты | plugin/test/*.cpp (binja-ghidra-tests), мост *Test.java | всегда |
| 1 | Round-trip БД Ghidra | мост *RoundTripTest.java (ProgramApplier против настоящего ProgramDB) | требует ghidra.home / GHIDRA_HOME |
| 2 | Round-trip BN BinaryView/.bndb | plugin/test/bn/ (binja-ghidra-bn-tests; headless binaryninjacore) | чисто пропускается без headless-совместимой лицензии BN (учитывается env BN_LICENSE) |
| 3 | Кросс-БД паритет | CanonicalParityTest (C++ и Java) против общих эталонов в testdata/parity/fixtures/ | вместе с уровнями 1+2 |
| 4 | E2E с живым сервером | мост LiveServerE2ETest — поднимает настоящий ghidraSvr во временном каталоге, наполняет через analyzeHeadless, прогоняет checkout → export → checkin → re-export по RMI | test.bat --e2e (устанавливает GHIDRA_E2E=1) |
Оракул паритета (уровень 3). Обе стороны независимо проверяются против одного и
того же зафиксированного в репозитории канонического JSON (форма моста DatabaseExporter). Направление импорта: эталон загружается в ProgramDB (Java) и в BinaryView
через SyncEngine (C++), и каждый повторный экспорт должен совпасть с эталоном. Направление checkin: скриптовые правки BN должны давать ровно
fixtures/checkin/*/expected-preview.json (C++), а применение этого превью через
ProgramApplier должно повторно экспортироваться как expected-after.json (Java). Если обе стороны совпадают с общими эталонами, то две базы согласуются по транзитивности. Режимы сравнения полей и таблица нормализации имён типов находятся в
testdata/parity/RULES.md; тестовый бинарник —
testdata/bin/parity_x64.bin (раскладка в parity_x64.md).
Давние регрессионные закрепления на стороне Java:
DataTypesRoundTripTest.struct_cloneSettings_doesNotThrow — настройки composite должны оставаться согласованными с заголовком (сбой cloneAllComponentSettings)FunctionSignaturesRoundTripTest.returnType_doesNotCorruptStackPurge — усечение IntFieldParametersRoundTripTest.noParameterSymbol_endsUpAtRamAddress — инвариант VariableAddressТесты round-trip и паритета требуют установки Ghidra (используется во время выполнения для
языковых сервисов). Путь читается из системного свойства Gradle ghidra.home или env-переменной GHIDRA_HOME; build.gradle по умолчанию передаёт ghidraHome
насквозь. Тестам C++ уровня 2/3 дополнительно нужен загружаемый binaryninjacore
(скрипты добавляют каталог установки BN в PATH).
| Зависимость | Примечания |
|---|---|
| Binary Ninja (коммерческая) | Протестировано против версии, соответствующей api_REVISION.txt в установке BN |
| Ghidra Server | Протестировано с Ghidra 12.0.4. Должен быть запущен и доступен по RMI/SSL |
| Java 17+ JDK | Рекомендуется Eclipse Adoptium JDK 21 |
| CMake 3.24+ | |
| Ninja | |
| Компилятор C++ | MSVC 2022+ на Windows; clang на macOS; gcc/clang на Linux |
| Qt 6.7+ | См. Настройка Qt ниже; qmake должен быть в PATH во время сборки |
| Gradle (через wrapper) | Мост использует Gradle wrapper — отдельная установка не нужна |
| Poetry (только для сборки Qt) | Требуется только при сборке Qt из подмодуля qt-build. Установите через pip install poetry или pipx install poetry. |
| libclang 19 (только для сборки Qt) | Требуется системой сборки Qt. Инструкции по загрузке см. в qt-build/README.md. |
Плагин линкуется против той же сборки Qt 6, которую использует Binary Ninja. У вас есть два варианта:
Вариант A — Использовать существующую установку Qt (быстрее всего, если Qt уже есть)
Передайте Qt6_DIR, указывающий на ваш каталог CMake Qt:
Qt6_DIR=/path/to/Qt/6.x.y/clang_64/lib/cmake/Qt6 ./build.sh
На macOS скрипт сборки автоматически определяет Qt, если он был установлен онлайн-установщиком Qt в /usr/local/Qt*.
Вариант B — Собрать Qt из подмодуля qt-build (~1-2 часа, один раз на машину)
Подмодуль qt-build (скрипты сборки Qt от Vector35) компилирует Qt 6 с патчами Binary Ninja. Требует Poetry и libclang 19 (см. Предварительные требования выше и qt-build/README.md).
Qt устанавливается в qt/<version>/<compiler>/ внутри репозитория:
| Платформа | Путь установки |
|---|---|
| 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
Шаг qt нужен только один раз. CMake и скрипты сборки обнаруживают собранный Qt в qt/ при каждом последующем запуске и полностью пропускают подмодуль. Каталог qt/ игнорируется 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)
Затем выполните настройку Qt выше (Вариант A или B) и запустите:
./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
Переменные окружения (все необязательные — скрипт задаёт разумные значения по умолчанию):
BN_INSTALL=/Applications/Binary\ Ninja.app/Contents/MacOS
Qt6_DIR=/usr/local/Qt-6.7.2/lib/cmake/Qt6
Отредактируйте пути в начале build.bat в соответствии с вашим окружением перед первым использованием:
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
Сборка C++ использует CMake FetchContent для клонирования binaryninja-api на точном коммите, записанном в api_REVISION.txt, поэтому ABI плагина всегда соответствует установленной версии BN. Ghidra автоматически загружается CMake при первой конфигурации, если GHIDRA_HOME не задан.
После установки задайте следующие параметры в настройках Binary Ninja (Edit → Preferences → Settings, поиск "Ghidra"):
| Настройка | Описание |
|---|---|
ghidra.javaExe | Полный путь к java.exe |
ghidra.ghidraHome | Корень вашей установки Ghidra (содержит Ghidra/Framework/…) |
ghidra.trustAllCerts | Установите true, если ваш Ghidra Server использует самоподписанный сертификат |
ghidra.defaultHost | Предзаполняет диалог подключения |
ghidra.defaultPort | По умолчанию: 13100 |
ghidra.defaultUser | Предзаполняет диалог подключения |
Предварительное условие для шага 5: файл программы должен быть зафиксирован в репозитории Ghidra Server (а не просто открыт локально в Ghidra). В Ghidra: щёлкните правой кнопкой файл в окне Project → Version Control → Add to Version Control….
Плагин и мост общаются через локальный TCP-сокет, используя JSON с разделением по строкам. Каждый запрос несёт целочисленный id и строковый op; каждый ответ повторяет id. Асинхронные события (изменения репозитория на стороне сервера) вместо этого несут ключ "event".
| Op | Направление | Назначение |
|---|---|---|
ping, status, connect, disconnect | запрос/ответ | жизненный цикл сессии |
list_repos, open_repo, close_repo | запрос/ответ | перечисление репозиториев |
list_items, get_subfolders | запрос/ответ | просмотр репозитория |
get_versions, get_checkouts | запрос/ответ | состояние контроля версий |
checkout, terminate_checkout | запрос/ответ | эксклюзивная блокировка записи |
open_db | запрос/ответ | чтение полной БД Ghidra → JSON (тяжёлая) |
checkin | запрос/ответ | применение изменений со стороны BN → новая версия репозитория (тяжёлая, через ProgramApplier) |
download_binary, upload_binary | запрос/ответ | перемещение исходного бинарного файла внутрь/наружу |
delete_item | запрос/ответ | удаление файла из репозитория |
repo_changed | событие (асинхронное) | push RepositoryChangeEvent со стороны сервера |
Репозиторий содержит всё необходимое для пересборки с нуля. Настройка для конкретного разработчика, которой нет в 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 на существующую установку, либо один раз запустите ./build.sh qt (Windows: build.bat qt).binaryninja-api с 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 и --bn-api взаимоисключающие; без обоих используется канал stable. --channel обращается к GitHub Vector35/binaryninja-api (последний релиз stable/* или голова ветки dev), поэтому требуется доступ к сети. Если ваша установленная BN отстаёт от последнего релиза, передайте --bn-api с точным SHA из api_REVISION.txt этой установки.При открытии свежей сессии Claude Code лучшими ориентирами для онбординга являются этот README плюс текущее состояние на 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 — каждая строка темы говорит, что изменилось и почемуProgramApplier пропускает записи параметров is_local, потому что сопоставление индексов регистров BN с хранилищем Ghidra требует трансляции таблицы регистров для каждой архитектуры. Параметры работают; локальные переменные пока не синхронизируются.DatabaseExporter предполагает единое RAM-адресное пространство. Overlay-пространства или гарвардские архитектуры могут давать некорректные адреса.DBChangeSet, чтобы checkouts продолжали работать. Поэтому механизм слияния при checkout в Ghidra не может автоматически разрешать параллельные правки между пользователями BN и Ghidra — побеждает последний записавший.