免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

逆向工程第一课:静态分析入门与实用工作流

逆向工程第一课:静态分析入门与实用工作流 拿到一个二进制文件没有源代码没有文档甚至看不到它到底做了什么——你都不知道该从哪里下手。很多第一次接触逆向的人最先遇到的不是“看不懂汇编”而是根本不知道第一眼应该看什么是先打开十六进制编辑器还是直接拖进调试器还是先跑起来看看这篇文章要回答的就是这个问题。我给出的判断很明确静态分析是二进制逆向的第一课也是门槛最低、回报最稳定的一步。它不需要程序真正运行不依赖调试环境不要求你立刻读懂整段汇编你只需要按一套固定流程先回答“这个文件是什么”“它里面有哪些线索”“哪些代码值得重点看”就能为后续动态调试打下基础。本文会从二进制文件的基本概念讲起说明为什么先学静态分析而不是动态分析然后给你一套可以直接复用的命令行分析工作流再用一个自行编译的最小案例走完整条链路。题材覆盖软件安全、游戏安全、CTF 比赛和封包技术但难度控制在入门级别。建议你先会一点 C 语言并习惯在 Linux 或 WSL 终端里操作。1. 为什么第一个要学“二进制”而不是先学工具在 CSDN 和各类技术群里经常有人问“逆向该用什么工具”。答案通常是 IDA、Ghidra、x64dbg、OllyDbg。但工具只是放大器真正决定你入门速度的是你对二进制文件本身的认知。我们日常写的 C、C 代码经过编译、链接之后就变成了一串机器指令和数据的组合。它不再是你能直接阅读的源代码而是一个结构化容器有文件头有代码段有数据段有重定位信息也可能有符号表和字符串表。逆向分析的本质就是把这个容器一层层打开把里面的机器指令还原成人类可读的汇编语言再把汇编语言整理成接近源代码的逻辑。为什么要从零开始看二进制因为真实世界里的分析对象几乎都不是你熟悉的开源项目CTF 逆向题你拿到的就是一个编译好的可执行文件要找到验证逻辑算出 flag。软件安全分析闭源软件出问题没有源码可查只能从二进制里找线索。恶意代码分析一个来路不明的样本不能轻易运行最稳妥的办法是先静态分析。游戏安全外挂、修改器、反作弊对抗背后核心能力之一就是读懂游戏进程和模块的二进制逻辑。封包技术客户端与服务端通信之前程序会先把数据进行编码、加密或签名这些处理函数的入口同样藏在二进制代码里。所以工具可以后面慢慢学但对“可执行文件里到底有什么”的理解必须一开始就建立。否则你会陷入一种很尴尬的状态界面操作很熟练但换个题目就完全找不到思路。2. 可执行文件里的“二进制”到底长什么样2.1 机器码、汇编与高级语言先分清三层关系。高级语言写的是if (strcmp(input, 123456) 0)普通人能看懂。编译后变成机器码是 CPU 真正执行的0x48 0x83 0x3D这类字节。机器码很难直接读所以逆向工具把它翻译成汇编语言比如cmp dword ptr [rip0x2c1f], 0 je 0x401160汇编语言本身也很啰嗦所以 Ghidra、IDA 这样的工具又会进一步给出伪代码让你读起来像 C 语言。但你要记住一件事机器码是唯一的真实存在汇编是翻译伪代码是翻译之后再转述。分析时可以先从伪代码入手提效率但下结论之前一定要回汇编里核实。2.2 PE、ELF、Mach-O 三种常见容器不同的操作系统使用不同的可执行文件格式。最常见的有三种格式全称主要使用平台常见扩展名PEPortable ExecutableWindows.exe, .dll, .sysELFExecutable and Linkable FormatLinux、Android无固定扩展名Mach-OMach ObjectmacOS、iOS无固定扩展名这三种格式的底层细节不同但大思路一致文件开头有一个文件头说明文件类型、目标架构、入口点后面跟若干节区分别存放代码和数据。文件头里有一个很直观的东西叫“魔数”。PE 文件以MZ开头ELF 文件开头是0x7F 45 4C 46也就是\x7fELFMach-O 的魔数通常是0xFEEDFACE或0xFEEDFACF。不要小看这几个字节它们决定了你后续所有分析工具的选择。2.3 节区、符号表与字符串表打开一个可执行文件的内部结构通常会看到这些概念.text存放代码。.rodata存放只读数据比如字符串常量。.data存放已初始化的全局变量。.bss存放未初始化的全局变量。.dynsym/.symtab动态符号表、符号表。.strtab字符串表。.dynamic动态链接时需要的信息。对入门者来说最有价值的是字符串表。程序如果直接使用明文常量比如提示信息、URL、密钥片段都会保留在字符串表里。很多 CTF 简单题甚至不用看汇编strings一下就能看到关键字符串。但字符串只是线索入口不是最终答案。真正复杂的情况是程序把关键字符串拆开、加密、动态拼接或者通过网络协议下发这时字符串表就看不到有价值的内容了。3. 为什么静态分析是逆向的第一课3.1 静态分析与动态分析的核心区别静态分析是指不运行程序只通过文件内容、结构、代码反汇编来理解程序动态分析则是在调试器、沙箱或虚拟机中真正把程序跑起来观察它的执行流程、内存变化和系统调用。它们很像两种不同的学习方式。静态分析像“解剖”你可以慢慢看每一段结构动态分析像“观察实验”你得给程序输入、让它运行看它如何反应。维度静态分析动态分析是否运行程序否是对不可信样本的风险相对较低较高可能触发恶意行为可重复性同一文件结果确定依赖参数、环境、网络状态学习曲线主流程较平缓需要调试器、断点、寄存器和堆栈知识核心目标建立整体结构和代码地图验证关键路径和运行时数据常用工具file、strings、readelf、objdump、Ghidra、IDAgdb、x64dbg、OllyDbg、WinDbg、沙箱动态分析当然重要而且到了中后期是必须的。但入门阶段如果直接从调试器开始很容易陷入“单步执行了半天不知道自己在追什么”的状态。静态分析可以让你先知道程序里有哪些可疑函数再带着目标去动态验证。3.2 静态分析能解决哪些问题静态分析至少能回答这几个问题这个文件是什么平台的程序32 位还是 64 位有没有加壳程序里有没有直接可见的提示字符串、密钥片段、路径信息导出函数、导入函数有哪些它依赖哪些系统功能关键函数在哪一段地址调用关系是什么某个算法是手写的还是编译器生成的有没有可能被混淆这些问题一旦在静态阶段解决进入动态调试时你会非常清楚自己该在哪下断点该观察哪个寄存器和内存区域。这也是为什么说“静态分析是逆向第一课”。4. 环境准备一套低成本可复现的分析环境4.1 安装命令行工具本文演示使用 Linux 环境。如果你使用 Windows建议安装 WSL2再在 Ubuntu/Debian 终端里执行命令。打开终端先安装基础工具sudo apt update sudo apt install -y binutils file xxd gdb这些工具分别做什么file识别文件类型。xxd十六进制查看器也可以直接查看头部字节。readelf读取 ELF 文件头、节区、动态信息。objdump反汇编工具配合 binutils 一起安装。gdb先装好后面动态分析课程会用。4.2 安装图形化逆向工具命令行工具适合快速定位但要真正读代码推荐安装开源免费的 Ghidra。Ghidra 由美国国家安全局开源支持导入 PE、ELF、Mach-O并提供反编译器能把汇编转成伪代码。在 Ubuntu 上安装 Ghidrasudo apt install -y openjdk-17-jdk unzip wget cd /opt sudo wget https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_11.1.2_build/ghidra_11.1.2_PUBLIC_20240520.zip sudo unzip ghidra_11.1.2_PUBLIC_20240520.zip注意具体版本号请以 Ghidra 官方仓库发布为准上面链接仅演示安装思路。如果你不想从源码仓库下载也可以直接访问 Ghidra 官网下载安装包。4.3 工具初步验证安装完成后先验证一下命令是否可用file --version xxd --version readelf --version gdb --version如果每一条都正常输出版本信息说明环境已经就绪。Ghidra 是图形界面工具首次启动会显示一个项目窗口后面第 6 节再演示具体导入流程。5. 一套通用的静态分析工作流不管你拿到的是什么二进制文件都建议按下面的顺序走一遍。这套流程不保证一定能破解所有程序但它能帮你快速建立起“程序地图”。5.1 第一步识别文件类型先不急着打开十六进制编辑器最简单的动作是使用filefile demo_binary典型输出demo_binary: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]...从这段输出里你可以得到几个关键信息这是 64 位 ELF 程序动态链接使用 x86-64 架构。如果是 Windows 的 PE 文件输出可能是sample.exe: PE32 executable (GUI) x86-64, for MS Windows这一步决定你后面用哪个工具集。不要把 Windows 的 PE 文件塞给readelf也不要指望 Linux 的objdump直接分析 Mach-O。5.2 第二步抓取字符串线索用strings查看文件中的可打印字符串strings -n 8 demo_binary | head -n 40-n 8表示只输出长度不小于 8 的字符串避免太碎的干扰项。head是防止输出太长刷屏。在新手练习阶段这一步通常会直接暴露关键提示。比如看到Usage: ./crackme key、Correct!、Wrong.你基本就能猜到程序是一个密码校验程序。但注意strings的局限性如果程序把字符串做了加密、编码或运行时拼接这个命令就看不到有效信息。同时它输出里可能混入大量动态链接库的字符串比如libc.so.6、__libc_start_main这些不是你分析的重点。5.3 第三步读取文件头与节区信息使用readelf查看 ELF 文件头readelf -h demo_binary重点关注Class是 32 位还是 64 位。Machine目标架构如Advanced Micro Devices X86-64。Entry point address程序入口地址通常是0x4010xx或0x5xx。Number of section headers节区数量。接着看节区readelf -S demo_binary你会看到一个表格列出各个节区的名称、类型、地址和大小。如果程序被加壳或混淆节区名称往往不标准或者某个节区大小异常地大这些都是需要警觉的信号。查看符号表readelf -s demo_binary | grep -i main编译时如果保留符号表你能直接看到main、printf、strcmp等函数符号。很多真实程序会在发布时去掉符号表也就是 Stripped 状态输出里可能没有main只有_start和动态导入符号。5.4 第四步反汇编与反编译命令行下最直接的反汇编工具是objdumpobjdump -d -M intel demo_binary | less-d表示反汇编可执行代码段-M intel让汇编输出使用 Intel 语法。如果你的 CPU 架构不是 x86命令可能需要调整。打开后先找入口和关键函数。对于保留符号的程序直接找main对于被 Stripped 的程序从入口_start往下翻通常能发现__libc_start_main的调用它前面压栈的参数就是main函数的地址。这条流程走完你已经从“一堆看不懂的十六进制字节”变成了“一份带有函数边界和头部地址的汇编代码”。下一步就是针对重点函数精读这时再打开 Ghidra 效率会更高。6. 最小案例分析一个自行编译的 Crackme为了让流程更具体我们现场编译一个小程序。它不是恶意样本而是专门用来练习的 Crackme。6.1 准备示例源码新建文件crackme_demo.c// 文件路径crackme_demo.c #include stdio.h #include string.h int main(int argc, char *argv[]) { if (argc 2) { puts(Usage: ./crackme key); return 1; } if (strcmp(argv[1], Pssw0rd) 0) { puts(Correct!); return 0; } else { puts(Wrong.); return 1; } }编译成两个版本一个保留符号一个去除符号gcc crackme_demo.c -o crackme_demo -fno-stack-protector -no-pie gcc crackme_demo.c -o crackme_demo_stripped -fno-stack-protector -no-pie -s参数解释-fno-stack-protector关闭栈保护方便看汇编实际项目中不要随便关。-no-pie关闭地址随机化确保反汇编地址固定便于教学。-s链接时去掉符号表。先运行一下感受程序行为./crackme_demo ./crackme_demo abc ./crackme_demo Pssw0rd正常情况会分别输出Usage、Wrong.、Correct.。6.2 用命令行工具完成第一轮静态分析第一步先看文件类型file crackme_demo file crackme_demo_stripped第二步看字符串strings -n 6 crackme_demo输出里应该能看到Usage: ./crackme key Correct! Wrong. Pssw0rd这一步已经泄漏了答案。原因很简单编译器把字符串常量原样放进了.rodata节区strings直接把它扫出来了。再看被剥离符号的版本strings -n 6 crackme_demo_stripped你会发现Pssw0rd依然存在。这就是一个很容易被新手忽略的坑**strip只移除符号表和调试信息不会移除程序运行所需的数据常量。** 这道题之所以简单是因为它在strcmp中直接使用了明文密码。第三步用objdump反汇编关键函数objdump -d -M intel crackme_demo | grep -A 80 main:你会看到main函数的大致结构核心调用大概是lea rsi, [rip0x2c0c] ; 第二个参数字符串地址 mov rdi, rax ; 第一个参数字符串 call 401030 strcmpplt test eax, eax je 401196 main0x50 ; 相等时跳转到 Correct 分支意思很清楚程序调用strcmp比较输入字符串和某个预设字符串相等跳转到成功分支否则走失败分支。6.3 用 Ghidra 得到伪代码命令行足够应付简单的逻辑但遇到大型程序时还是建议用 Ghidra 看伪代码。流程如下打开 Ghidra新建一个 Non-Shared Project。把crackme_demo_stripped拖进项目窗口。双击导入的文件选择“Analyze”并默认选项运行自动分析。等分析结束后在 Symbol Tree 窗口找到main。如果文件被 StrippedGhidra 通常也能通过入口点分析出main。双击函数名进入反编译窗口。Ghidra 反编译输出大概会是undefined8 main(int argc, char **argv) { if (argc 2) { puts(Usage: ./crackme key); return 1; } iVar1 strcmp(argv[1], Pssw0rd); if (iVar1 0) { puts(Correct!); return 0; } puts(Wrong.); return 1; }这段伪代码已经非常接近原始 C 源码。但注意Ghidra 的伪代码是通过汇编反推出来的不是真正还原源码。它的变量命名、类型推断都可能与原始代码不一致。6.4 判断“这条路径到底怎么走”这个案例里验证路径只有一个strcmp判断整个逻辑非常直白。你可以通过交叉引用Cross Reference找到Pssw0rd字符串被哪个指令引用再跳到它所属的函数就能画出如下路径用户输入 - argv[1] | v strcmp(argv[1], Pssw0rd) | -- 相等 - Correct | -- 不相等 - Wrong真实程序会比这个复杂得多可能有编码函数、哈希比较、反调试、花指令、虚拟机保护和加壳。但从这个最小案例里你已经能体会到静态分析的核心节奏先看结构再找线索再定位关键函数最后还原判断逻辑。7. 静态分析常见问题与排查思路问题现象可能原因排查方式解决方案strings输出里全是重复的系统字符串程序是动态链接的输出混入了 libc 字符串先运行file确认链接方式过滤关键字结合strings -n加大长度并配合grep过滤函数名找不到main函数发布时使用了strip或链接时去符号表用readelf -s查看符号表看入口点附近的__libc_start_main参数从_start开始追踪或使用 Ghidra 自动分析入口点objdump反汇编出的代码像垃圾文件被加壳、压缩或加密用readelf -S检查节区名称和大小用strings查找壳的特征字符串先识别壳再决定脱壳方案不能直接硬读Ghidra 伪代码赋值可疑程序包含优化后代码或混淆对照汇编窗口核实变量、函数调用的真实逻辑不要盲信伪代码优先还原算法步骤程序一运行就崩溃动态分析阶段触发检测或环境依赖不急着运行先检查静态特征和系统要求对样本做静态分析前先做好隔离必要时建立模拟环境关键字符串完全看不到程序运行时拼接、编码或加密字符串搜索常量地址、定位对字符串表的引用在动态调试中观察解密后的内存或用脚本批量提取这里特别提醒一句如果遇到加壳文件静态分析的“第一步判断”就不是硬读代码而是先识别壳。壳会改变文件入口点、压缩原始代码使静态分析工具看到的是“壳的代码”而不是“程序本身的代码”。这类文件需要单独学习壳的识别和脱壳流程初学者不要一上来就硬啃。8. 安全边界与最佳实践8.1 合法授权是底线这一点必须放在最前面。你在 CTF 比赛中分析题目是合法练习你在自己开发的软件上做安全测试是合法行为你分析一个明确得到授权的样本也没有问题。但如果你拿到一个别人的软件、游戏进程、通信协议在没有授权的情况下进行逆向或修改可能违反软件许可协议、相关法律法规甚至触犯刑法。逆向本身就是一把双刃剑技术本身中立但使用边界必须清楚。本文所有案例都应当在你拥有源码、自编译程序或已获授权的目标上练习。8.2 分析不可信样本的环境隔离当你开始接触来路不明的样本时最稳妥的做法是使用隔离环境使用虚拟机如 VirtualBox、VMware快照后分析。断开网络或使用受限的虚拟网络。不在宿主机的真实桌面运行不明代码。对文件先记录哈希值便于后续回溯和查证。即使你只做静态分析也要保持警惕。某些恶意样本被构造出极端的自我解压逻辑仅仅读取文件内容并不会触发恶意行为但不排除文件解析类漏洞的存在。始终用最低权限和隔离环境操作是安全研究的基本原则。8.3 做一份能复现的分析笔记静态分析越到后面信息量越大单靠记忆根本不够。建议你在每次分析时记录下面的信息文件基本信息文件类型、大小、哈希值、编译信息。环境信息分析用系统、工具版本、是否启用了 PIE。关键发现字符串线索、可疑函数地址、主验证路径。结论与依据为什么判断这段逻辑是校验函数、证据是什么。实际项目中你可以直接在 Ghidra 里给函数重新命名、加注释再导出项目文件。这样隔几天回来不用重新做一遍也能快速接续分析。9. 总结与后续学习建议回到文章开头的问题为什么从零懂二进制那么重要因为所有逆向任务的起点都是一个没有注释、没有源码、甚至没有符号的二进制文件。静态分析帮你做的不是一步解谜而是先建立一张地图文件是什么平台、有哪些节区、哪里有可疑字符串、关键函数在哪个地址、调用关系长什么样。有了这张地图动态调试才能成为“验证假设”的环节而不是漫无目的地单步。你已经学到的东西可执行文件的容器结构包括 PE、ELF、Mach-O。静态分析与动态分析的区别和定位。Linux 命令行工具file、strings、readelf、objdump的基本用法。一套从识别文件到反汇编的通用工作流。一个自编译 Crackme 的完整静态分析过程。常见问题和安全边界。下一步可以按这个顺序继续深入自己写几个带不同判断逻辑的 C 程序编译后反复用静态分析拆解。学习 GDB 的基础断点和寄存器查看把静态分析线索和动态验证结合起来。认识常见加密算法的特征码比如 Base64、MD5、AES知道它们会留下什么痕迹。学习加壳与脱壳搞清楚为什么静态分析会遇到壳这种“拦路虎”。如果对封包技术感兴趣可以结合协议抓包先分析哪些信息是从客户端明文发送的再去二进制里定位编码函数。本课内容建议收藏备用但更重要的是亲手做一遍编译crackme_demo.c把符号表去掉再用命令行和 Ghidra 各跑一次流程。等你能在没有任何人提示的情况下独立画出程序的验证路径再去挑战更复杂的题目会顺畅很多。下一篇会进入动态分析拿同一个 Crackme 用 GDB 下断点看程序运行时到底什么时候调用strcmp。如果你已经按本文把静态部分走通了那一步会非常轻松。
返回列表