8、静态分析工具(下):Ghidra 基础使用、反编译、脚本编写入门
上一章我们聊了 IDA Pro,今天来聊聊 Ghidra。说实话,我第一次接触 Ghidra 是抱着怀疑态度的——NSA 开源的工具,能好用吗?结果用了半小时,我就真香了。它免费、开源、功能强大,尤其反编译引擎,我个人觉得在某些场景下比 IDA 还顺手。
8.1 Ghidra 的界面与项目管理
Ghidra 启动后,你会看到一个 Java 风格的界面。嗯,别被它略显复古的 UI 吓到,内核很强大。
核心概念:
- 项目(Project):每个分析任务对应一个项目,类似 IDA 的 .idb 文件
- 工具(Tool):默认的 CodeBrowser 是最常用的工具
- 程序(Program):被加载的二进制文件
我习惯的做法是:每个样本单独建一个项目,命名规则用「样本名_日期」。这样方便后期回溯,尤其是当你同时分析十几个样本时,项目管理混乱会让人崩溃。
8.2 反编译引擎的使用
Ghidra 的反编译引擎叫 Decompiler,它能把汇编代码转换成类似 C 语言的伪代码。这是 Ghidra 最吸引我的地方。
基本操作:
- 在 Listing 窗口选中一个函数
- 按
F5或右键选择Decompile - Decompiler 窗口会显示伪代码
实战经验:我曾经分析过一个混淆严重的恶意软件,IDA 的 F5 直接罢工了。但 Ghidra 的 Decompiler 居然能输出可读的伪代码,虽然有些变量名是乱的,但逻辑清晰多了。后来我查了资料,Ghidra 的 Decompiler 对控制流平坦化(Control Flow Flattening)的还原能力确实比 IDA 强一些。
反编译窗口的常用功能:
- 重命名变量:双击变量名,改成有意义的名称
- 添加注释:选中代码行,按
;键 - 设置数据类型:右键变量 → Set Data Type
- 导航到引用:右键 → References → Show References to
8.3 脚本编写入门
Ghidra 支持多种脚本语言:Python(Jython)、Java、JavaScript。我个人推荐用 Python,因为生态好,而且你可以在脚本里直接调用 Ghidra 的 API。
脚本环境:
- 菜单栏 → Window → Script Manager
- 点击
Manage Script Directories添加你的脚本目录 - 点击
New创建新脚本
下面是一个简单的脚本示例,用于遍历所有函数并打印它们的名称和地址:
# 获取当前程序
from ghidra.program.model.listing import Function
from ghidra.util.task import ConsoleTaskMonitor
program = getCurrentProgram()
listing = program.getListing()
functions = listing.getFunctions(True)
monitor = ConsoleTaskMonitor()
print("函数列表:")
for func in functions:
name = func.getName()
addr = func.getEntryPoint()
print(f"0x{addr} : {name}")
脚本运行:
- 在 Script Manager 中找到你的脚本
- 双击运行,或选中后点击
Run - 输出会显示在
Console窗口
小技巧:写脚本时,我习惯先打开 Ghidra 的 API 文档(Help → Ghidra API Help)。里面每个类和方法都有详细说明,还有示例代码。别自己硬记 API,查文档效率高得多。
8.4 实战:自动化分析恶意软件
我曾经遇到一个样本,里面有 2000 多个函数,手动分析根本不可能。于是我写了个脚本,自动标记所有调用 strcpy 和 memcpy 的函数,并输出它们的调用路径。
脚本核心逻辑:
# 查找所有调用特定函数的引用
from ghidra.program.model.symbol import RefType
target_funcs = ["strcpy", "memcpy", "sprintf"]
program = getCurrentProgram()
listing = program.getListing()
func_manager = program.getFunctionManager()
for target in target_funcs:
symbols = program.getSymbolTable().getSymbols(target, None)
for sym in symbols:
refs = sym.getReferences()
for ref in refs:
if ref.getReferenceType() == RefType.DATA_IND:
continue
caller_func = func_manager.getFunctionContaining(ref.getFromAddress())
if caller_func:
print(f"[危险调用] {caller_func.getName()} 调用了 {target} 在 {ref.getFromAddress()}")
这个脚本帮我快速定位了所有危险函数调用,节省了至少 3 小时的手动分析时间。
8.5 Ghidra 脚本的进阶技巧
1. 使用 FlatProgramAPI
Ghidra 提供了 FlatProgramAPI,它封装了常用操作,让脚本更简洁。比如上面的脚本可以用 FlatProgramAPI 改写:
from ghidra.app.script import GhidraScript
from ghidra.program.model.symbol import RefType
class FindDangerousCalls(GhidraScript):
def run(self):
target_funcs = ["strcpy", "memcpy", "sprintf"]
for target in target_funcs:
symbols = self.getSymbols(target, None)
for sym in symbols:
refs = sym.getReferences()
for ref in refs:
if ref.getReferenceType() == RefType.DATA_IND:
continue
caller_func = self.getFunctionContaining(ref.getFromAddress())
if caller_func:
self.println(f"[危险调用] {caller_func.getName()} 调用了 {target}")
2. 脚本参数化
你可以在脚本中定义参数,运行时弹出对话框让用户输入。比如:
from ghidra.app.script import GhidraScript
class ParamExample(GhidraScript):
def run(self):
min_addr = self.askAddress("请输入起始地址", "0x1000")
max_addr = self.askAddress("请输入结束地址", "0x2000")
self.println(f"分析范围: {min_addr} - {max_addr}")
注意:Ghidra 的脚本 API 版本更新较快,不同版本之间可能有细微差异。我建议你始终使用当前版本对应的 API 文档。另外,Jython 的性能不如原生 Python,如果脚本处理大量数据,考虑用 Java 重写。
8.6 本章知识体系
下面这张图总结了 Ghidra 的核心使用流程和脚本编写路径:
8.7 避坑指南
最后分享几个我踩过的坑:
- 中文路径问题:Ghidra 对中文路径支持不好,项目路径和文件路径最好都用英文。我曾经因为路径里有中文,导致反编译结果乱码,排查了半天才发现是路径问题。
- 内存不足:分析大文件时,Ghidra 默认内存可能不够。在启动脚本里加上
-Xmx4G参数,分配 4GB 内存会稳很多。 - 脚本调试:Ghidra 的脚本调试功能比较弱,我习惯在脚本里加大量
println输出,配合 Console 窗口看日志。 - 版本兼容:不同版本的 Ghidra 保存的项目文件可能不兼容。升级前最好备份项目,或者导出为 .gzf 格式。
好了,Ghidra 的基础使用就聊到这里。工具只是手段,关键还是你的分析思路。多写脚本,多实战,慢慢你就会发现 Ghidra 的强大之处。