资讯动态

IDA Pro逆向入门:PE装载、F5伪代码与异或校验解密

发布时间:2026/10/27 14:00:21 来源:尧图企业网站定制
简介这是一份面向网络安全初学者与逆向工程入门者的IDA Pro实操笔记围绕反汇编工具初识、PE文件分析与EXE逆向解密展开适合刚接触静态分析、希望补齐工具使用与调试思路的读者。整包仅含1个PDF文件大小约5.5MB内容以图文步骤、界面截图和案例笔记为主便于按章节阅读与对照练习。资料从IDA Pro简介、新建工程和PE文件装载讲起逐步说明IDA View、Hex View、Strings窗口及文件类型识别等基础用法并延伸到代码加密与逆向解密的实战流程文中还结合CrackMe与一元一次方程等案例帮助读者理解函数、字符串和跳转关系积累从工具操作到案例复盘的完整路径同时保留案例背景、操作提示与关键窗口说明方便读者在本地复现时对照查看。已有1661人学习下载可作为入门工具书和查漏补缺的参考材料。1. 手上只有一个 exe 时IDA Pro 能给你什么一个只有 exe、没有源码的小程序跑起来弹一句please input the key:输错就退出。你想知道它到底在比什么键盘上能按的东西其实不多——IDA Pro就是其中一个。它是交互式反汇编器把编译好的二进制按段拆开、还原成汇编视图光标停在函数体里按 F5还能拿到一份接近 C 的伪代码。这份材料围绕一套网络安全自学笔记里的 IDA Pro 入门部分展开从装载 PE 文件、认窗口到一个异或校验小程序的完整逆向工程解密过程密钥串和明文口令都能落回纸面。适合刚接触逆向、看得懂基本 C 语法但没碰过反汇编的人已经能顺畅读汇编的可以直接跳到第 4、5 章看伪代码与源码对不上的那部分排错。2. 装好就能用IDA Pro 装载 PE 文件的完整流程装完 IDA目录里会躺着一堆文件新手最常问的第一个问题是我该点哪个。答案取决于目标程序的位数而位数这件事光看文件名是猜不准的——test01.exe这种名字既不告诉你编译器也不告诉你架构。下面把从判断位数到存下数据库的完整链路走一遍每一步都给出判断依据避免开了半天发现反汇编区一片db字节。2.1 32 位与 64 位版本的选型判断安装完成后会有两个可执行入口分别对应 32 位和 64 位程序的分析。选错的后果很直接IDA 会把代码段识别成一堆无法解析的字节或者弹窗提示处理器类型与文件不匹配你得从头再来一遍。判断目标 PE 的架构靠文件名不行靠资源管理器里应用程序这个类型也不行可靠的入口是 PE 头里的 Machine 字段。PE 文件开头是MZ偏移0x3C处是一个 4 字节小端整数指向真正的 PE 签名PE\0\0签名后紧跟 2 字节就是 Machine。写个十几行的脚本比反复试错省事import struct def detect_pe_arch(path): with open(path, rb) as f: data f.read(0x400) # 头部 1KB 足够读完 DOS 头 PE 头 if data[:2] ! bMZ: return not a PE file e_lfanew struct.unpack_from(I, data, 0x3C)[0] # PE 头偏移 if data[e_lfanew:e_lfanew 4] ! bPE\x00\x00: return invalid PE signature machine struct.unpack_from(H, data, e_lfanew 4)[0] table {0x014C: x86 (32-bit), 0x8664: x64 (64-bit)} return table.get(machine, unknown machine: 0x%04X % machine) print(detect_pe_arch(test01.exe))这段代码的关键参数只有三个0x3C是 DOS 头里e_lfanew的固定偏移I表示 4 字节小端无符号整数e_lfanew 4是 Machine 相对 PE 签名起点的位置。0x014C对应 I3860x8664对应 AMD64。输出x86 (32-bit)就开 32 位入口输出x64 (64-bit)就开 64 位入口。还有个更土但同样有效的办法直接把文件拖进 IDA装载窗口的文件类型下拉框会显示Portable executable for 80386 (PE)还是Portable executable for AMD64 (PE)选错了它也会给提示只是不如脚本一眼看清来得干脆。2.2 新建工程与 Load a new file 的选项第一次打开 IDA 会弹一个 Support message 窗口点 OK 就行勾上不再提示之后就不会再打扰。接着是三个入口New打开标准文件对话框新建分析GO进入一个空白工作区适合先开界面再把文件拖进去Previous列出最近打开过的文件。日常用New最省事选中 exe 后进入装载确认环节。装载窗口里要做的事只有一件确认文件类型。绝大多数 Windows 程序选Portable executable for 80386 (PE)如果是纯二进制固件、ShellCode 片段、被抽出来的某一段内存镜像则要选Binary file并手动指定处理器类型和装载基址。选完点 OK随后可能还有几组选项框我一般的处理是全默认理由逐条说清Loading segment/Loading offset只在 Binary file 模式下有意义用来告诉 IDA 这段数据原本该放在哪个地址。PE 文件自带节表这两项保持默认即可。Kernel options 1/2内存布局与栈相关的参数默认值与真实加载行为一致改动只会让你看到的地址对不上调试器。Processor options处理器细节开关除非要分析非标准指令集否则不碰。Analysis里的几个复选框创建导入段、重命名 DLL 项、最终分析扫描、追加签名注释默认全勾。Manual load只在需要控制装载基址、或者文件大到想分段加载时才勾否则会打断自动分析。装载完成后IDA 做的是两件事。第一阶段把代码和数据分开用递归下降加线性扫描的方式标记函数边界、分析参数调用与跳转关系第二阶段识别编译器类型装载对应的特征文件给运行时库函数自动赋名——你看到的printf、strcmp能被认出来就是这一步的功劳。分析结束可能还会弹一个组件更新提示点 OK 放过即可。2.3 数据库文件的四个组成部分与保存习惯分析过程中产生的所有工作状态都写进数据库反汇编结果、区段扫描结论、用户自定义结构体、你改过的函数名、你敲进去的注释。这些东西不是保存在 exe 里的exe 从头到尾只读。数据库在磁盘上由四个文件构成理解它们各自存什么排查改了名字重开就没了这类问题时能省很多时间文件内容丢失后果.id0二叉树形式的节点数据库函数树、名字索引全部重建自定义命名丢失.id1程序字节与指令快照需重新装载原始 exe 并重跑分析.namNames 窗口的索引信息命名列表需要重新扫描.til本地类型定义信息自定义结构体、枚举需要重做关闭时 IDA 会弹保存数据库窗口用默认选项点 OK得到一个打包好的数据库文件。下次可以直接加载它恢复上次的分析现场不用再等一遍自动分析。这里有个血泪经验改结构体、批量重命名之前先 File → Save as 另存一份因为数据库是二进制快照误操作没法撤销回几步之前。另外数据库和原始 exe 最好放同一目录路径变了 IDA 仍能打开数据库但引用原始字节的地方会提示找不到文件。3. 四个窗口读懂一个 EXEIDA View、Hex View、Strings 与函数窗口主界面打开后一片密密麻麻容易劝退。实际只需要先吃透四个窗口IDA View 看代码Hex View 看字节Strings 找入口函数/导入导出窗口做范围收敛。剩下的结构体、枚举、段窗口属于按需打开的类型不熟不影响起步。3.1 IDA View 的三区结构IDA View 是主工作区横向分成三块。地址区显示的是 PE 加载到内存后的虚拟地址也就是镜像基址加偏移常见程序从0x00401000起步这个地址和调试器里看到的一致所以后续下断点、算偏移都能对上。OpCode 操作区默认是隐藏的因为很多人嫌它占地方——这也是新手第一个迷惑点明明在教程里看到指令左边有一串十六进制机器码自己这儿没有。打开方式是Options → General把Number of opcode bytes从 0 改成 8或 16确定后立刻出现。这串字节在对照补丁、核对指令编码时是刚需建议一开始就打开。反汇编代码区是主战场双击函数名或变量名跳转到定义处选中符号按 N 可以重命名光标处按分号能加注释。视图本身有两种形态Text View 是线性反汇编列表Graph View 把函数画成基本块流程图。按空格键在两者之间切换右键菜单里也有对应项。Graph View 的价值在于条件分支一眼可见——绿色箭头代表条件成立时走的分支红色箭头代表不成立时走的分支左侧还有整个函数的缩略图函数长了直接在缩略图上拖动定位。分析校验逻辑时我基本全程待在 Graph View只有核对具体指令编码才切回 Text View。3.2 Strings 窗口是最快的入口一个程序里所有能被识别为字符串的常量都在 Strings 窗口里列着。打开方式是按 ShiftF12也可以从View → Open subviews → Strings进入。窗口有四列地址、长度、类型、字符串内容。它的用法是从程序的输出反推代码的位置——程序打印了please input the key:那这个字符串一定在某个地方被引用找到引用就找到了调用点。操作链路是这样的在 Strings 窗口里定位到目标字符串双击跳到它在数据段里的地址此时光标停在字符串上按 X 打开交叉引用列表里面每一条都是哪条指令引用了这个地址选中一条回车就跳到代码段里对应的位置。如果这条引用出现在某个函数内部往上翻到函数头就是你要分析的函数。这套动作在几乎所有带提示信息的程序上都管用比从入口点一路跟进去快得多。Strings 窗口也有明确的边界被加密或编码过的字符串不会显示成可读文本拼接出来的字符串比如分成两段常量在运行时strcat只能看到碎片宽字符字符串需要先在 Strings 窗口的右键设置里勾上对应的编码类型才会被识别。所以Strings 里搜不到不等于程序里没有这个字符串这时候要回到导入表从strcmp、MessageBoxA这类 API 的引用处反向找。3.3 Hex View 的只读切换与交叉验证Hex View 按十六进制显示数据默认是只读状态。光标停在某个区域按 F2可以在只读和编辑之间切换进入编辑态后对应字节会以绿色标出改完再按 F2 退出。它最常见的用途不是改数据而是交叉验证反汇编区里显示某个常量或某段字符串时切到 Hex View 能看到它的原始字节确认没有被显示层做过任何转换。配合右键 → Sync with → IDA View可以让两个窗口的光标联动点到哪跟到哪。这个联动在看结构体、看栈上局部变量的初始值时特别顺手——伪代码告诉你Str1是个 20 字节的缓冲区Hex View 告诉你在文件里它初始全是 0。要注意的是在 Hex View 里做的编辑只存在于数据库里不会写回 exe确实要落地得走Edit → Patch program → Apply patches to input file这一步会改原始文件动手前先备份。3.4 导航类窗口把分析范围收窄除了上面三个还有一组窗口负责划定范围用熟了能省掉大量瞎翻的时间窗口打开方式典型用途ExportsView → Open subviews → Exports看导出函数判断这是个 DLL 还是带导出表的 EXEImports同上 → Imports列出导入的 APIstrcmp、scanf一出现就知道有输入校验Names同上 → Names全局符号列表搜索已知函数名最快Functions同上 → Functions所有识别出的函数、起始地址、长度按长度排序能快速找到主逻辑Structures同上 → Structures展开数据结构看字段布局分析网络包、文件格式时必用Enums同上 → Enums枚举常量API 参数里的魔数常在这里有名字Segments同上 → Segments段表确认代码段、数据段、资源段的边界对这个小程序来说最有价值的是 Imports 和 Functions 两个。Imports 里出现strcmp和scanf基本可以断定读输入 → 变换 → 比较这个骨架Functions 按长度排序排在前面的往往就是main。IDA 也支持脚本扩展plugins目录下放 Python 脚本就能在菜单里多出命令批量导出函数列表、批量重命名这类重复劳动交给脚本比手点快。4. 逆向解密实战从 F5 伪代码到还原出明文口令前面都是准备工作这里开始动真格。目标程序是自己写、自己编译的练习程序逻辑是一段最朴素的异或校验读入一个口令逐字节与内置密钥异或结果与一个固定字符串比较相等就打印成功。把它当成黑匣子只用 IDA 去把密钥和明文口令挖出来。4.1 目标程序的加密逻辑源码不复杂但里面藏着几个后面会反复用到的约束条件#include stdio.h #include string.h int main(void) { int i, len; char key[20] {0}; char res[20] {0}; /* 必须清零否则 strcmp 会读到栈残留 */ const char *secret keysecret; /* 异或密钥9 字节 */ const char *right 123456789; /* 期望的密文结果9 字节 */ printf(please input the key:); scanf(%19s, key); len (int)strlen(key); if (len 6 || len 10) { /* 长度闸门 1越界直接报错 */ printf(Error, The length of the key is 6~10\n); return 0; } for (i 0; i len; i) { res[i] (char)(key[i] ^ secret[i]); /* 逐字节异或长度由输入决定 */ } if (strcmp(res, right) 0) { /* 长度闸门 2隐式要求 res 正好 9 字节 */ printf(You are right, Success.\n); } else { printf(Error, please input the right key.\n); } return 0; }三个约束串起来看输入长度必须在 6 到 10 之间异或循环的上界是输入长度不是密钥长度strcmp要和 9 字节的123456789完全相等。第二条和第三条叠加意味着只有输入正好 9 字节时异或结果才可能凑齐 9 个字节去比较——输入 6 字节res只有前 6 字节被赋值、后面是 0strcmp必然在长度上先失败。这个推论不是从源码看出来的是从后面伪代码的结构看出来的也正是逆向最有意思的地方源码告诉你它是这么写的二进制告诉你编译器把它变成了什么样。4.2 F5 反编译后读到的三个关键点把编译好的 exe 拖进 IDA走完装载流程先按 ShiftF12 打开 Strings 窗口找到please input the key:双击落到数据段后按 X 看交叉引用回车跳到代码段里的引用点往上翻到函数头光标停在函数体内按 F5。得到的伪代码大致长这样int __cdecl main(int argc, const char **argv, const char **envp) { char Str[40]; // [esp10h] [ebp-30h] BYREF char Str1[32]; // [esp24h] [ebp-1Ch] BYREF int v5; // [esp38h] [ebp-8h] int i; // [esp3Ch] [ebp-4h] __main(); printf(please input the key:); scanf(%19s, Str); v5 strlen(Str); if ( v5 5 v5 10 ) { for ( i 0; i v5; i ) Str1[i] gcc2_compiled_[i] ^ Str[i]; /* 密钥在数据段稍后定位 */ if ( !strcmp(Str1, 123456789) ) printf(You are right, Success.\n); else printf(Error, please input the right key.\n); } else { printf(Error, The length of the key is 6~10\n); } return 0; }这段伪代码里有三个点必须停下来看清楚。第一长度判断被编译器改了形。源码写的是len 6 || len 10时报错伪代码变成v5 5 v5 10才走正常分支——两个比较被合并成一次范围判断逻辑完全等价但字面顺序反过来了。新手在这里读反、把正常路径当成报错路径后面全盘皆输。第二循环上界是v5也就是输入长度不是常量 9这印证了前面必须正好 9 个字节的推论。第三比较发生在本地strcmp的两个参数一个是栈上缓冲区Str1一个是只读数据段里的常量字符串中间没有任何网络调用或外部输入说明这是个纯离线校验可以放心在本地把答案算出来。4.3 定位密钥变量 gcc2_compiled_ 的两种方式伪代码里那个gcc2_compiled_[i]就是密钥名字本身是个误会——IDA 在识别编译器特征时把某个数据符号映射到了这类名字上它不代表这段数据真和编译器有关。名字不可信地址可信所以定位要绕开名字看内容。第一种方式最简单在伪代码里把光标放到gcc2_compiled_上双击IDA 直接跳到它在数据段的定义处此时切到 Hex View能看到连续的 9 个字节6B 65 79 73 65 63 72 65 74右边 ASCII 一栏写的就是keysecret。第二种方式从字符串入口走ShiftF12 搜123456789双击后按 X 找引用回到strcmp那一行再往上找异或指令两个操作数里一个是栈地址输入另一个就是密钥的绝对地址按 G 输入这个地址直达。方式操作路径适合场景局限双击变量名伪代码里双击 → 数据段 → Hex View伪代码已识别出符号符号被识别错名字时容易看漏Strings 反查ShiftF12 → X → 回到比较点 → 找异或指令字符串可读、逻辑集中字符串被加密或拼接时失效直接看反汇编Text View 里找xor指令及其操作数伪代码不可用、无 F5需要能读基本指令三种方式互相交叉验证比只信伪代码稳。我在这个例子上就是先用双击法看到keysecret再用 Strings 法从123456789反着走一遍两条路指向同一个地址才确认没有看错。4.4 写一段解密脚本把口令算回来现在手里有两个常量期望结果123456789和密钥keysecret算法是逐字节异或。异或的自反性决定了还原公式和加密公式长得一样——把比较结果再异或一次密钥就是正确输入# 两个常量都来自 IDA 的数据段字节序按原样抄 target b123456789 # strcmp 的期望值 secret bkeysecret # gcc2_compiled_ 指向的 9 字节密钥 # zip 按最短长度截断两串必须等长bytes 迭代得到 int正好直接异或 plain bytes(t ^ s for t, s in zip(target, secret)) print(plain) # bZWJGPUEM print(plain.decode(ascii)) # 回代验证模拟一遍程序内部的判断 def check(inp: bytes) - bool: if not (6 len(inp) 10): return False res bytes(a ^ b for a, b in zip(inp, secret)) return res target print(check(plain)) # True几个参数说明清楚zip按两者中较短的长度截断所以这里要求target和secret都恰好 9 字节少一个字节就算不出来bytes迭代时每个元素是 0 到 255 的整数与 C 里对char做异或是同一件事不用额外处理符号位plain出来是bZWJGPUEM九个字符全在大写字母范围内可以直接敲进程序的控制台。把ZWJGPUEM输进去屏幕上会打印You are right, Success.——从只有一个 exe到拿到明文口令中间没有任何猜测成分。5. 避坑与排查五个让新手卡半天的细节逆向这件事卡住你的往往不是算法看不懂而是环境、视图、优化这三类问题。下面五条都是我在这个练习程序上真实撞过的按看到什么现象、为什么、怎么解决写遇到对得上的直接照做。5.1 装载后满屏 db 字节或者提示无法识别现象文件拖进去反汇编区不是push ebp/mov ebp, esp这类正常指令而是一排排db 0FFh、db 8Bh或者干脆弹窗说文件类型无法识别。原因位数选错是最常见的一类用 64 位入口打开 32 位程序IDA 会按 64 位指令长度去解码字节流从第一条指令就错位其次是文件类型选错把带 PE 头的 exe 当Binary file装载没有节表信息代码和数据无法区分。解决先用 2.1 的脚本确认 Machine 字段再换对应位数的入口重开已确认位数还出问题就在装载窗口手动把类型指定为Portable executable for 80386 (PE)或 AMD64 对应项。已经建好的数据库不要硬救直接关掉删库重来比重头修快得多。5.2 反汇编区没有机器码字节现象教程截图上每条指令左边都有一串十六进制自己这儿干干净净对不上补丁也核不了指令编码。原因Options → General里的Number of opcode bytes默认值是 0也就是不显示。解决改成 8 或 16确定后立刻出现。这个设置是全局的改一次以后开着都生效不用每个文件重设。5.3 按 F5 没反应或者提示反编译失败现象光标放在函数里按 F5界面没变化或者弹出Decompilation failure提示地址处不是函数。原因三种情况分开看。一是反编译器组件没装好或没授权菜单里Generate pseudocode是灰的二是光标不在函数体内停在数据段或段头F5 当然没对象三是这段代码没被识别成函数导入表里的库函数、被跳转表切碎的代码块都可能这样。解决先在反汇编区确认光标落在某个sub_xxxxxx或命名函数的范围内不是函数就在函数头按 P 手动定义让 IDA 重新按函数边界分析确认函数存在但 F5 仍失败退出重开数据库、换一个入口点试能复现就说明是反编译器对这个函数的识别有问题退回去读汇编。5.4 伪代码和源码对不上怀疑自己看错了程序现象明明源码写的是len 6 || len 10报错伪代码变成v5 5 v5 10才执行源码里清清楚楚的循环伪代码里被拆成两段变量名全变成v5、v6、Str1。原因编译器优化。条件合并、循环展开、常量传播、寄存器复用都会改变代码形态伪代码是反编译推测出的等价 C不是原始源码的还原。变量名丢失是因为发布版 exe 通常不带调试符号。解决把伪代码当意图描述读不当字面源码读关键分支一定要回到 Text View 看汇编确认跳转条件怀疑优化干扰判断时重新编一份关掉优化的版本对照两者一比哪些是优化引入的、哪些是原始逻辑立刻分明。5.5 算出来的口令输进去还是失败现象脚本算出的字符串看起来没问题粘进程序还是报Error, please input the right key.。原因四种常见情况。一是长度不匹配异或循环按输入长度走少一位就少异或一个字节strcmp必然不等二是结果里混进了不可打印字节小于 0x20 或大于本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