一个 Binary Ninja 插件,可连接到 Ghidra Server 仓库,并将其分析结果——符号、函数名和注释——直接导入到已打开的 Binary Ninja 二进制视图中。
Ghidra 和 Binary Ninja 各有优势。本插件让你可以在同一个二进制文件上同时使用两者,而无需手动在它们之间复制名称或注释。连接到正在运行的 Ghidra Server,浏览其仓库,双击任意项目文件即可将其分析结果拉取到当前打开的 BN 视图中。
同步是双向的。
| BN ← Ghidra(导入) | BN → Ghidra(检入) | |
|---|---|---|
| 符号(标签、函数名) | ✓ | ✓ |
| 注释(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 子进程(即“bridge”)。bridge 持有与 Ghidra Server 的 RMI 连接,并通过本地 TCP 套接字与插件之间使用简单的 JSON 协议通信。这样可以将所有 Java/RMI 代码排除在 C++ 进程之外,并让 JVM 在 BN 完成加载的同时在后台启动。
bridge JVM 还会在启动时初始化 Ghidra 的 Application 框架,以便写入路径可以使用 Ghidra 的高层程序模型 API(ProgramDB、DataTypeManager、SymbolTable、FunctionManager),而不是原始的 db.Table.putRecord() 写入——请参阅下方的检入写入路径。
| 路径 | 语言 | 角色 |
|---|---|---|
plugin/ | C++ / Qt6 | Binary Ninja 侧边栏插件 |
bridge/ | Java 17 | Ghidra RMI 客户端 + JSON 桥接服务器 |
插件(C++):
plugin.cpp — 注册设置和侧边栏控件;在加载时立即启动 bridge JVMGhidraConnection.cpp — 单例;管理 bridge 生命周期及所有基于 RMI 的操作BridgeProcess.cpp — 将 bridge JAR 作为子进程启动,带有 stdout/stderr 管道;读取 READY port=N 握手行BridgeClient.cpp — TCP 客户端;发送 JSON 请求、接收响应、分发异步事件SyncEngine.cpp — 将 GhidraDbExport 应用到 BinaryView(符号、注释、标志)ui/ProjectPanel.cpp — 侧边栏控件:仓库树、连接对话框、活动日志ui/ConnectDialog.cpp — 主机/端口/用户/密码对话框Bridge(Java):
BridgeMain.java — 参数解析;初始化 UniversalIdGenerator 和 Ghidra Application 框架;启动 TCP 服务器;向 stdout 打印 READY port=NBridgeServer.java — 接受一个 TCP 客户端连接,并为其分配一个 BridgeConnectionBridgeConnection.java — JSON 请求分发器;将 Ghidra API 响应序列化为 JSON;处理 opCheckin(在服务器上创建新的程序版本)GhidraSession.java — 已认证的 RMI 会话;封装 RemoteRepositoryServerHandleEventStreamer.java — 每个已打开仓库的后台线程;将 RepositoryChangeEvent 作为异步 JSON 事件推送到插件DatabaseExporter.java — 读取路径:通过原始 访问,从 (Ghidra 的远程数据库缓冲区)中提取符号/注释/函数标志/数据类型/等价常量/书签表opCheckin 以写入模式打开程序的管理缓冲区文件,在其上构造一个 ProgramDB,并通过 Ghidra 的程序模型 API 应用 BN 侧的更改。避免使用原始的 db.Table.putRecord() 写入——它们是我们遇到过的所有检入损坏 bug 的根源:
ProgramApplier 没有这些陷阱,因为它通过 DataTypeManager.addDataType、SymbolTable.createLabel、Listing.setComment、Function.setReturnType 等 API 进行路由——这些 API 会自动维护 Ghidra 相互关联表的不变量。它还会在每次检入开始时运行 cleanupBadVariableSymbols 过程,以清除旧版 bridge 在数据库中留下的损坏。
server-package/CleanupBadVariableSymbols.java 是一个独立的 GhidraScript,它通过 analyzeHeadless 运行相同的清理——当文件损坏严重到无法在 Ghidra GUI 中打开时非常有用。
./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 顶部的兼容性矩阵)。
一致性判据(第 3 层)。 双方独立地针对相同的已检入规范 JSON(bridge DatabaseExporter 形状)进行验证。导入方向:黄金文件加载到 ProgramDB(Java)中,并通过 SyncEngine(C++)加载到 BinaryView 中,每次重新导出都必须等于黄金文件。检入方向:脚本化的 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 — 复合设置必须与头部保持一致(cloneAllComponentSettings 崩溃)FunctionSignaturesRoundTripTest.returnType_doesNotCorruptStackPurge — IntField 截断ParametersRoundTripTest.noParameterSymbol_endsUpAtRamAddress — VariableAddress 不变量往返和一致性测试需要 Ghidra 安装(在运行时用于语言服务)。路径从 ghidra.home Gradle 系统属性或 GHIDRA_HOME 环境变量读取;build.gradle 默认传递 ghidraHome。第 2/3 层 C++ 测试还需要 binaryninjacore 可加载(脚本将 BN 安装目录添加到 PATH)。
插件链接到与 Binary Ninja 使用的相同 Qt 6 构建。你有两个选项:
选项 A — 使用现有的 Qt 安装(如果你已有 Qt,这是最快的方式)
将 Qt6_DIR 指向你的 Qt CMake 目录:
Qt6_DIR=/path/to/Qt/6.x.y/clang_64/lib/cmake/Qt6 ./build.sh
在 macOS 上,如果 Qt 是通过 Qt 在线安装程序安装在 /usr/local/Qt* 下,构建脚本会自动检测 Qt。
选项 B — 从 qt-build 子模块构建 Qt(约 1-2 小时,每台机器一次)
qt-build 子模块(Vector35 的 Qt 构建脚本)使用 Binary Ninja 的补丁编译 Qt 6。它需要 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/ 目录已被 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)
然后按照上方的 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 在 api_REVISION.txt 中记录的精确提交处克隆 binaryninja-api,因此插件 ABI 始终与已安装的 BN 版本匹配。如果未设置 GHIDRA_HOME,CMake 会在首次配置时自动下载 Ghidra。
安装后,在 Binary Ninja 的设置中设置以下内容(Edit → Preferences → Settings,搜索 "Ghidra"):
步骤 5 的先决条件: 程序文件必须已提交到 Ghidra Server 仓库(而不仅仅是在 Ghidra 中本地打开)。在 Ghidra 中:右键单击 Project 窗口中的文件 → Version Control → Add to Version Control…。
插件和 bridge 通过本地 TCP 套接字使用换行分隔的 JSON 进行通信。每个请求都携带一个整数 id 和一个字符串 op;每个响应都会回显 id。异步事件(服务器端仓库更改)则携带一个 "event" 键。
仓库包含从头重新构建所需的一切。不在 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 提交:
./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 和 互斥;两者都不指定时,使用 通道。 会查询 GitHub(最新的 发布版,或 分支头),因此需要网络访问。如果你安装的 BN 落后于最新发布版,请传递 并附上该安装的 中的精确 SHA。当打开一个新的 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 地址空间。覆盖空间或哈佛架构可能会产生不正确的地址。DBChangeSet 以保持检出正常工作。因此 Ghidra 的检出时合并机制无法自动解决 BN 和 Ghidra 用户之间的并发编辑——最后写入者获胜。db.jarManagedBufferFileHandleProgramApplier.java — 写入路径:将缓冲区文件作为真正的 ProgramDB 打开,并通过 Ghidra 的高层 API 应用所有 BN 侧的更改(请参阅下方的检入写入路径)DatabaseImporter.java — 仅作为测试接缝保留的旧式原始写入辅助工具;生产环境的 apply(...) 委托给 ProgramApplier| 错误层级的写入 | 失败模式 |
|---|
在 Function Data 表上调用 setIntValue(col, longTypeId) | IntField.setLongValue 会静默地 l2i 截断 → 每次签名更新时 StackPurge 损坏 |
在 V5V6 Composite Data Types 上调用 setByteValue(col, isUnion) | 在 Ghidra 12.x 中该列是 BooleanField → IllegalFieldAccessException(“Illegal field access”) |
在 V2 Typedef Flags 列上调用 setIntValue(col, 0) | 该列是 ShortField → 同样的崩溃,不同的 schema |
| 写入复合头但没有组件设置行 | 在 Ghidra 中打开结构体时,CompositeEditorModel.cloneAllComponentSettings 抛出 ArrayIndexOutOfBoundsException |
写入 PARAMETER 符号时 SYM_ADDR_COL = RAM 地址 | 任何函数访问时,FunctionDB.loadSymbolBasedVariables 抛出 Address is not a VariableAddress |
向 DBHandle.save() 传递 null DBChangeSet | 服务器写入 0 字节的变更数据文件 → 下次检出时在 ProgramContentHandler.loadProgramChangeSet 中因 EOFException 失败 |
| 层级 | 内容 | 位置 | 门槛 |
|---|
| 0 | 纯单元测试 | plugin/test/*.cpp(binja-ghidra-tests)、bridge *Test.java | 始终运行 |
| 1 | Ghidra-DB 往返 | bridge *RoundTripTest.java(ProgramApplier 针对真实的 ProgramDB) | 需要 ghidra.home / GHIDRA_HOME |
| 2 | BN BinaryView/.bndb 往返 | plugin/test/bn/(binja-ghidra-bn-tests;headless binaryninjacore) | 在没有支持 headless 的 BN 许可证时干净地 SKIP(遵循 BN_LICENSE 环境变量) |
| 3 | 跨数据库一致性 | CanonicalParityTest(C++ 和 Java)针对 testdata/parity/fixtures/ 中的共享黄金文件 | 与第 1+2 层一起 |
| 4 | 实时服务器 E2E | bridge LiveServerE2ETest — 在临时目录中启动真实的 ghidraSvr,通过 analyzeHeadless 播种,通过 RMI 驱动检出 → 导出 → 检入 → 重新导出 | test.bat --e2e(设置 GHIDRA_E2E=1) |
| 依赖项 | 说明 |
|---|
| Binary Ninja(商业版) | 针对与 BN 安装中 api_REVISION.txt 匹配的版本进行测试 |
| Ghidra Server | 使用 Ghidra 12.0.4 测试。必须正在运行并可通过 RMI/SSL 访问 |
| Java 17+ JDK | 推荐 Eclipse Adoptium JDK 21 |
| CMake 3.24+ | |
| Ninja | |
| C++ 编译器 | Windows 上为 MSVC 2022+;macOS 上为 clang;Linux 上为 gcc/clang |
| Qt 6.7+ | 请参阅下方的 Qt 设置;构建时 qmake 必须在 PATH 上 |
| Gradle(通过 wrapper) | bridge 使用 Gradle wrapper——无需单独安装 |
| Poetry (仅 Qt 构建) | 仅当从 qt-build 子模块构建 Qt 时需要。使用 pip install poetry 或 pipx install poetry 安装。 |
| libclang 19 (仅 Qt 构建) | Qt 构建系统需要。下载说明请参阅 qt-build/README.md。 |
| 设置 | 描述 |
|---|
ghidra.javaExe | java.exe 的完整路径 |
ghidra.ghidraHome | Ghidra 安装的根目录(包含 Ghidra/Framework/…) |
ghidra.trustAllCerts | 如果你的 Ghidra Server 使用自签名证书,请设置为 true |
ghidra.defaultHost | 预填充连接对话框 |
ghidra.defaultPort | 默认值:13100 |
ghidra.defaultUser | 预填充连接对话框 |
| 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 DB → JSON(重量级) |
checkin | 请求/响应 | 应用 BN 侧更改 → 新仓库版本(重量级,通过 ProgramApplier) |
download_binary、upload_binary | 请求/响应 | 移入/移出原始二进制文件 |
delete_item | 请求/响应 | 从仓库中删除文件 |
repo_changed | 事件(异步) | 服务器端 RepositoryChangeEvent 推送 |
--bn-api--channelstable/*dev--bn-apiapi_REVISION.txt