📄 pe文件格式.txt
字号:
PE 文件格式
对 PE 的一些说明(猛禽): PE 是 Portable Excutable 的缩写,是指“可移植可执行”文件,是 32 位 Windows (包括 OS/2 )可执行文件的标准格式。以前的 16 位 Windows 可执行文件的格式称为 NE ,即 New Excutable “新可执行”文件。参考: NE 文件格式
一、简介
PE文件最前面是一个DOS可执行文件(STUB),这使PE文件成为一个合法的MS-DOS可执行
文件。
DOS文件头后面是一个32位的PE文件标志0X00004550(IMAGE_NT_SIGNATURE)。
接着就是PE的文件头了,包含的信息有该程序运行平台、有多少段(sections)、文件
链接的时间、它是一个可执行文件(EXE)还是一个动态链接库(DLL)或是其他。
后面紧接着有一个“可选”头部(这个部分总是存在,但是因为COFF在库(Libraries)
中用了这个词,在一可执行模块中并没有用这个词,但是仍被叫做可选的)。这可部分包含程
序加载的更多的信息:开始地址、保留堆栈数量、数据段大小等等。
可选头中还有一个重要的域是一叫做“数据目录表”(data directories)的数组;表
中的每一项是一个指向某一个段的指针。例如:如果某程序有一个输出目录表(export dire
ctory ),那你就会在数据目录表中找到一个为IMAGE_DIRECTORY_ENTRY_EXPORT的指针,并且
它将指向某一个段。
可选头的下面就是“段”(sections)了,通过一个叫做“段头”(section headers)
的结构索引。实际上,段的内容才是你要真正执行的程序,上面介绍的所有的文件头及目录表
等信息就是为了能正确的找到它。
每一个段都有一些有关的标志,例如它包含什么数据(“初始化数据”或其他),它能
否被共享等,及它数据本身的特征。大多数情况下(并不是全部),每个段会被一个或多个目
录表指向,目录表可通过可选头的“数据目录表”的入口找到,就象输出函数表或基址重定位
表。也有没有目录表指向的段,如可执行代码或初始化数据。
整个文件结构如下:
+-------------------+
| DOS-stub |
+-------------------+
| file-header |
+-------------------+
| optional header |
|- - - - - - - - - -|
| |
| data directories |
| |
+-------------------+
| |
| section headers |
| |
+-------------------+
| |
| section 1 |
| |
+-------------------+
| |
| section 2 |
| |
+-------------------+
| |
| ... |
| |
+-------------------+
| |
| section n |
| |
+-------------------+
下面介绍一下相关虚拟地址(Relative Virtual Addresses)
PE格式文件中经常用到RVA,即相关虚拟地址,用在不知道基地址的情况下表示一个内存
地址。它需要加上基地址才能得到线性地址(Linear address)。
例如:假设一个可执行程序调入内存0x400000处并且程序从RVA 0x1560处开始执行。那
么正确的开始地址是0x401560。如果可执行程序调入0x100000处,则开始地址为0x101560。
因为PE文件的每一个段不必按同样的边界对齐方式调入,因此RVA地址的计算变得比较复
杂。例如,在文件中每一个段往往按512个字节的方式对齐,而在内存中可能以4096字节的方
式对齐。这方面的介绍可见下面的“SectionAlignment”、“FileAlignment”。举个例子,
假设你知道一个程序从RVA 0x1560开始执行,你想从那儿反汇编它。你发现内存中的段对齐方
式为4096并且.code段开始于内存RVA 0x1560并且有16384字节长;那么你可以知道RVA 0x156
0在这个段的0x560处。你又发现这个段在文件中以512字节方式对齐并且.code开始于文件0x8
00处,那现在你知道了可执行程序开始于0x800+0x560 = 0xd60处。
二、DOS头(DOS-stub )
众所周知DOS头的概念是从16位的WINDOWS可执行程序(NE格式)中来的,这个部分主要
用在OS/2可执行程序、自解压文档及其他应用程序。在PE格式文件中,大多数程序的这个部分
中只有大约100个字节的代码,只输出一个诸如“this program needs windows NT ”之类的
信息。
你可以通过一个叫做IMAGE_DOS_HEADER的结构来识别一个合法的DOS头。这个结构的头两
个字节一定是“MZ”(#define IMAGE_DOS_SIGNATURE "MZ")。怎么才能找到PE开始的标志呢
?你可以通过该结构的一个叫做“e_lfanew”(offset 60,32bits) 的成员来找到它。在O
S/2及16位WINDOWS程序中这个标志是一个16位的字;在PE程序中,它是一个32位的双字,值为
0x00004550(#define IMAGE_NT_SIGNATURE 0x00004550)。
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
WORD e_magic; // Magic number
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;
三、文件头(File Header)
通过DOS头,你可以找到一个叫做IMAGE_FILE_HEADER的结构,如下;下面我分别介绍一
下。
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; //0x04
WORD NumberOfSections; //0x06
DWORD TimeDateStamp; //0x08
DWORD PointerToSymbolTable; //0x0c
DWORD NumberOfSymbols; //0x10
WORD SizeOfOptionalHeader; //0x14
WORD Characteristics; //0x16
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
Machine:表示该程序要执行的环境及平台,现在已知的值如下:
IMAGE_FILE_MACHINE_I386(0x14c)
Intel 80386 处理器以上
0x014d
Intel 80486 处理器以上
0x014e
Intel Pentium 处理器以上
0x0160
R3000(MIPS)处理器,高位在前
IMAGE_FILE_MACHINE_R3000(0x162)
R3000(MIPS)处理器,低位在前
IMAGE_FILE_MACHINE_R4000(0x166)
R4000(MIPS)处理器,低位在前
IMAGE_FILE_MACHINE_R10000(0x168)
R10000(MIPS)处理器,低位在前
IMAGE_FILE_MACHINE_ALPHA(0x184)
DEC Alpha AXP处理器
IMAGE_FILE_MACHINE_POWERPC(0x1f0)
IBM Power PC,低位在前
NumberOfSections:段的个数,段的概念我们将在下面介绍。
TimeDateStamp:文件建立的时间。你可用这个值来区分同一个文件的不同的版本,即使
它们的商业版本号相同。这个值的格式并没有明确的规定,但是很显然的大多数的C编译器都
把它定为从1970.1.1 00:00:00以来的秒数(time_t )。这个值有时也被用做绑定输入目录表
,这将在下面介绍。
注意:一些编译器将忽略这个值。
PointerToSymbolTable 及 NumberOfSymbols:用在调试信息中,我不太清楚它们的用途
,不过发现它们总为0。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -