软件逆向工程组会:IDA 常见操作与基础知识
面向课程与安全研究,请遵守所在地法律与软件授权。涉及下载/外链:按授权使用或选开源替代(Ghidra/Cutter)。
一、核心认知:IDA 作用与逆向目标
1.1 IDA Pro 定位
IDA Pro 是逆向工程核心工具,支持将二进制文件(exe/so/elf/dll)反汇编为汇编代码并生成伪代码,辅助解析程序逻辑。
1.2 逆向入门核心目标
将 “不可读的二进制文件” 转化为 “可理解的逻辑”,具体包括:
-
定位程序入口(主函数);
-
识别关键逻辑(验证、加密等);
-
修改程序行为(Patch 操作);
-
跟踪运行流程(动态调试)。
二、环境准备:IDA工具安装
IDA Pro 版本:推荐 9.1/9.2(稳定性与新功能兼顾),需匹配样本位数(32 位 / 64 位);
通过网盘分享的文件:IDA Professional 9.1.7z 链接: https://pan.baidu.com/s/1yrROk_2y_jpwky_AsaOQSw?pwd=f4ns 提取码: f4ns
通过网盘分享的文件:ida9.2.zip 链接: https://pan.baidu.com/s/1YInTZr7qnGNMLvqz9b-5ZQ?pwd=f4ns 提取码: f4ns
三、核心操作:静态分析(逆向基础)
静态分析无需运行程序,直接通过 IDA 解析代码逻辑,是逆向的核心环节。
3.1 视图切换与高频快捷键(分类汇总)
| 功能类型 | 快捷键 | 作用说明 |
|---|---|---|
| 视图切换 | tab | 汇编视图 ↔ 伪代码视图(核心切换方式) |
| 空格 | text view(文本视图) ↔ graph view(图表视图) | |
| 导航类 | G | 跳转至指定地址(需输入目标地址) |
| Alt+T | 全局文本搜索(搜指令、字符串、函数名) | |
| 编辑类(汇编) | ;(分号) | 为汇编指令添加注释(便于后续回溯) |
| U | Undefine:将代码转换为 “数据”(处理花指令) | |
| C | Code:将数据转换为 “代码”(恢复误判代码) | |
| 编辑类(伪代码) | N | 变量 / 函数重命名(如 sub_401000 → main) |
| Y | 修改变量 / 函数类型(如 void → int,识别 JNI) | |
| /(斜杠) | 为伪代码添加多行注释(可读性优先) | |
| LazyIDA | Ctrl+Alt+N | 一键 NOP 选中代码(过反调、去花指令,基础功能) |
| 函数 / 字符串 | Shift+F12 | 打开字符串窗口(定位关键逻辑核心工具) |
| ida9.2更新跳转 | Ctrl-Alt-G | 函数、局部类型、名称和段进行不区分大小写的搜索 |
重点提示:空格(视图切换)、G(地址跳转)、N/Y(伪代码编辑)、Shift+F12(字符串查询)为基础高频操作;LazyIDA 进阶功能见第四章专项。
3.2 汇编界面操作要点
3.2.1 必认汇编指令(对应 C 语言逻辑)
| 汇编指令 | 核心作用 | 对应 C 语言场景 |
|---|---|---|
| mov | 数据赋值(mov a,b → a=b) | 变量赋值(如 int a = b;) |
| push/pop | 栈操作(push:数据入栈;pop:数据出栈) | 函数传参(参数入栈)、局部变量初始化(栈空间分配 / 释放) |
| call | 调用函数(call sub_401000 → 跳转到 sub_401000 执行) | 函数调用(如 check_password (input);) |
| ret | 函数返回(从当前函数跳回调用处) | 函数执行结束(return 语句) |
| jmp | 无条件跳转(jmp 0x401200 → 强制跳转到 0x401200) | goto 语句(如 goto end;) |
| je/jne | 条件跳转(je:等于时跳;jne:不等于时跳) | if-else(如 if (a==b) { … } else { … })、循环 |
| cmp | 比较两个值(cmp a,b → 计算 a - b,仅置标志位不保存结果) | if 判断的条件基础(如 if (a==b)、if (a>b) 的底层判断) |
| jz | 零标志位(ZF=1)时跳转(即上一步结果为 0 时跳) | if (结果 == 0)(如 if (func_return == 0) { … }) |
| jnz | 零标志位(ZF=0)时跳转(即上一步结果不为 0 时跳) | if (结果!= 0)(如 if (func_return != 0) { … }) |
| test | 按位与运算(不保存结果,仅置标志位),常用于判断是否为 0 | if (变量 == 0)(如 test eax,eax → 判断 eax 是否为 0,对应 if (eax == 0)) |
| and | 按位与运算(a and b → 结果存回 a),可用于清零、保留特定位 | 按位与操作(如 a &= b;)、清零变量(如 a &= 0;) |
| lea | 计算有效地址(非加载数据),常用于取变量地址或简单计算 | 取变量地址(如 int *p = &a; → lea eax,[a])、简单表达式计算(如 lea eax,[ebx+4] → eax = ebx + 4) |
| add | 加法运算(add a,b → a = a + b) | 数值加法(如 a = a + b;) |
| sub | 减法运算(sub a,b → a = a - b) | 数值减法(如 a = a - b;) |
| xor | 按位异或运算(a xor b → 结果存回 a),可用于清零、交换变量 | 按位异或操作(如 a ^= b;)、清零变量(如 xor eax,eax → eax = 0)、交换变量(无需临时变量) |
| shr | 逻辑右移(shr a,n → a 右移 n 位,高位补 0) | 无符号数除法(如 a = a » 1; → a = a / 2)、取高位字节 |
| shl | 逻辑左移(shl a,n → a 左移 n 位,低位补 0) | 无符号数乘法(如 a = a « 1; → a = a * 2)、低位字节赋值 |
| leave | 释放当前函数栈帧(等价于 mov esp, ebp; pop ebp),恢复调用者栈环境 | 函数执行结束前清理栈空间(对应 C 函数中局部变量销毁、栈帧恢复,通常紧跟在 return 前) |
| nop | 无操作(仅占用 1 个指令周期,不修改寄存器 / 内存) | 代码字节对齐(编译器填充空白)、调试占位(预留指令位置)、延时(底层简单延时逻辑)、屏蔽无用指令(如破解时 nop 掉校验指令) |
3.2.2 汇编界面核心操作
-
注释添加:选中目标指令,按「;」输入注释内容;
-
关键词搜索:按「Alt+T」输入关键词(如
password,success),定位关键逻辑; -
交叉引用查询:选中函数名(如 sub_401000),按「Alt+F7」,查看调用该函数的指令位置。
3.3 伪代码界面操作要点
伪代码可读性优于汇编,是逆向分析的主力视图。
3.3.1 核心编辑操作
-
重命名(
N):选中默认名称(如 v1、sub_401000),按「N」输入有意义名称(如 input、check); -
类型修改(
Y):选中变量 / 函数,按「Y」修改类型,例如:
-
JNI 函数(so文件分析):将 void __cdecl sub_10001234() 改为 JNIEXPORT jint JNICALL Java_com_example_Test_check(JNIEnv *env, jobject thiz, jstring input),IDA 自动识别 JNI 参数;
-
数组类型:将 int v2 改为 char v2[],明确数组逻辑;
- 注释添加:按「/」输入多行注释,补充逻辑说明(如 “密码验证核心逻辑,返回 1 表示验证通过”)。
3.3.2 伪代码生成失败解决
若提示 “Cannot generate pseudocode”,需修正 IDA 代码识别:
-
切换至汇编视图,定位报错地址;
-
选中误判为 “数据” 的代码段,按「U」(undefine)转为数据;
-
重新选中目标段,按「C」(code)转为代码(按P识别成函数);
-
按「tab」切换回伪代码视图,即可正常生成。
3.4 主函数定位方法(分场景)
场景 1:样本带符号(如调试版 exe/so)
-
一般主界面左侧就是函数列表,如果不存在就按「Shift+F3」打开函数列表;
-
搜索目标函数名:C 语言程序搜 main/start,Windows GUI 程序搜 WinMain,Android so 搜 JNI_OnLoad;
-
双击函数名跳转至主函数。
场景 2:样本无符号(脱壳后 /release 版)
-
按「Shift+F12」打开字符串窗口,筛选关键字符串(如 “success”“right”“flag”);
-
右键选中目标字符串 → 「Jump to xref」(交叉引用 , 快捷键是[ x ]),跳转至引用该字符串的代码;
-
向上追溯代码,找到 “调用次数最少、层级最顶层” 的函数(通常整个程序仅调用一次,且调用其他函数),即为主函数。
场景 3:DLL/so 文件(导出函数)
-
按「Ctrl+I」打开导出表;
-
筛选对外提供的函数(如 DLL 的
ExportFunc、so 的 Java_包名_类名_方法名); -
此类导出函数为外部调用入口,等效于 DLL/so 的 “主函数”。
重点提示:无符号样本优先使用「Shift+F12 找关键字符串」,为最高效的主函数定位方式。
四、LazyIDA 专项速查
LazyIDA 是逆向高频操作的 “加速器”,以下按「快捷键」「右键菜单」「实操工作流」分类,覆盖地址处理、Patch、格式转换等核心场景(组会重点演示功能)。
4.1 快捷键速查表(按视图 / 上下文分类)
| 快捷键 | 动作 | 作用说明 | 适用视图 / 上下文 | 备注 |
|---|---|---|---|---|
| w | Copy EA | 复制当前地址到剪贴板(如:0x401000) | 反汇编视图 / 转储视图 | 输出窗口会打印确认信息 |
| Shift+G | Goto clip EA | 读取剪贴板地址 / 文本,打开 Lazy Jumper 窗口并跳转 | 反汇编视图 / 转储视图 | 支持 “新镜像基址 + 目标地址” 的无重定位跳转,Esc 关闭窗口 |
| Ctrl+Alt+N | NOP them | 对选区批量写 NOP;无选区时对当前指令地址写 1 个 NOP | 反汇编视图 | 输出区打印起止地址与字节数,比基础 Ctrl+N 更灵活 |
| v | Remove return type | 把当前函数 / 调用点的返回类型临时改为 void(再次按可还原) | Hex-Rays 伪代码视图 | 针对 VDI_FUNC / 函数指针调用生效 |
| w(伪代码) | Copy EA (Hex-Rays) | 复制当前伪代码位置关联的 EA | Hex-Rays 伪代码视图 | 与反汇编视图的 w 不冲突,各自生效 |
| c(伪代码) | Copy name | 复制当前高亮名称(变量 / 函数名等) | Hex-Rays 伪代码视图 | 取自 IDA 的 get_highlight () 接口 |
| Shift+G(伪代码) | Goto clip EA (Hex-Rays) | 从剪贴板取地址并在 IDA 中跳转 | Hex-Rays 伪代码视图 | 与反汇编视图的 Shift+G 行为一致 |
小技巧:Lazy Jumper 对话框中,按 Enter/Return 可触发跳转,Esc 关闭窗口,无需鼠标点击(组会实操演示重点)。
4.2 右键菜单功能(按视图分类)
4.2.1 反汇编 / 转储视图(通用右键菜单)
| 菜单项 | 作用说明 | 备注 |
|---|---|---|
| Paste Data | 弹出 “粘贴数据” 窗口,支持 HEX / BASE64 / ASCII 三种输入并写回字节 | 适合快速打补丁,输入后点击 Apply 生效(组会实操演示) |
| Lazy Dumper | 弹出转储窗口:输入基址与大小,保存选定范围字节到文件 | 操作失败会在输出窗口提示原因 |
| Lazy Jumper [Shift + G] | 打开跳转窗口:输入新基址 / 目标地址,计算偏移后跳到当前 IDB 的等效位置 | 自动记忆历史基址,下次打开可快速复用 |
| Copy RVA | 计算并复制当前地址的 RVA(相对虚拟地址) | 调试中按 “模块基址” 计算,非调试按 “imagebase” 计算 |
| NOP them | 与 Ctrl+Alt+N 功能一致:选区批量 NOP / 无选区单字节 NOP | 快速过反调、去花指令(组会实操演示) |
| Get xored data | 选中一段代码→输入 0–255 的 XOR 值,输出异或后的文本 | 仅只读输出结果到输出窗口,不写回二进制文件 |
| Fill with NOPs | 把选区整段填充为 NOP | 必须先框选代码段(单指令不生效),比 NOP them 更彻底 |
| Convert/ → 格式选项 | 把选中字节转为指定格式:- 转义字符串- 十六进制串- C 数组(BYTE/WORD/DWORD/QWORD)- Python 列表(同上) | 转换结果打印到 Output 窗口,需手动复制使用 |
注:Get xored data / Fill with NOPs / Convert/ 仅在 “存在选区” 或 “当前指令大小 > 1” 时显示。
4.2.2 仅反汇编视图(特定架构专属菜单)
| 菜单项 | 作用说明 | 备注 |
|---|---|---|
| Scan format string vulnerabilities | 扫描 printf/sprintf/fprintf 等调用点,若格式串来自可写段则标记为 “可能漏洞”,列表展示可跳转 | 仅支持 x86 (32/64)、ARM32 架构,其他架构不挂载 |
4.2.3 Hex-Rays 伪代码视图(右键菜单)
| 菜单项 | 作用说明 | 备注 |
|---|---|---|
| Remove return type | 与快捷键 v 一致:临时将返回类型改为 void(再次点击还原) | 针对复杂函数指针调用场景优化 |
| Copy ea | 与快捷键 w(伪代码)一致:复制当前伪代码关联的 EA | 快速定位伪代码对应的汇编地址 |
| Copy name | 与快捷键 c(伪代码)一致:复制高亮的变量 / 函数名 | 避免手动输入名称,减少拼写错误 |
| Goto clipboard ea | 与快捷键 Shift+G(伪代码)一致:从剪贴板跳转地址 | 伪代码中快速跳转到外部引用的地址 |
| 双击增强:obj->func () 表达式 | 自动解析并跳转到目标 func;若找不到则尝试 Class::func | 命中时直接跳转,未命中无响应(需手动定位) |
4.3 常见工作流
- 一键 NOP 某段代码:
框选目标代码段 → 右键点击「Fill with NOPs」(或按 Ctrl+Alt+N)→ 输出窗口确认 NOP 范围。
(适用场景:批量去除花指令、跳过反调试检测代码)
- 快速打补丁:
定位需修改的地址 → 右键「Paste Data」→ 选择输入格式(如 HEX)→ 粘贴修改后的字节(如将 74 改为 75,je 改 jne)→ 点击「Apply」→ 保存 Patch 后的文件。
(适用场景:修改跳转条件、绕过验证逻辑)
- 跨镜像基址跳转:
复制外部样本的目标地址(如 0x10001234)→ 在当前 IDA 按 Shift+G → Lazy Jumper 窗口输入 “新镜像基址(如 0x10000000)” 和 “目标地址(0x10001234)”→ 按 Enter 跳转。
(适用场景:对比多个样本、分析脱壳后代码)
五、Patch/Keypatch(修改程序逻辑)
Patch 即 “二进制打补丁”,通过修改代码实现逻辑调整(如过反调、跳过验证),依赖 IDA+LazyIDA+Keypatch 组合(LazyIDA 负责高效操作,Keypatch 负责指令汇编)。
5.1 Keypatch 插件安装
将 Keypatch 插件文件复制至 IDA 的 plugins 目录,重启 IDA 即可(与 IDA 9.1/9.2 兼容),功能为 “可视化修改汇编指令,无需手动计算机器码”。
5.2 核心 Patch 操作(分场景)
场景 1:NOP 掉无用代码(基础版)
(LazyIDA 已实现进阶批量 NOP,见第四章 4.3 工作流)
-
适用场景:单指令 NOP、简单逻辑跳过;
-
操作步骤:选中指令 → 按 Ctrl+N(LazyIDA 基础功能)→ 指令替换为 NOP。
场景 2:修改指令(调整跳转条件)
-
适用场景:强制跳转至正确逻辑(如跳过验证失败分支);
-
操作步骤:
-
在汇编视图选中待修改指令(如 je 0x401200);
-
右键 → 「Keypatch」→ 输入新指令(如 jmp 0x401300);
-
点击「Assemble」→ 「Patch file」→ 保存修改后的二进制文件。
场景 3:恢复误判代码(U+C 组合)
-
适用场景:IDA 将正常代码误判为数据(显示为十六进制);
-
操作步骤:
-
选中误判数据段,按「U」(undefine)转为数据;
-
重新选中目标段,按「C」(code)转为代码;
-
按「tab」切换回伪代码视图,恢复解析。
5.3 地址跳转操作(G 快捷键 + Lazy Jumper)
-
基础跳转:已知地址(如 0x401100)→ 按「G」→ 输入地址跳转;
-
进阶跳转:跨基址 / 剪贴板地址 → 按 W和Shift+G(Lazy Jumper)组合→ 输入参数跳转(见第四章 4.1)。
六、动态调试:跟踪程序运行
动态调试通过运行程序、下断点,观察变量值与函数调用,精准定位核心逻辑。
6.1 核心概念
-
断点:程序运行至指定位置暂停,便于观察;
-
单步执行:暂停后逐行执行代码,跟踪变量变化;
-
本地调试:在本地设备运行并调试程序;
-
远程调试:调试其他设备(如 Linux 服务器、Android 手机)上的程序。
6.2 断点类型对比(分场景选用)
| 断点类型 | 操作方式 | 原理 | 优点 | 缺点 |
|---|---|---|---|---|
| 软件断点 | 选中指令按「F2」 | 将指令替换为 int3(调试中断指令) | 操作简单、无数量限制 | 易被程序检测(int3 为特征) |
| 硬件断点 | 右键断点 → 「Edit breakpoint」→ 勾选「Hardware breakpoint」 | 利用 CPU 调试寄存器实现 | 隐蔽性强、不易被检测 | 数量有限(通常最多 4 个) |
重点提示:新手优先使用软件断点(F2),遇到反调试检测时切换为硬件断点(组会实操演示)。
6.3 Windows 平台调试(exe/dll)
6.3.1 本地调试步骤
-
IDA 加载 exe 文件,等待分析完成;
-
按「F9」(Start process)启动程序(无断点时程序会直接运行结束);
-
在关键位置(如验证函数、字符串引用处)按「F2」下断点,程序运行至断点处自动暂停;
-
单步操作:
-
F7:单步步入(进入函数内部,如 call 指令时跟进);
-
F8:单步步过(不进入函数,直接执行完 call 指令);
-
F9:运行至当前函数结束(快速跳出函数);
- 变量观察:暂停时,鼠标悬停伪代码中的变量(如 input_pass),显示当前变量值(组会实操演示重点)。
6.3.2 附加进程调试(调试已运行程序)
-
确保目标程序已在本地运行;
-
IDA 菜单栏 → 「Debugger」→ 「Attach」→ 「Local Process」;
-
在进程列表中筛选目标程序(按名称检索),选中后点击「Attach」;
-
按「F2」下断点,按「F9」恢复程序运行,触发断点后按单步操作调试。
6.4 Linux 平台调试(elf/so)
6.4.1 远程调试步骤(如调试服务器 / 手机 so)
-
从本地 IDA 安装目录获取调试服务器:
- 本地路径:
E:\IDA\IDA Professional 9.2\dbgsrv - 选择对应版本:32 位样本用
linux_server,64 位样本用linux_server(Android 对应android_server/android_server32);
- 本地路径:
-
将调试服务器文件复制到目标机的「调试样本同目录」;
-
给调试服务器和样本文件加权(赋予执行权限):
1 2chmod 777 linux_server # 调试服务器加权 chmod 777 test.exe # 待调试样本加权 -
启动调试服务器:
-
方式 1:使用自定义端口(推荐,避免端口冲突)
1./linux_server -p 1234 # 1234 为自定义端口,可替换为 1024-65535 之间未占用端口 -
方式 2:使用默认端口(无需指定 -p,默认端口为 23946)
1./linux_server
-
-
启动成功后,终端会显示 “Listening on port XXXX”(XXXX 为端口号),保持终端窗口开启。
6.5 调试避坑指南
-
调试前关闭杀毒软件 / 防火墙,避免程序被拦截;
-
Windows 调试 GUI 程序时,断点不设置在 UI 初始化函数(易导致程序卡住);
-
远程调试需确保目标机与本地机网络互通(如同一局域网,关闭防火墙);
-
Android so 调试:先将 so 文件从手机拉取至本地,IDA 加载后远程附加手机进程。这里可以参考