Ghidra——一款开源的逆向的尚方宝剑

0 阅读9分钟

一、常用工具与命令

(一)file命令

作用:检查文件类型的强大工具,不依赖文件扩展名,而是基于文件内容识别。

检查原理

  1. ASCII 编码的文本文件:识别特定的字符串特征。
  2. 非 ASCII 编码的二进制文件:识别幻数(Magic Numbers)和特殊结构。

什么是幻数? 文件格式要求的特殊标签值,通常位于文件头部。

  • MS-DOS 可执行文件MZ (Mark Zbikowski, MS-DOS 架构设计师姓名缩写)
  • Java Class 文件0xcafebabe (便于记忆的十六进制值)
  • Windows PE 可执行文件4D5A (即 ASCII 的 "MZ")
  • JPG 图片文件FFD8... 结尾通常包含 4A464946 (ASCII 的 "JFIF")

幻数定义文件位置/usr/share/file/magic/usr/share/misc/magic/etc/magic

(二)strip命令

作用:剥离二进制文件中的符号表(Symbol Table)和调试信息。

  • 影响:会导致无法进行源码级调试。
  • 优势:显著减小文件体积,常用于发布版本。

(三)nm命令

作用:列出目标文件(如 ELF 可执行文件、.o 中间文件)中的符号表。

符号字母说明

  • 大小写含义:大写字母表示全局符号,小写字母表示局部符号。

  • 常见符号类型

    • U (Undefined):未定义的符号,通常引用自外部库或其他文件。
    • T / t (Text):代码段定义的符号。T 为全局函数,t 为静态函数(本地)。
    • D / d (Data):已初始化的数据段。D 为全局变量,d 为局部静态变量。
    • C (Common):未初始化的数据(通常在全局区)。
    • B / b (BSS):未初始化的数据段。

(四)查看动态库依赖

不同平台下查看可执行文件所需动态链接库的命令:

平台工具/命令使用示例备注
Linuxlddldd <可执行文件名>最常用
Linuxobjdump / readelfreadelf -d <文件名>基于 libbfd,提供更底层信息
Windowsdumpbindumpbin /dependents <文件名>需安装 Visual Studio
macOSotoolotool -L <可执行文件名>macOS 原生工具

(五)c++filt命令

作用:将 C++ 编译器生成的修饰名(Mangled Names)还原为可读的函数名。 典型用法:配合 nm 使用,分析重载函数。

nm <文件名> | c++filt

(六)strings命令

作用:从二进制文件中提取可打印的字符串序列。 典型用法

strings <文件名>

常用参数

  • -t [o|x|d]:打印字符串的偏移量(八进制、十六进制或十进制),便于定位。
  • -e [s|S|l|L|b|B|h|H]:指定字符编码(如 UTF-8, UTF-16 等),用于搜索宽字符。
  • -a:扫描整个文件,而不仅仅是数据段(默认只扫描已加载的数据区)。
  • -n <len>:设置最小字符串长度(默认为 4)。

二、平台特定工具与对抗技术

1. Windows 可执行文件分析工具

  • PE Tools:全面的 PE 文件查看器,可编辑节表、资源等。
  • PEiD:经典的查壳工具,用于识别打包器、编译器版本(注:较老,对新壳支持有限)。

2. Linux/通用反汇编工具

  • ndisasm (NASM 配套):简单的线性反汇编器。
  • distorm3:强大的轻量级反汇编引擎,常用于脚本集成。

3. 反逆向方案(保护技术)

用于增加逆向分析难度的加壳或混淆工具:

  • 压缩壳UPX (开源,主要压缩体积,易脱壳), ASPack
  • 加密/保护壳ASProtect, Themida, TELock, VMProtect (虚拟化保护,难度较高)。

4. 去混淆与脱壳方案

  • QuickUnpack

    • 原理:在内存中运行被保护的程序,利用其自身解压/解密机制,在内存中还原原始代码后提取(Dump)。
    • 建议:由于涉及执行未知代码,务必在沙盒或隔离虚拟机环境中进行分析
  • 其他通用方法:OllyDbg/x64dbg 动态调试脱壳、Unpacker 插件、手动修复 IAT。

三、语言特性与底层结构

1. 堆栈(Stack)机制

  • 数据残留pop 操作通常只是移动栈顶指针(ESP/RSP),并不会立即清除内存中的数据。
  • 安全性:旧数据在被新数据覆盖(push)之前,理论上仍存在于内存中,可通过内存转储或调试手段恢复(可能导致信息泄露)。

2. Main 函数

  • 程序的入口点。在 C/C++ 中,实际入口往往是启动例程(如 _start -> __libc_start_main -> main)。
  • 逆向时需注意参数传递(argc, argv, envp)。

3. 函数重载与名称修饰

C++ 支持函数重载,编译器会将函数名、参数类型、命名空间等信息编码成唯一的符号名。

  • 示例函数void SubClass::vfunc1()

  • Microsoft (MSVC) 格式?vfunc1@SubClass@@UAEXXZ

    • ? 开头,@ 分隔符,包含类名、调用约定、返回类型等信息。
  • Intel/GNU (GCC/Clang) 格式_ZN8SubClass6vfunc1Ev

    • _Z 开头,N 表示命名空间/类,数字表示后续标识符长度,E 结束,v 表示 void。
  • 解析工具:使用 c++filt 可相互转换。

四、Ghidra 逆向分析框架详解

(一)特殊前缀与数据类型

