Dos操作系统中的Com文件是最早也是结构最简单的可执行文件,Com文件是"纯代码的"。它没有附加数据来指定文件入口,因此第一条指令必须处于文件开头的位置。而且,没有重定位信息,这样代码中不能有跨段操作数据的指令。
EXE文件是我们熟悉的另一种可执行文件,EXE文件的代码前面加了一个文件头,其中包含各种数据,如文件入口、堆栈位置、重定位表等。操作系统会根据文件头中的信息将代码部分装入内存,根据重定位表修正代码,最后在设置好堆栈后从文件头中指定的入口开始执行。
PE格式(Portable Executable File Format/可移植的执行体)
Windows3.x 起引入了一种新的文件格式,既PE格式。在PE文件中,代码、已初始化数据、资源、和重定位信息等数据被按照属性分类放到不同的节(Section)中,而每个节的属性和位置信息用一个 IMAGE_SECTINO_HEADER 结构来描述,所有的IMAGE_SECTINO_HEADER结构组成一个节表(Section Table),节表数据在PE文件中被放在所有节数据的前面。
由于数据是按照属性在节中放置的,不同用途但是属性相同的数据(如导入表、导出表以及.const段指定的只读数据)可能被放在同一个节中,所以PE文件中还用一系列的数据目录结构 IMAGE_DATA_DIRECTORY 来分别指明这些数据的位置,数据目录表和其他描述文件属性的数据合在一起称为PE文件头,PE文件头被放置在节和节表的前面。
PE文件的基本结构:
DOS文件头和DOS块
PE文件中的DOS部分由Dos文件头和可执行代码两部分组成,可执行代码被称为“DOS块”。最前面的
MZ Header 是一个Dos文件头,占用了PE文件的前64个字节。它由 IMAGE_DOS_HEADER 结构定义:
WINNT.H
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
USHORT e_magic; // Magic number
USHORT e_cblp; // Bytes on last page of file
USHORT e_cp; // Pages in file
USHORT e_crlc; // Relocations
USHORT e_cparhdr; // Size of header in paragraphs
USHORT e_minalloc; // Minimum extra paragraphs needed
USHORT e_maxalloc; // Maximum extra paragraphs needed
USHORT e_ss; // Initial (relative) SS value
USHORT e_sp; // Initial SP value
USHORT e_csum; // Checksum
USHORT e_ip; // Initial IP value
USHORT e_cs; // Initial (relative) CS value
USHORT e_lfarlc; // File address of relocation table
USHORT e_ovno; // Overlay number
USHORT e_res[4]; // Reserved words
USHORT e_oemid; // OEM identifier (for e_oeminfo)
USHORT e_oeminfo; // OEM information; e_oemid specific
USHORT e_res2[10]; // Reserved words
LONG e_lfanew; // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
e_magic 字段是DOS可执行文件标记,其数值等于Ascii码“MZ”。(在Windows.inc中已预定义为IMAGE_DOS_SIGNATURE)
对于PE文件来说,有用的是最后的 e_lfanew 字段,它指出了PE文件头在文件中的位置,这个位置总是以8字节为单位对齐的。
下图是用 UltraEdit 打开某个EXE文件的样子:
( UltraEdit :一个强大的替代Windows记事本(Notepad)的编辑器,可以用它直接编辑器十六进制文件(HEX)。)
我们可以看到前两个字节是 4D 5A 其Ascii码等于“MZ”。这就是 e_magic 字段,不过值得注意的是在小端法
机器上,数据在内存中是按低位在低字节存放的,所以 e_magic = 0x5A4D
PE文件头
PE文件头的第一个4字节是一个标志,它被定义为0x00004550,也就是"P","E"。这也是“PE”这个称呼的由来。
PE文件头是由 IMAGE_NT_HEADERS结构定义的:
IMAGE_NT_HEADERS STRUCT
Signature DWORD ? ; PE signature
FileHeader IMAGE_FILE_HEADER <>
OptionalHeader IMAGE_OPTIONAL_HEADER32 <>
IMAGE_NT_HEADERS ENDS
DOS文件头的 e_lfanew字段(文件头偏移 0x003C)的值就是PE文件头相对于文
件头的偏移值。
现在让我们从上图找出e_lfanew字段的数值,它的地址就是0x0000003C。方法是从最左边找到地址 00000030h 这一
行与图中最上边C这一列的交界点,这就是地址0x0000003C,也就是e_lfanew字段的第一个
字节。我们看到 e_lfanew = 0x000000d0 ,这表明PE文件头的地址等于文件头的地址加 0x000000d0 。看看
0x000000d0 这个地址有什么东西,嗯,0x00004550 正是PE文件标识"PE"。