逆向工程基础:x86/x64汇编基础、PE/ELF文件格式解析、常用逆向工具介绍

各位同学,欢迎来到逆向工程的第一课。说实话,逆向分析这活儿,看着挺酷,但入门确实有点门槛。不过别怕,咱们一步步来。今天要聊的三个东西——汇编、文件格式、工具——是逆向的三大支柱。我个人习惯把这三点比作「语言、地图和武器」。你想想看,不懂语言怎么读代码?没地图怎么找关键函数?没武器怎么调试?好,咱们开整。

x86/x64汇编基础:从寄存器到指令

汇编语言,说白了就是CPU能直接听懂的话。x86是32位时代的王者,x64是64位时代的霸主。两者兼容,但细节上差异不小。

寄存器:CPU的临时记事本

x86有8个通用寄存器:EAX、EBX、ECX、EDX、ESI、EDI、EBP、ESP。每个32位。到了x64,扩展成了RAX、RBX……一共16个,每个64位。嗯,这里要注意:x64下,低32位还能用EAX访问,但高32位是独立的。

我遇到过不少新手,一上来就记寄存器名字,记到崩溃。其实不用。你只要记住几个关键角色:

  • EAX/RAX:累加器,函数返回值通常放这儿。
  • EBP/RBP:基址指针,指向当前栈帧的底部。
  • ESP/RSP:栈指针,指向栈顶。这俩是调试时的命根子。
  • EIP/RIP:指令指针,指向下一条要执行的指令。逆向时你盯着它看就行。
我的小技巧: 在IDA Pro里,我习惯把寄存器窗口固定在右侧。调试时,重点看EIP和ESP的变化,基本能猜出程序在干嘛。

常用指令:别被吓到

汇编指令看着多,其实常用的就那几十条。我列几个你一定会遇到的:

指令作用例子
MOV数据传送MOV EAX, 0x1234
PUSH/POP压栈/出栈PUSH EAX; POP EBX
CALL/RET调用函数/返回CALL 0x401000
JMP/Jcc无条件/条件跳转JMP 0x401200; JE 0x401300
LEA取有效地址LEA EAX, [EBP-0x4]

为什么会这样?因为程序本质上就是数据搬运+跳转。你只要看懂数据怎么流、控制怎么跳,逆向就成功了一半。

避坑指南: 我曾经在分析一个加壳样本时,被花指令搞晕了。一堆JMP和NOP穿插在代码里,IDA静态分析直接跑偏。后来我养成习惯:遇到可疑的跳转,先看目标地址是不是在代码段内。不是?那八成是花指令。

PE/ELF文件格式解析:可执行文件的骨架

PE是Windows的格式,ELF是Linux的格式。两者结构不同,但核心思想一样:告诉操作系统「我的代码在哪儿、数据在哪儿、入口在哪儿」。

PE文件格式:Windows下的标准

PE文件的结构,我习惯从DOS头开始看。DOS头里有个e_lfanew字段,指向真正的PE签名。签名之后是文件头、可选头、节表。嗯,这里要注意:可选头其实不是可选的,它包含了程序入口点、镜像基址、节对齐等关键信息。

节表定义了各个节(.text、.data、.rdata等)的虚拟地址、文件偏移、大小和属性。逆向时,我经常先看节表,判断代码段和数据段的位置。

// 一个简单的PE解析示例(伪代码)
DOS_HEADER *dos = (DOS_HEADER *)file_base;
PE_HEADER *pe = (PE_HEADER *)(file_base + dos->e_lfanew);
IMAGE_OPTIONAL_HEADER *opt = &pe->OptionalHeader;
printf("Entry Point: 0x%X\n", opt->AddressOfEntryPoint);
printf("Image Base: 0x%X\n", opt->ImageBase);
// 遍历节表
for (int i = 0; i < pe->FileHeader.NumberOfSections; i++) {
    SECTION_HEADER *sec = &opt->Sections[i];
    printf("Section: %s, VA: 0x%X, Size: 0x%X\n", sec->Name, sec->VirtualAddress, sec->SizeOfRawData);
}

ELF文件格式:Linux下的优雅设计

ELF的结构比PE更简洁。文件头(Ehdr)直接告诉你这是32位还是64位、入口点、节头表偏移。节头表(Shdr)描述每个节的信息。程序头表(Phdr)描述段(Segment)信息,用于加载。

我个人觉得ELF的设计更「干净」。没有DOS头那些历史包袱,结构紧凑。逆向ELF时,我一般先看节头表,找.text和.rodata节。前者是代码,后者是只读数据(比如字符串)。

注意: PE和ELF的地址计算方式不同。PE用RVA(相对虚拟地址),ELF用VA(虚拟地址)或偏移。我曾经在分析一个跨平台样本时,把PE的RVA当成了ELF的VA,结果地址全算错了。嗯,从那以后我每次都会确认文件格式再动手。

常用逆向工具:IDA Pro、Ghidra、x64dbg

工具是逆向工程师的武器。选对工具,事半功倍。我个人的工具箱里,这三样是常驻的。

IDA Pro:静态分析的王者

IDA Pro,业内公认的静态分析神器。它的反编译功能(F5)能把汇编转成类C代码,极大降低分析门槛。我刚开始用IDA时,最震撼的就是按一下F5,一堆汇编变成了清晰的逻辑判断。

IDA的交叉引用(Xref)功能也很强。你选中一个函数,它能列出所有调用它的地方。这在分析恶意软件的控制流时特别有用。

不过IDA Pro是收费的,而且价格不菲。如果你预算有限,可以先用免费版(IDA Free),功能少一些,但基础分析够用。

Ghidra:开源界的黑马

Ghidra是美国NSA开源的逆向工具,功能直逼IDA Pro。它的反编译引擎很成熟,支持多种架构(x86、ARM、MIPS等)。而且,Ghidra是免费的!

我建议新手从Ghidra入手。它的界面可能不如IDA直观,但胜在功能全、更新快。而且,Ghidra的脚本功能(Python/Java)很强大,可以批量分析样本。

我记得有一次分析一个变种样本,IDA的签名库没识别出来,但Ghidra的自动分析直接匹配到了已知的恶意代码模式。嗯,从那以后我两个工具都开着。

x64dbg:动态调试的利器

静态分析只能看代码,动态调试才能看行为。x64dbg是Windows下最流行的开源调试器,支持x86和x64。它的界面简洁,功能实用:断点、单步、内存查看、寄存器编辑……该有的都有。

我调试恶意软件时,习惯先在入口点下断点,然后单步跟踪。遇到反调试代码(比如IsDebuggerPresent),我会手动修改寄存器或跳过它。x64dbg的「Run to User Code」功能也很实用,能快速跳过系统加载代码,直接进入程序逻辑。

我的工作流: 先用IDA/Ghidra做静态分析,理清代码结构。然后打开x64dbg,在关键函数下断点,动态验证我的猜测。两者结合,效率最高。

好了,这一章的内容就这么多。逆向工程的基础,说白了就是「看懂汇编、摸清格式、用好工具」。这三样练熟了,后面的课程你会轻松很多。下一章,咱们聊聊如何用这些基础去分析一个真实的恶意样本。到时候,我会拿一个我实际分析过的案例来演示。敬请期待!