Ghidra 在反编译窗口中会对变量和地址添加前缀以区分类型:

前缀含义说明
param_参数传递给函数的参数。
local_局部变量函数栈帧内的局部变量。
undefined4未知类型编译器无法确定具体类型,长度为 4 字节的变量。
LAB_address代码标签自动生成的代码跳转目标地址。
DAT_address数据标签全局数据或静态数据地址。
FUN_address函数标签已识别的函数入口地址。
SUB_address子程序标签被调用但未被完全确认为标准函数的地址。
EXT_address外部符号外部导入的函数或变量。
OFF_address偏移/中断可能指向中断向量、数据偏移或错误处理。
UNK_address未知Ghidra 无法识别其属性或用途的地址。

注:address 代表具体的十六进制地址值。

(二)Language ID 规范

Ghidra 使用特定的字符串标识处理器架构和模式,格式如下: 处理器:端序:位宽:变体/模式:编译器

  • 处理器x86, x86_64, ARM, MIPS, AARCH64 等。

  • 端序

    • LE :小端序(Intel x86, ARM 默认)。
    • BE:大端序(部分 MIPS, 旧版 ARM)。
  • 架构大小16, 32, 64 (指寄存器宽度/地址总线宽度)。

  • 处理器变体或模式

    • x86:real (实模式), protected (保护模式), v86 (虚拟 8086)。
    • ARM:v4, v5, v7, v8, thumb (Thumb 指令集模式)。
  • 编译器gcc, borlandcpp, borlanddelphi, msvc 等,有助于优化反编译逻辑。

示例x86:LE:32:default:gcc

(三)协作分析

  • 优势:Ghidra 原生支持多人同时在一个项目中工作,适合团队逆向大型项目。相比 IDA Pro 的协作方案,Ghidra 更加开放且免费。
  • 设置:需要搭建 Ghidra Server (基于 Java),配置数据库连接,团队成员通过客户端连接同一服务器项目进行实时同步。

(四)二次开发

  • 支持语言:主要支持 Java (编写插件/模块) 和 Python/Jython (编写自动化脚本)。

  • 应用场景

    • 批量处理文件。
    • 自定义分析逻辑(如识别特定的加密算法)。
    • 自动化重命名函数和变量。
    • 导出特定格式的报告。
  • API:提供丰富的 API 访问反汇编列表、反编译伪代码、符号表等。

    用户界面交互

    方法签名功能描述
    public void popup(final String message)弹出提示框:在对话框中显示信息并要求用户选择。
    public String askString(String title, String message)字符串输入框:带标题和提示信息的文本输入对话框。
    public boolean askYesNo(String title, String message)确认对话框:询问是或否的问题。
    public Address askAddress(String title, String message)地址输入框:解析用户输入的地址字符串。
    public int askInt(String title, String message)整数输入框:获取用户输入的整数值。
    public File askFile(final String title, final String approveButtonText)文件选择器:让用户选择文件。
    public File askDirectory(final String title, ...)文件夹选择器:让用户选择目录。
    public void printf(String message, Object... args)格式化输出:类似 C 语言的 printf,将格式化后的内容输出到控制台。
    public void println(String message)普通输出:非入侵式地打印一行信息到控制台。

    核心数据接口

    1. 地址与符号
    接口方法功能描述
    地址getOffset()获取地址的长整型偏移值。
    符号getAddress()获取该符号所在的内存地址。
    符号getName()获取符号的名称字符串。
    2. 引用关系
    方法功能描述
    getFromAddress()获取引用的源地址
    getToAddress()获取引用的目的地址
    getReferenceType()获取引用的类型。

    GhidraScript 类成员与任务监控

    1. 预设数据成员

    变量名类型功能描述
    currentProgramProgram当前打开的程序:代表正在分析的二进制文件对象。
    currentAddressAddress当前光标地址:用户在反汇编窗口中光标所在的地址。
    currentLocationProgramLocation当前位置详情:包含地址及更具体的上下文信息。
    currentSelectionProgramSelection当前选中范围:用户在 GUI 中高亮选中的地址区域。

    2. 任务监控

变量/方法功能描述
monitor (TaskMonitor)任务监视器:用于更新长时间运行任务的状态进度条。
monitor.isCancelled()检查取消状态:判断用户是否点击了“取消”按钮。通常在循环中调用此方法以优雅地退出脚本。

(五)无头模式

  • 定义:在不启动图形用户界面 (GUI) 的情况下运行 Ghidra。

  • 工具analyzeHeadless 脚本。

  • 用途

    • CI/CD 集成:在自动化流水线中自动分析二进制文件。
    • 批量处理:一次性分析成百上千个样本。
    • 服务器部署:在资源受限或无显示环境的服务器上运行分析任务。
    • 脚本执行:直接调用 Python/Java 脚本对文件进行处理并输出结果。
  • 基本命令结构

    analyzeHeadless <项目路径> <项目名称> -import <文件路径> -scriptPath <脚本目录> -postScript <脚本名>.py
    
  • 常用参数详解

参数说明示例
-import导入文件,支持通配符-import *.dll
-scriptPath添加脚本搜索路径-scriptPath /home/user/scripts
-postScript分析后执行的脚本(可多次使用)-postScript dump_funcs.py
-scriptArgs传递给脚本的参数-scriptArgs output.txt
-deleteProject处理完后删除项目目录(无参数)
-projectBackup是否备份项目-projectBackup false
-log指定日志文件-log analysis.log
-help显示帮助信息-help