摘要
本文描述了Windows系统的PE文件格式。
PE文件格式简介
PE(Portable Executable)文件格式是一种Windows操作系统下的可执行文件格式。PE文件格式是由Microsoft基于COFF(Common Object File Format)格式所定义的,它规定了Windows可执行文件(.exe)和动态链接库(.dll)的结构,包括文件头、节表、导入和导出表、资源表、重定位表等。PE格式被广泛应用于Windows操作系统及Windows软件开发中,它的特点是文件结构清晰、易于扩展、安全性高等。
PE文件格式分为PE32和PE32+,Windows 32位系统采用的是PE32,Windows 64位系统采用的是PE32+。PE32和PE32+之间的区别如下:
1. PE32+将PE32中的32位RVA字段和尺寸相关字段扩展成了64位。
2. 在PE32+中使用了新的数据结构,其中重要的变化是IMAGE_OPTIONAL_HEADER结构的扩展,以包括更多的字段和属性。
3. PE32+中使用了更多的CPU指令集,包括SSE2、SSE3和AVX等指令,以提高程序的性能和处理能力。
本文以PE32文件格式和x86机器模型作为示例进行讲解。
PE文件主要由若干节(Section)构成,节是PE文件格式的核心概念。
PE文件中节与CPU架构中的段(Segment)的概念相对应。加载器将不同内存访问属性的节被加载进内存后,由CPU的LDTR和GDTR指示的段描述表来描述。程序运行时,CPU通过的各种段选择子(如CS、DS、SS、ES、FS、GS等)来选择对应的段(如:代码段.text、数据段.data)的段描述符,通过前端总线(FSB)来访问段内的代码或数据。
COFF文件结构图
Windows系统中的VC编译出来的目标文件格式采用的COFF文件格式,其结构如下:
COFF(Common Object File Format)文件格式起源于UNIX系统,最早是由AT&T Bell Laboratories和Microsoft合作开发出来的。这种文件格式最初用于存储编译后的目标文件和共享库,并被广泛应用于UNIX系统中。COFF文件格式在1985年被POSIX标准所接受,并逐渐成为了一种通用的目标文件格式。
在后来的Windows平台中,微软对COFF进行了一些修改和扩展,并将其用作PE(Portable Executable)文件的基础格式。PE文件包含了Windows中可执行文件和动态链接库的细节信息,并成为了Windows系统中的主要二进制文件格式。在Windows上,COFF和PE同宗同源,联系紧密,可统称为COFF/PE格式。
PE文件结构图
PE文件结构定义
PE文件结构的定义在winnt.h头文件中,winnt.h典型路径为:
C:\Program Files (x86)\Windows Kits\10\Include\10.0.22621.0\um\winnt.h
DOS头
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
WORD e_magic; // Magic number:'M','Z'
WORD e_cblp; // Bytes on last page of file
WORD e_cp; // Pages in file
WORD e_crlc; // Relocations
WORD e_cparhdr; // Size of header in paragraphs
WORD e_minalloc; // Minimum extra paragraphs needed
WORD e_maxalloc; // Maximum extra paragraphs needed
WORD e_ss; // Initial (relative) SS value
WORD e_sp; // Initial SP value
WORD e_csum; // Checksum
WORD e_ip; // Initial IP value
WORD e_cs; // Initial (relative) CS value
WORD e_lfarlc; // File address of relocation table
WORD e_ovno; // Overlay number
WORD e_res[4]; // Reserved words
WORD e_oemid; // OEM identifier (for e_oeminfo)
WORD e_oeminfo; // OEM information; e_oemid specific
WORD e_res2[10]; // Reserved words
LONG e_lfanew; // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
DOS stub
DOS存根程序,一般只打印程序不能在DOS下运行的提示,也可以是具备完整功能的DOS程序。
在Windows上.exe和.dll文件都包含了DOS头和DOS stub。
映像文件头
PE的映像文件头采用了与COFF的映像文件头一致的结构。
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; // 例如:intel 386 | 486 | 568,DEC Alpha AXP, IBM Power PC。
WORD NumberOfSections; // 节数量
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader; // 可选头尺寸可为0,也可比标准定义更大。
WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
PE可选头
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
//
// 镜像数据目录项结构
//
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
//
// Optional header format.
//
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//
WORD Magic; // 魔法标记:'P', 'E'
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
//
// NT additional fields.
//
DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem; // 子系统类型:Windows | Console
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
DataDirectory[16]根据索引值定义了不同的数据内容的起始RVA和尺寸,此数组是理解PE节内容语义的关键数据结构,它直接描述了导入表、导出表、资源等数据在内存中的RVA和尺寸。
数据是按照内存访问属性合并存储在特定的节中的,而不是按照语义分节的,数据语义和节名称没有对应关系。
节头表
PE文件中的代码、数据、资源全部是存储在不同内存访问属性的节中的,类似竹子的多节结构。
节头用于描述一节的名称、节内容的RVA、节大小、特征(是否缓存、代码、数据)等。
节头由定长尺寸的结构体组成,所有节的节头构成一个节头表。
节头表以一个空的节头结构作为结束标记。
//
// Section header format.
//
#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 节名称,最多8个字符
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress; // 节内容的起始RVA
DWORD SizeOfRawData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics; // 内存访问属性:可读 | 可写 | 可执行
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
// 节头固定长度为40
#define IMAGE_SIZEOF_SECTION_HEADER 40
节内容
读者可将可节内容理解为于节头(header)对应的节体(body),类似HTML的头和体。
每一个节的内容是链接器根据代码或数据的内存访问属性合并而成的。
每一个节内容的起始地址和尺寸由节头表中的一个节头项描述。
节内的数据语义分界由IMAGE_DATA_DIRECTORY结构定义。
PE文件分析
dumpbin.exe是微软Visual Studio的一个工具,它可以用于检查和显示二进制文件的详细信息。它可以显示一个可执行文件、静态库、动态库或任何其他二进制文件的导出表、导入表、资源表、头文件、符号表和其他详细信息。它可以被用于分析和调试二进制文件,以了解它们内部的结构和内容。
dumpbin.exe的典型路径为:
%VS_INSTALL_DIR%\VC\Tools\MSVC\14.36.32532\bin\Hostx86\x86\dumpbin.exe
以下是一些dumpbin.exe的用法示例:
1. 显示可执行文件的导入表:
dumpbin /imports myapp.exe
2. 显示静态库的符号表:
dumpbin /symbols mylib.lib
3. 显示动态库的导出表:
dumpbin /exports mydll.dll
4. 显示PE文件的各种头结构:
dumpbin /headers myfile.bin
5. 显示可执行文件的资源表:
dumpbin /resources myapp.exe
6.显示线程局部存储节的汇总信息和原始数据
dumpbin /section:.tls /rawdata myapp.exe
7.显示代码节的反汇编数据
dumpbin /section:.text /disasm myapp.exe
8.显示RVA重定位表
dumpbin /relocations myapp.exe
dumpbin.exe还有许多其他选项和用法,可以通过运行“dumpbin /?”命令来查看完整的用法和选项列表。
参考资料
PE文件结构详解-1
PE文件结构详解-2
PE Format - Win32 apps | Microsoft Learn
PE文件结构详解_大囚长的博客-CSDN博客
PE结构详解_weixin_44870554的博客-CSDN博客
PE文件格式 - 随笔分类 - 不会笑的孩子 - 博客园 (cnblogs.com)
PE结构分析 - Lonely Blog (wuhao13.xin)
DUMPBIN 选项 | Microsoft Learn
总结
PE文件格式是学习Windows系统反病毒、手动查杀木马技术所要求的前置基础知识,需要深刻领会。