一、常用工具与命令
(一)file命令
作用:检查文件类型的强大工具,不依赖文件扩展名,而是基于文件内容识别。
检查原理:
- ASCII 编码的文本文件:识别特定的字符串特征。
- 非 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):未初始化的数据段。
(四)查看动态库依赖
不同平台下查看可执行文件所需动态链接库的命令:
| 平台 | 工具/命令 | 使用示例 | 备注 |
|---|---|---|---|
| Linux | ldd | ldd <可执行文件名> | 最常用 |
| Linux | objdump / readelf | readelf -d <文件名> | 基于 libbfd,提供更底层信息 |
| Windows | dumpbin | dumpbin /dependents <文件名> | 需安装 Visual Studio |
| macOS | otool | otool -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 指令集模式)。
- x86:
-
编译器 :
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 |