静态分析基础:PE/ELF文件格式解析、文件头与节区表、导入导出表分析

各位同学,咱们今天聊点硬核的。静态分析,说白了就是对着二进制文件“看”,不动手跑它。你想想看,一个恶意软件你哪敢随便双击?所以,读懂PE和ELF文件格式,是逆向工程师的基本功。我个人习惯,拿到一个样本,第一件事就是拿十六进制编辑器或者工具把它的结构扒开看看。

PE文件格式:Windows世界的身份证

PE(Portable Executable)是Windows下的可执行文件格式。我刚开始学的时候,觉得这东西很复杂,其实拆开来看,就那么几块。

PE文件的核心结构是这样的:

+-------------------+
|   DOS头 (64字节)  |
+-------------------+
|   DOS Stub        |
+-------------------+
|   PE签名 (4字节)  |
+-------------------+
|   COFF文件头      |
+-------------------+
|   可选头          |
+-------------------+
|   节区表          |
+-------------------+
|   节区1 (.text)   |
|   节区2 (.data)   |
|   节区3 (.rdata)  |
|   ...             |
+-------------------+

嗯,这里要注意,DOS头虽然古老,但它指向了PE签名的位置。我见过有人直接跳过DOS头去解析,结果偏移算错了,整个分析都跑偏。

文件头与节区表

PE签名之后紧跟着的是COFF文件头。这里面有几个关键字段:

字段 含义 我的经验
Machine 目标CPU架构(0x14C是x86,0x8664是x64) 有一次我拿到一个样本,Machine字段被篡改成0xFFFF,工具直接报错。这就是个反分析的小花招。
NumberOfSections 节区数量 这个值如果异常大,说明文件可能被加壳或者被篡改过。
SizeOfOptionalHeader 可选头的大小 PE32+(64位)的可选头比PE32大,解析时一定要区分。

节区表是PE文件的“目录”。每个节区表项占40字节,记录了节区的名称、虚拟地址、原始数据偏移等信息。我建议你重点关注这几个节区:

  • .text:代码段,可执行。逆向分析的核心区域。
  • .data:已初始化的全局变量。
  • .rdata:只读数据,导入导出表通常在这里。
  • .rsrc:资源段,图标、字符串、对话框都在里面。
小技巧: 用CFF Explorer或者010 Editor打开PE文件,直接看节区表的Raw Offset和Virtual Address。如果两者差距很大,说明这个节区可能被压缩或加密了——嗯,大概率是加壳了。

导入导出表分析

导入表(Import Table)告诉你这个程序调用了哪些外部函数。比如调用了MessageBoxACreateFile等等。导出表(Export Table)则相反,它告诉别人这个DLL提供了哪些函数。

导入表的结构有点绕,我当年也花了不少时间才理清楚。它本质上是一个IMAGE_IMPORT_DESCRIPTOR数组,每个描述符对应一个DLL。结构如下:

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;
        DWORD   OriginalFirstThunk;  // INT (Import Name Table)
    };
    DWORD   TimeDateStamp;
    DWORD   ForwarderChain;
    DWORD   Name;                    // DLL名称的RVA
    DWORD   FirstThunk;             // IAT (Import Address Table)
} IMAGE_IMPORT_DESCRIPTOR;

这里有个关键点:OriginalFirstThunkFirstThunk都指向一个IMAGE_THUNK_DATA数组。前者指向函数名称的RVA,后者在加载后会被系统填充为函数的实际地址。我曾经在分析一个被Hook的样本时,发现IAT被篡改了,但INT还是原始的。通过对比两者,我找到了Hook点。

避坑指南: 我曾经遇到过一个样本,它的导入表被故意破坏,OriginalFirstThunk指向了无效地址。这时候不能直接解析,得用特征匹配或者动态调用来恢复导入表。

ELF文件格式:Linux世界的通行证

ELF(Executable and Linkable Format)是Linux下的标准格式。和PE相比,ELF的设计更简洁,但核心思想是一样的。

ELF文件的结构:

+-------------------+
|   ELF头           |
+-------------------+
|   程序头表         |
|   (可选,用于加载) |
+-------------------+
|   节区头表         |
+-------------------+
|   节区             |
|   .text           |
|   .data           |
|   .symtab         |
|   .strtab         |
|   ...             |
+-------------------+

ELF头的前4个字节是魔数0x7F 'E' 'L' 'F'。我习惯用readelf -h命令快速查看ELF头信息。

节区表与关键节区

ELF的节区头表相当于PE的节区表。每个节区头表项描述了节区的名称、类型、偏移、大小等。常用的节区有:

  • .text:代码段。
  • .data:已初始化数据。
  • .bss:未初始化数据(不占文件空间)。
  • .symtab:符号表,包含函数名和变量名。
  • .strtab:字符串表,符号名称的字符串都在这。
  • .plt.got:动态链接的关键,相当于PE的IAT。
重要: 在逆向ELF时,.symtab.strtab是你的好朋友。如果程序没有被strip(剥离符号),你能直接看到函数名,分析起来事半功倍。如果被strip了,那就得靠函数特征来识别了。

导入导出表(动态链接)

ELF没有像PE那样明确的导入导出表结构。它的动态链接信息分散在.dynamic节区中。.dynamic节区包含了一系列的Elf64_Dyn结构,其中:

  • DT_NEEDED:依赖的共享库(相当于PE的导入DLL)。
  • DT_SYMTAB:动态符号表的地址。
  • DT_STRTAB:动态字符串表的地址。
  • DT_PLTGOT:GOT(全局偏移表)的地址。

说白了,ELF的导入解析是通过PLT(过程链接表)和GOT配合完成的。第一次调用函数时,PLT跳转到GOT,GOT指向PLT的解析代码;解析完成后,GOT被更新为函数的真实地址。我建议你用objdump -d查看PLT段,能直观看到这个过程。

个人经验: 我在分析一个Linux后门时,发现它通过修改GOT表来Hook系统调用。通过对比原始GOT和运行时的GOT,我找到了它的Hook点。所以,GOT表是ELF动态分析的“兵家必争之地”。

知识体系总览

下面这张图,是我自己总结的PE和ELF静态分析的核心逻辑。你把它记在脑子里,分析任何样本都不会迷路。

静态分析核心知识体系 PE文件格式 ELF文件格式 DOS头 + PE签名 COFF文件头 可选头 节区表 导入表 (IAT/INT) 导出表 (EAT) ELF头 (魔数) 程序头表 节区头表 .dynamic节区 PLT / GOT 表 符号表 (.symtab / .dynsym) 核心目标:识别文件结构 → 定位关键数据 → 恢复符号与导入导出 常用工具:010 Editor / CFF Explorer / readelf / objdump / IDA Pro 静态分析是基础,动态求解是进阶。打好地基,后面才稳。

好了,这一章的内容就到这里。PE和ELF的格式细节很多,但核心就是文件头、节区表、导入导出表这三板斧。你把这些搞明白了,静态分析就算入门了。下一章我们会聊到如何用符号执行来动态求解,那才是真正有意思的地方。

专注资料整理