nccgroup/ghostrings
GitHub: nccgroup/ghostrings
该项目提供一组 Ghidra 脚本,利用 P-Code 分析恢复 Go 二进制文件中的字符串定义,解决无空终止符导致的字符串恢复难题。
Stars: 133 | Forks: 9
# Ghostrings
用于通过 P-Code 分析恢复 Go 二进制文件中的字符串定义的脚本。
已在 x86、x86-64、ARM 和 ARM64 上测试通过。
## 脚本
### Golang
这些脚本可在脚本管理器的 Golang 分类中找到。
* `GoDynamicStrings.java`
* 分析 P-Code 以查找在栈上创建的字符串结构。使用底层“寄存器”风格分析。
* `GoFuncCallStrings.java`
* 分析 P-Code 以查找通过寄存器直接传递给函数调用的字符串结构,而无需写入栈。使用“标准化”风格分析,并依赖 Ghidra 新的内置 Golang 支持(请参阅 Ghidra 发行说明以了解支持的 Golang 版本)。
* `GoStaticStrings.java`
* 查找静态分配在只读内存中的 Go 字符串结构。
* `GoKnownStrings.java`
* 搜索标准唯一字符串并定义它们。字符串数据从 `data/known_strings.json` 加载。
* `GoStringFiller.java`
* 在初始分析后,基于字符串数据长度的升序顺序,填补 `go.string.*` 中的空白。
还有一些动态字符串分析脚本的特殊变体:
* `GoDynamicStringsSingle.java`
* 执行与 `GoDynamicStrings.java` 相同的分析,但使用单个反编译器进程。如果分析二进制文件导致并行反编译器进程耗尽系统内存,请使用此脚本。
* `GoDynamicStringsHigh.java`
* 实验性脚本,使用“标准化”风格分析的高级 P-Code 输出。目前依赖一个关闭反编译器中死代码消除的 hack(详见 )。*此 hack 在 Ghidra 10.2 中失效。*
### P-Code
可在脚本管理器的 PCode 分类中找到。
* `PrintHighPCode.java`
* 将当前选中函数的高级 P-Code 输出打印到控制台,并附带一个用于选择反编译器简化风格的选项。主要用于开发 P-Code 分析脚本。
## 字符串恢复流程
以下是使用这些脚本在 Go 二进制文件中恢复字符串定义的一般流程:
1. 清除只读数据内存块中所有错误或自动定义的字符串。
* 在“已定义字符串”窗口中,启用“内存块”列,并通过内存块筛选以选择 `.rodata`、`.rdata` 或 `__rodata` 中的所有字符串。然后在代码列表中右键单击并选择“清除代码字节”。
2. (可选)运行 `GoKnownStrings.java` 以检测一些标准字符串。
3. 运行 `GoStaticStrings.java`。
4. 运行 `GoFuncCallStrings.java`(如果 Ghidra 内置的 Golang 功能支持该 Golang 版本)。
5. 运行 `GoDynamicStrings.java`。
6. 运行 `GoStringFiller.java`。
* 如果检测到字符串数据长度升序顺序的违规,请清除该区域中的错误字符串定义并重新运行脚本。该脚本提供自动执行此操作的选项。
* 如果二进制文件被剥离,请定位动态字符串脚本找到的单字节字符串区域。
确保它是分组的非空终止字符串的起始位置(后续应定义更多按长度升序排列的字符串)。
在第一个单字节字符串处创建标签 `go.string.*`。
7. 检查 `go.string.*` 中剩余的空白,并定义任何具有明显起始和结束点的字符串。
* 为了充分利用 `GoStringFiller.java`,请确定字符串长度在未定义字符串数据中发生变化的位置,并定义最接近该边界的字符串。然后重新运行 `GoStringFiller.java` 以自动填充其能够正确确定长度的剩余未定义字符串。
8. (可选)重新运行 Ghidra 内置的 ASCII 字符串分析工具。
* 禁用覆盖现有字符串。先运行不带空终止符要求的选项,再运行带空终止符要求的选项。
## 安装
### 安装
在 Ghidra 中:
1. 文件 -> 安装扩展 -> 添加扩展
2. 选择扩展 ZIP 文件
### 开发
使用带 GhidraDev 插件的 Eclipse:
1. 克隆仓库
2. 文件 -> 导入 -> 从文件夹或归档导入项目
3. 选择仓库目录
4. GhidraDev -> 链接 Ghidra...
5. 选择导入的项目
### 构建
使用 Eclipse 构建:
* GhidraDev -> 导出 -> Ghidra 模块扩展...
直接使用 Gradle 构建:
```
$ cd Ghostrings
$ gradle -PGHIDRA_INSTALL_DIR=
```
## 背景
逆向工程 Go 程序时一个众所周知的问题是:Go 字符串缺乏空终止符,这使得从编译后的二进制文件中恢复字符串定义变得困难。许多 Go 程序的常量字符串值被一起存储在一个巨大的数据块中,字符串数据中没有内置的终止字符来标记一个字符串结束和下一个字符串开始的位置。即使是一个只打印 “Hello world!” 的简单程序,其中也包含超过 1,500 个与 Go 运行时系统和标准库相关的字符串。这会导致典型的 ASCII 字符串发现实现(例如 Ghidra 提供的实现)创建长度达到数万个字符的错误字符串定义。
Go 使用的不是空终止字符串,而是一种由指针和长度值组成的字符串结构。这些字符串结构中有许多是在程序运行时动态创建在栈上的,因此恢复单个字符串的起始位置和长度值需要分析编译后的机器代码。虽然已有一些脚本通过检查特定的 x86-64 指令模式来执行此类分析,但它们会遗漏使用未处理指令变体创建的最终效果相同的结构,并且它们还受限于特定的指令集架构。
Ghostrings 通过使用 Ghidra 反编译器生成的简化、与架构无关的 P-Code 操作来避免这两个问题。
## 版本信息
版权所有 2022 NCC Group。根据 GPLv3 许可证发布(请参阅 LICENSE)。
主要作者:James Chambers
标签:Ghidra脚本, Go二进制, Go语言, Hakrawler, JARM, JS文件枚举, P-Code分析, URL提取, 二进制分析, 云安全监控, 云安全运维, 云资产清单, 代码分析, 内存分析, 凭证管理, 反编译, 域名枚举, 堆栈分析, 字符串定义, 字符串恢复, 寄存器分析, 技术分析, 日志审计, 程序破解, 脚本管理器, 读-only内存, 逆向工程, 静态分析