Const24/GhidraSharp
GitHub: Const24/GhidraSharp
一个基于 gRPC 桥接的强类型 C# 客户端,让 .NET 应用无需 Python 即可调用 Ghidra 进行二进制反编译与逆向分析。
Stars: 1 | Forks: 0
# GhidraSharp
一个基于小型 gRPC 桥接的**强类型 [Ghidra](https://ghidra-sre.org/) 的 C# 客户端**。
整个调用链中不涉及 Python。
目前没有官方或广泛使用的 Ghidra C# 绑定——社区提供的方案是
Python 的 `ghidra_bridge`,而且 Ghidra 唯一的第一方跨语言通信
协议仅局限于调试器范围内,并非静态分析 API。GhidraSharp
旨在通过一个精心设计的、极简且强类型的接口层来填补这一空白:
```
┌─────────────────────┐ gRPC (protobuf) ┌─────────────────────────────┐
│ Const24.GhidraSharp │ ──────────────> │ GhidraSharpServer (Java) │
│ C# client (net8/10) │ <────────────── │ Ghidra-as-library, headless │
└─────────────────────┘ └─────────────────────────────┘
```
* **`proto/ghidrasharp.proto`** — 通信契约的唯一事实来源。双方均基于此生成代码;无需手动编写 DTO。
* **`src/GhidraSharp/`** — C# 客户端库(NuGet 包名为 `Const24.GhidraSharp`,
命名空间为 `Const24.GhidraSharp`)。在构建时通过 `Grpc.Tools` 从 proto 生成 gRPC 客户端 stub。
* **`src/GhidraSharp.Sample/`** — 用于测试桥接的控制台冒烟测试。
* **`server/`** — Java gRPC 服务器,将 Ghidra 作为库运行,提供
反编译 / 分析服务(Gradle 构建)。
## 环境要求
* **.NET 8 或更高版本** — 客户端支持 `net8.0` 和 `net10.0`。
* **JDK 21 或更高版本** — 用于构建和运行服务器(Ghidra 12.1 要求 21+)。
* **Ghidra 12.1 或更高版本** — 将 `GHIDRA_INSTALL_DIR` 指向安装目录(基于 12.1 版本构建并测试)。
## 安装说明
**全套包含** — 客户端 + 服务器,服务器会被打包进应用的
输出目录(随 `dotnet publish` 发布,并在运行时自动找到):
```
dotnet add package Const24.GhidraSharp.Server
```
**仅客户端** — 如果你连接的是自行运行的 `GhidraSharpServer`
(参见[运行服务器](#running-the-server)):
```
dotnet add package Const24.GhidraSharp
```
无论哪种方式,运行服务器的机器都需要 **JDK 21+** 和 **Ghidra**
安装(`GHIDRA_INSTALL_DIR`)——这些不包含在发布包中。
## 快速入门
从一个原始的固件 dump(`.bin`)到其反编译的函数 —— 安装
`Const24.GhidraSharp.Server`,将 `GHIDRA_INSTALL_DIR` 指向你的 Ghidra,然后开始。
无需启动服务器,只需指定固件路径:
```
using Const24.GhidraSharp;
// the Const24.GhidraSharp.Server package ships the server next to your app;
// StartAsync finds + runs it, and stops it on dispose.
await using var server = await GhidraServer.StartAsync();
var ghidra = server.Client;
// import a raw firmware dump and analyze it — no project to manage.
// languageId is the target chip's processor (here Renesas SH-2A, Subaru ECUs).
await ghidra.OpenProgramAsync(@"C:\firmware\ecu.bin", languageId: "SuperH:BE:32:SH-2A");
// list every function Ghidra recovered
var functions = await ghidra.ListFunctionsAsync();
foreach (var fn in functions)
Console.WriteLine($" {fn.EntryPoint} {fn.Name}");
// decompile the first one to C
var dec = await ghidra.DecompileAtAsync(functions[0].EntryPoint);
Console.WriteLine(dec.CCode);
```
`languageId` 是唯一特定于 Ghidra 的输入:选择与你的芯片匹配的
处理器 —— 例如 `SuperH:BE:32:SH-2A`、`ARM:LE:32:v7`、`x86:LE:64:default`。不
确定选哪个?`ListLanguagesAsync()` 会枚举 Ghidra 支持的每一种处理器(可以像
`ListLanguagesAsync("SuperH")` 这样进行过滤)。
上述示例是临时性的 —— 不会向磁盘写入任何内容。要**保存项目**
(分析一次,快速重新打开,持久化重命名),请使用
`CreateProjectAsync(binaryPath, projectLocation, projectName, languageId)` — 它
会写入 `.gpr` 文件 + 一个 `.rep` 文件夹,你之后可以
使用 `OpenProgramAsync(name, projectPath: @"...\ecu.gpr")` 重新打开它。想运行
你自己的(或共享的)服务器?跳过 `StartAsync` 并使用
`GhidraClient.Connect("http://127.0.0.1:50080")`。
### 运行服务器
有两种运行方式:
* **独立运行(如下)** — 运行启动器;它会一直保持运行直到你停止它,因此可以
在多次客户端运行中重复使用,由非 .NET 客户端驱动,或作为服务运行。
使用 `GhidraClient.Connect("http://127.0.0.1:50080")` 连接。
* **客户端衍生** — `GhidraServer.StartAsync`(带有 `ServerDirectory`)为一个 .NET 应用
启动并管理一个服务器,并在销毁时停止它(参见上面的快速入门)。
从[发布页面](https://github.com/Const24/GhidraSharp/releases)下载 `ghidrasharp-server-.zip`,
解压,将 `GHIDRA_INSTALL_DIR` 指向你的 Ghidra 安装目录,然后运行启动器(需要 JDK 21+):
```
# Windows (PowerShell)
$env:GHIDRA_INSTALL_DIR = "C:\ghidra_12.1_PUBLIC"
.\ghidrasharp-server.ps1
```
```
# Linux / macOS
export GHIDRA_INSTALL_DIR=/opt/ghidra_12.1_PUBLIC
./ghidrasharp-server.sh
```
它监听 `127.0.0.1:50080` 并一直运行直到你停止它(Ctrl+C,或关闭
终端 —— 它会优雅关闭;没有空闲超时)。想从源码构建?
`cd server && ./gradlew writeServerArgs`,然后 `java @build/ghidrasharp-java.args`。
### 并行批处理 — `GhidraServerPool`
单个服务器一次只能处理一个程序。要处理整个语料库,请运行一个
包含 N 个独立服务器的池(N 个 JVM)并将任务交给它:每个项目在一个服务器上运行,
会捕获单个项目的失败,并且崩溃的服务器会自动重启,以确保批处理顺利完成。
```
await using var pool = await GhidraServerPool.StartAsync(size: 8, new GhidraServerOptions());
var results = await pool.ForEachAsync(romPaths, async (ghidra, rom, ct) =>
{
await ghidra.CreateProjectAsync(rom, projectDir, Path.GetFileNameWithoutExtension(rom), ct: ct);
// ... decompile / extract on this server
}, progress: new Progress(p => Console.WriteLine($"{p.Done}/{p.Total} ({p.Failed} failed)")));
foreach (var f in results.Where(r => !r.Ok))
Console.WriteLine($"FAILED {f.Item}: {f.Error?.Message}");
```
容量规划:加载 Ghidra 后每个 JVM 大约需要 1.5–2 GB 内存 → 在 64 GB 的机器上大约能运行 24 个。
## 状态
桥接功能可用。随着使用者需求的增加,接口会逐个 RPC 地扩展。
* `Ping` — 存活状态 + Ghidra 版本
* `OpenProgram` — 打开已分析的项目,或导入二进制文件
* `DecompileFunction` / `DecompileFunctions`(批量,服务器流式传输) — 函数 → C
* `ListFunctions` — 函数(及被调用者),旨在使用 LINQ 在客户端进行查询
* `GetFunction` — 单个函数的完整详情(强类型签名、参数、局部变量、调用者)
* `GetReferencesTo` / `GetReferencesFrom` — 交叉引用
* `GetFunctionReferences` — 函数体产生的所有向外引用(例如其表数据引用)
* `ListSymbols` / `GetSymbolsAt` — 按名称或地址查找符号
* `RenameSymbol` — 将发现记录回程序中(需要 `writable: true`;除非保存,否则仅存在于内存中)
* `ReadBytes` — 原始程序内存(为纯 C# 字节/表层提供数据)
* `GetInstructions` — 反汇编列表(助记符、操作数、字节)
* `GetDataAt` / `ListDataTypes` / `ApplyDataType` — 定义的数据和数据类型
* `GetInstructionDetail` — 单条指令的结构化操作数 + 原始 PCode
* `GetComments` / `SetComment` — 注释(包含 Ghidra 的全部五种类型)
* `GetBookmarks` / `SetBookmark` — 书签
* `CreateProject` — 将二进制文件导入到新的持久化项目(`.gpr`/`.rep`)中,自动分析并保存
* `SaveProgram` — 将编辑(重命名、应用的数据类型、注释)持久化到磁盘
* `CloseProgram` — 关闭当前程序,释放其磁盘锁(为连接池中的服务器腾出空间以处理下一项)
* `RunScript` — 逃生舱:运行任何 GhidraScript 并捕获其输出
* `ListLanguages` — Ghidra 支持的处理器语言(其语言选择器),用于挑选 `languageId`
架构上天生无关 —— 它只是转发 Ghidra 的语言 ID,因此
同一套代码可以驱动 Ghidra 支持的任何处理器。已在三种截然不同的 ISA —— SH-2A 固件、JVM 字节码和
x86-64 PE —— 上验证了与 pyghidra 的逐字节一致性(参见 [bench/README](https://github.com/Const24/GhidraSharp/blob/main/bench/README.md#multi-architecture))。
公共 C# API 仅公开手写、有文档说明的结果类型
(`ProgramInfo`、`GhidraFunction`、`Decompilation`、`GhidraReference` 等);
生成的 gRPC 通信类型是内部的。命名遵循 Ghidra 自己的术语,因此
Ghidra 用户读起来会很自然,同时 XML 文档也会向不熟悉 Ghidra 的
.NET 开发者解释每一个概念。已在**全 API 范围内(函数、符号、反编译、指令、交叉引用、字节、
函数详情、数据类型)验证了与 pyghidra 的逐字节一致性**,且速度相当 —— 参见
[一致性报告](https://github.com/Const24/GhidraSharp/blob/main/bench/REPORT.md) 和 [bench/](https://github.com/Const24/GhidraSharp/tree/main/bench)。
## 版本控制
客户端和服务器共享同一个通信契约,因此请保持它们的版本匹配 —
安装与 `Const24.GhidraSharp` 包版本相同的服务器发行版。
更新 NuGet 包不会影响你的服务器文件夹;请同时下载匹配的
服务器发行版。版本不匹配时会显式报错,而不是静默失败:`PingAsync().ServerVersion`
会报告服务器的版本,调用版本过低的服务器所不支持的 RPC 时会抛出一个
明确的错误,提示你进行更新。
## 构建客户端
```
dotnet build GhidraSharp.slnx
```
## 测试
采用测试金字塔模型;快速的测试层不需要 Ghidra,可在任何地方运行。
```
# fast — C# 单元 + contract 测试(client ↔ in-process fake server)。无需 Ghidra/JVM。
dotnet test tests/GhidraSharp.Tests --filter "Category!=Integration"
# Java service mapping 测试(JUnit 5 + in-process gRPC + fake engine)
cd server && ./gradlew test
# integration — 真实的 Ghidra 端到端测试(启动 server,使用
# javac 构建一个 JVM 目标,并断言 decompile/rename+save/space-qualified-address 行为)。门控条件:
# 除非设置了 GHIDRA_INSTALL_DIR 且启动参数 argfile 存在,否则将跳过。
cd server && ./gradlew writeServerArgs # once, to produce the argfile
dotnet test tests/GhidraSharp.Tests --filter "Category=Integration"
```
[`bench/`](https://github.com/Const24/GhidraSharp/tree/main/bench) 是验收 + 基准测试层(与 pyghidra 的逐字节一致性);
可通过 `python bench/verify.py` 运行。[`tests/`](https://github.com/Const24/GhidraSharp/tree/main/tests) 和
`server/src/test` 下的测试是单元/契约/集成层。
## 适用范围 — 桥接了什么,没有桥接什么
GhidraSharp **不是**整个 Ghidra API 的镜像,也无意于此。
其目标是针对**核心逆向工程操作**
(如上所述)提供一个精心挑选、强类型且有文档说明的接口层,每一个都经过验证,与 pyghidra 输出完全一致。它故意**不**公开
以下内容:
* **任意 Ghidra API / 实时对象图** — 你得到的是扁平化的结果记录,而不是
可遍历的 Ghidra 对象。
* **反编译器内部结构** — 公开了 C 代码文本、强类型签名和逐指令的 **原始
PCode**,但没有公开反编译器的高级 PCode / `HighFunction`
(data-flow SSA)或 C token↔address 标记。
* **进程内脚本语义** — 无法针对实时程序进行 Java/Python 的临时求值。
对于强类型接口层之外的所有内容,还有 **`RunScript`**:它运行针对
当前程序的任何 GhidraScript 并返回其输出。因此你绝不会比 pyghidra *更受*
限制 —— 只是在长尾需求上缺乏强类型。强类型 RPC 会随着使用者需求按需
添加。
这些限制带来的好处是:一个纯粹的强类型 .NET 技术栈,使用方不包含 Python 和 JVM,
可进行端到端的代码审查,并且拥有你可以亲自验证的正确性保证
(`bench/`)。
**安全说明:** 服务器未经身份验证,并且暴露了 `RunScript`(任意
GhidraScript)以及文件和内存访问权限,因此它**仅绑定到回环地址**,
旨在作为由同一主机上的客户端驱动的本地工具使用 — 切勿暴露其端口。
## 许可证
[Apache-2.0](https://github.com/Const24/GhidraSharp/blob/main/LICENSE) — 与 Ghidra 相同,服务器与其进行了链接。
标签:Ghidra, gRPC, JS文件枚举, Python工具, 二进制分析, 云安全监控, 云安全运维, 云资产清单, 后台面板检测, 逆向工程, 静态分析