PE文件结构详解(二)可执行文件头

PE文件结构详解(一)基本概念里,解释了一些PE文件的一些基本概念,从这篇开始,将详细讲解PE文件中的重要结构。

了解一个文件的格式,最应该首先了解的就是这个文件的文件头的含义,因为几乎所有的文件格式,重要的信息都包含在头部,顺着头部的信息,可以引导系统解析整个文件。所以,我们先来认识一下PE文件的头部格式。还记得上篇里的那个图吗?

DOS头和NT头就是PE文件中两个重要的文件头。

一、DOS头

DOS头的作用是兼容MS-DOS
操作系统中的可执行文件,对于32位PE文件来说,DOS所起的作用就是显示一行文字,提示用户:我需要在32位windows上才可以运行。我认为这是
个善意的玩笑,因为他并不像显示的那样不能运行,其实已经运行了,只是在DOS上没有干用户希望看到的工作而已,好吧,我承认这不是重点。但是,至少我们
看一下这个头是如何定义的:

[cpp] view plaincopy

  1. typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
  2. WORD   e_magic;                     // Magic number
  3. WORD   e_cblp;                      // Bytes on last page of file
  4. WORD   e_cp;                        // Pages in file
  5. WORD   e_crlc;                      // Relocations
  6. WORD   e_cparhdr;                   // Size of header in paragraphs
  7. WORD   e_minalloc;                  // Minimum extra paragraphs needed
  8. WORD   e_maxalloc;                  // Maximum extra paragraphs needed
  9. WORD   e_ss;                        // Initial (relative) SS value
  10. WORD   e_sp;                        // Initial SP value
  11. WORD   e_csum;                      // Checksum
  12. WORD   e_ip;                        // Initial IP value
  13. WORD   e_cs;                        // Initial (relative) CS value
  14. WORD   e_lfarlc;                    // File address of relocation table
  15. WORD   e_ovno;                      // Overlay number
  16. WORD   e_res[4];                    // Reserved words
  17. WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
  18. WORD   e_oeminfo;                   // OEM information; e_oemid specific
  19. WORD   e_res2[10];                  // Reserved words
  20. LONG   e_lfanew;                    // File address of new exe header
  21. } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

我们只需要关注两个域:

e_magic:一个WORD类型,值是一个常数0x4D5A,用文本编辑器查看该值位‘MZ’,可执行文件必须都是‘MZ‘开头。

e_lfanew:为32位可执行文件扩展的域,用来表示DOS头之后的NT头相对文件起始地址的偏移。

二、NT头

顺着DOS头中的e_lfanew,我们很容易可以找到NT头,这个才是32位PE文件中最有用的头,定义如下:

[cpp] view plaincopy

  1. typedef struct _IMAGE_NT_HEADERS {
  2. DWORD Signature;
  3. IMAGE_FILE_HEADER FileHeader;
  4. IMAGE_OPTIONAL_HEADER32 OptionalHeader;
  5. } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

下图是一张真实的PE文件头结构以及其各个域的取值:

Signature:类似于DOS头中的e_magic,其高16位是0,低16是0x4550,用字符表示是‘PE‘。

IMAGE_FILE_HEADER是PE文件头,c语言的定义是这样的:

[cpp] view plaincopy

  1. typedef struct _IMAGE_FILE_HEADER {
  2. WORD    Machine;
  3. WORD    NumberOfSections;
  4. DWORD   TimeDateStamp;
  5. DWORD   PointerToSymbolTable;
  6. DWORD   NumberOfSymbols;
  7. WORD    SizeOfOptionalHeader;
  8. WORD    Characteristics;
  9. } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

每个域的具体含义如下:

Machine:该文件的运行平台,是x86、x64还是I64等等,可以是下面值里的某一个。

[cpp] view plaincopy

  1. #define IMAGE_FILE_MACHINE_UNKNOWN           0
  2. #define IMAGE_FILE_MACHINE_I386              0x014c  // Intel 386.
  3. #define IMAGE_FILE_MACHINE_R3000             0x0162  // MIPS little-endian, 0x160 big-endian
  4. #define IMAGE_FILE_MACHINE_R4000             0x0166  // MIPS little-endian
  5. #define IMAGE_FILE_MACHINE_R10000            0x0168  // MIPS little-endian
  6. #define IMAGE_FILE_MACHINE_WCEMIPSV2         0x0169  // MIPS little-endian WCE v2
  7. #define IMAGE_FILE_MACHINE_ALPHA             0x0184  // Alpha_AXP
  8. #define IMAGE_FILE_MACHINE_SH3               0x01a2  // SH3 little-endian
  9. #define IMAGE_FILE_MACHINE_SH3DSP            0x01a3
  10. #define IMAGE_FILE_MACHINE_SH3E              0x01a4  // SH3E little-endian
  11. #define IMAGE_FILE_MACHINE_SH4               0x01a6  // SH4 little-endian
  12. #define IMAGE_FILE_MACHINE_SH5               0x01a8  // SH5
  13. #define IMAGE_FILE_MACHINE_ARM               0x01c0  // ARM Little-Endian
  14. #define IMAGE_FILE_MACHINE_THUMB             0x01c2
  15. #define IMAGE_FILE_MACHINE_AM33              0x01d3
  16. #define IMAGE_FILE_MACHINE_POWERPC           0x01F0  // IBM PowerPC Little-Endian
  17. #define IMAGE_FILE_MACHINE_POWERPCFP         0x01f1
  18. #define IMAGE_FILE_MACHINE_IA64              0x0200  // Intel 64
  19. #define IMAGE_FILE_MACHINE_MIPS16            0x0266  // MIPS
  20. #define IMAGE_FILE_MACHINE_ALPHA64           0x0284  // ALPHA64
  21. #define IMAGE_FILE_MACHINE_MIPSFPU           0x0366  // MIPS
  22. #define IMAGE_FILE_MACHINE_MIPSFPU16         0x0466  // MIPS
  23. #define IMAGE_FILE_MACHINE_AXP64             IMAGE_FILE_MACHINE_ALPHA64
  24. #define IMAGE_FILE_MACHINE_TRICORE           0x0520  // Infineon
  25. #define IMAGE_FILE_MACHINE_CEF               0x0CEF
  26. #define IMAGE_FILE_MACHINE_EBC               0x0EBC  // EFI Byte Code
  27. #define IMAGE_FILE_MACHINE_AMD64             0x8664  // AMD64 (K8)
  28. #define IMAGE_FILE_MACHINE_M32R              0x9041  // M32R little-endian
  29. #define IMAGE_FILE_MACHINE_CEE               0xC0EE

NumberOfSections:该PE文件中有多少个节,也就是节表中的项数。

TimeDateStamp:PE文件的创建时间,一般有连接器填写。

PointerToSymbolTable:COFF文件符号表在文件中的偏移。

NumberOfSymbols:符号表的数量。

SizeOfOptionalHeader:紧随其后的可选头的大小。

Characteristics:可执行文件的属性,可以是下面这些值按位相或。

[cpp] view plaincopy

  1. #define IMAGE_FILE_RELOCS_STRIPPED           0x0001  // Relocation info stripped from file.
  2. #define IMAGE_FILE_EXECUTABLE_IMAGE          0x0002  // File is executable  (i.e. no unresolved externel references).
  3. #define IMAGE_FILE_LINE_NUMS_STRIPPED        0x0004  // Line nunbers stripped from file.
  4. #define IMAGE_FILE_LOCAL_SYMS_STRIPPED       0x0008  // Local symbols stripped from file.
  5. #define IMAGE_FILE_AGGRESIVE_WS_TRIM         0x0010  // Agressively trim working set
  6. #define IMAGE_FILE_LARGE_ADDRESS_AWARE       0x0020  // App can handle >2gb addresses
  7. #define IMAGE_FILE_BYTES_REVERSED_LO         0x0080  // Bytes of machine word are reversed.
  8. #define IMAGE_FILE_32BIT_MACHINE             0x0100  // 32 bit word machine.
  9. #define IMAGE_FILE_DEBUG_STRIPPED            0x0200  // Debugging info stripped from file in .DBG file
  10. #define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP   0x0400  // If Image is on removable media, copy and run from the swap file.
  11. #define IMAGE_FILE_NET_RUN_FROM_SWAP         0x0800  // If Image is on Net, copy and run from the swap file.
  12. #define IMAGE_FILE_SYSTEM                    0x1000  // System File.
  13. #define IMAGE_FILE_DLL                       0x2000  // File is a DLL.
  14. #define IMAGE_FILE_UP_SYSTEM_ONLY            0x4000  // File should only be run on a UP machine
  15. #define IMAGE_FILE_BYTES_REVERSED_HI         0x8000  // Bytes of machine word are reversed.

可以看出,PE文件头定义了PE文件的一些基本信息和属性,这些属性会在PE加载器加载时用到,如果加载器发现PE文件头中定义的一些属性不满足当前的运行环境,将会终止加载该PE。

另一个重要的头就是PE可选头,别看他名字叫可选头,其实一点都不能少,不过,它在不同的平台下是不一样的,例如32位下是IMAGE_OPTIONAL_HEADER32,而在64位下是IMAGE_OPTIONAL_HEADER64。为了简单起见,我们只看32位。

[cpp] view plaincopy

  1. typedef struct _IMAGE_OPTIONAL_HEADER {
  2. WORD    Magic;
  3. BYTE    MajorLinkerVersion;
  4. BYTE    MinorLinkerVersion;
  5. DWORD   SizeOfCode;
  6. DWORD   SizeOfInitializedData;
  7. DWORD   SizeOfUninitializedData;
  8. DWORD   AddressOfEntryPoint;
  9. DWORD   BaseOfCode;
  10. DWORD   BaseOfData;
  11. DWORD   ImageBase;
  12. DWORD   SectionAlignment;
  13. DWORD   FileAlignment;
  14. WORD    MajorOperatingSystemVersion;
  15. WORD    MinorOperatingSystemVersion;
  16. WORD    MajorImageVersion;
  17. WORD    MinorImageVersion;
  18. WORD    MajorSubsystemVersion;
  19. WORD    MinorSubsystemVersion;
  20. DWORD   Win32VersionValue;
  21. DWORD   SizeOfImage;
  22. DWORD   SizeOfHeaders;
  23. DWORD   CheckSum;
  24. WORD    Subsystem;
  25. WORD    DllCharacteristics;
  26. DWORD   SizeOfStackReserve;
  27. DWORD   SizeOfStackCommit;
  28. DWORD   SizeOfHeapReserve;
  29. DWORD   SizeOfHeapCommit;
  30. DWORD   LoaderFlags;
  31. DWORD   NumberOfRvaAndSizes;
  32. IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
  33. } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

Magic:表示可选头的类型。

[cpp] view plaincopy

  1. #define IMAGE_NT_OPTIONAL_HDR32_MAGIC      0x10b  // 32位PE可选头
  2. #define IMAGE_NT_OPTIONAL_HDR64_MAGIC      0x20b  // 64位PE可选头
  3. #define IMAGE_ROM_OPTIONAL_HDR_MAGIC       0x107

MajorLinkerVersion和MinorLinkerVersion:链接器的版本号。

SizeOfCode:代码段的长度,如果有多个代码段,则是代码段长度的总和。

SizeOfInitializedData:初始化的数据长度。

SizeOfUninitializedData:未初始化的数据长度。

AddressOfEntryPoint:程序入口的RVA,对于exe这个地址可以
理解为WinMain的RVA。对于DLL,这个地址可以理解为DllMain的RVA,如果是驱动程序,可以理解为DriverEntry的RVA。当
然,实际上入口点并非是WinMain,DllMain和DriverEntry,在这些函数之前还有一系列初始化要完成,当然,这些不是本文的重点。

BaseOfCode:代码段起始地址的RVA。

BaseOfData:数据段起始地址的RVA。

ImageBase:映象(加载到内存中的PE文件)的基地址,这个基地址是建议,对于DLL来说,如果无法加载到这个地址,系统会自动为其选择地址。

SectionAlignment:节对齐,PE中的节被加载到内存时会按照这个域指定的值来对齐,比如这个值是0x1000,那么每个节的起始地址的低12位都为0。

FileAlignment:节在文件中按此值对齐,SectionAlignment必须大于或等于FileAlignment。

MajorOperatingSystemVersion、MinorOperatingSystemVersion:所需操作系统的版本号,随着操作系统版本越来越多,这个好像不是那么重要了。

MajorImageVersion、MinorImageVersion:映象的版本号,这个是开发者自己指定的,由连接器填写。

MajorSubsystemVersion、MinorSubsystemVersion:所需子系统版本号。

Win32VersionValue:保留,必须为0。

SizeOfImage:映象的大小,PE文件加载到内存中空间是连续的,这个值指定占用虚拟空间的大小。

SizeOfHeaders:所有文件头(包括节表)的大小,这个值是以FileAlignment对齐的。

CheckSum:映象文件的校验和。

Subsystem:运行该PE文件所需的子系统,可以是下面定义中的某一个:

[cpp] view plaincopy

  1. #define IMAGE_SUBSYSTEM_UNKNOWN              0   // Unknown subsystem.
  2. #define IMAGE_SUBSYSTEM_NATIVE               1   // Image doesn‘t require a subsystem.
  3. #define IMAGE_SUBSYSTEM_WINDOWS_GUI          2   // Image runs in the Windows GUI subsystem.
  4. #define IMAGE_SUBSYSTEM_WINDOWS_CUI          3   // Image runs in the Windows character subsystem.
  5. #define IMAGE_SUBSYSTEM_OS2_CUI              5   // image runs in the OS/2 character subsystem.
  6. #define IMAGE_SUBSYSTEM_POSIX_CUI            7   // image runs in the Posix character subsystem.
  7. #define IMAGE_SUBSYSTEM_NATIVE_WINDOWS       8   // image is a native Win9x driver.
  8. #define IMAGE_SUBSYSTEM_WINDOWS_CE_GUI       9   // Image runs in the Windows CE subsystem.
  9. #define IMAGE_SUBSYSTEM_EFI_APPLICATION      10  //
  10. #define IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER  11   //
  11. #define IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER   12  //
  12. #define IMAGE_SUBSYSTEM_EFI_ROM              13
  13. #define IMAGE_SUBSYSTEM_XBOX                 14
  14. #define IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION 16

DllCharacteristics:DLL的文件属性,只对DLL文件有效,可以是下面定义中某些的组合:

[cpp] view plaincopy

  1. #define IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 0x0040     // DLL can move.
  2. #define IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY    0x0080     // Code Integrity Image
  3. #define IMAGE_DLLCHARACTERISTICS_NX_COMPAT    0x0100     // Image is NX compatible
  4. #define IMAGE_DLLCHARACTERISTICS_NO_ISOLATION 0x0200     // Image understands isolation and doesn‘t want it
  5. #define IMAGE_DLLCHARACTERISTICS_NO_SEH       0x0400     // Image does not use SEH.  No SE handler may reside in this image
  6. #define IMAGE_DLLCHARACTERISTICS_NO_BIND      0x0800     // Do not bind this image.
  7. //                                            0x1000     // Reserved.
  8. #define IMAGE_DLLCHARACTERISTICS_WDM_DRIVER   0x2000     // Driver uses WDM model
  9. //                                            0x4000     // Reserved.
  10. #define IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE     0x8000

SizeOfStackReserve:运行时为每个线程栈保留内存的大小。

SizeOfStackCommit:运行时每个线程栈初始占用内存大小。

SizeOfHeapReserve:运行时为进程堆保留内存大小。

SizeOfHeapCommit:运行时进程堆初始占用内存大小。

LoaderFlags:保留,必须为0。

NumberOfRvaAndSizes:数据目录的项数,即下面这个数组的项数。

DataDirectory:数据目录,这是一个数组,数组的项定义如下:

[cpp] view plaincopy

  1. typedef struct _IMAGE_DATA_DIRECTORY {
  2. DWORD   VirtualAddress;
  3. DWORD   Size;
  4. } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

VirtualAddress:是一个RVA。

Size:是一个大小。

这两个数有什么用呢?一个是地址,一个是大小,可以看出这个数据目录项定义的是一个区域。那他定义的是什么东西的区域呢?前面说了,DataDirectory是个数组,数组中的每一项对应一个特定的数据结构,包括导入表,导出表等等,根据不同的索引取出来的是不同的结构,头文件里定义各个项表示哪个结构,如下面的代码所示:

[cpp] view plaincopy

  1. #define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory
  2. #define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory
  3. #define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory
  4. #define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory
  5. #define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory
  6. #define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table
  7. #define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory
  8. //      IMAGE_DIRECTORY_ENTRY_COPYRIGHT       7   // (X86 usage)
  9. #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data
  10. #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of GP
  11. #define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS Directory
  12. #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // Load Configuration Directory
  13. #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers
  14. #define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table
  15. #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors
  16. #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor

看到这么多的定义,大家估计要头疼了,好不容易要把PE文件头学习完了,又“从天而降”一大波的结构。不用紧张,有了前面的知识,后面的部分就迎刃而解了。下一篇开始将沿着这个数据目录分解其余部分,继续关注哦~

时间: 2024-09-27 00:55:01

PE文件结构详解(二)可执行文件头的相关文章

PE文件结构详解(四)PE导入表

PE文件结构详解(二)可执行文件头的最后展示了一个数组,PE文件结构详解(三)PE导出表中解释了其中第一项的格式,本篇文章来揭示这个数组中的第二项:IMAGE_DIRECTORY_ENTRY_IMPORT,即导入表. 也许大家注意到过,在IMAGE_DATA_DIRECTORY中,有几项的名字都和导入表有关系,其中包括:IMAGE_DIRECTORY_ENTRY_IMPORT,IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT,IMAGE_DIRECTORY_ENTRY_IAT

PE文件结构详解(三)PE导出表

上篇文章 PE文件结构详解(二)可执行文件头 的结尾出现了一个大数组,这个数组中的每一项都是一个特定的结构,通过函数获取数组中的项可以用RtlImageDirectoryEntryToData函数,DataDirectory中的每一项都可以用这个函数获取,函数原型如下: PVOID NTAPI RtlImageDirectoryEntryToData(PVOID Base, BOOLEAN MappedAsImage, USHORT Directory, PULONG Size); Base:模

PE文件结构详解(五)延迟导入表

PE文件结构详解(四)PE导入表讲 了一般的PE导入表,这次我们来看一下另外一种导入表:延迟导入(Delay Import).看名字就知道,这种导入机制导入其他DLL的时机比较“迟”,为什么要迟呢?因为有些导入函数可能使用的频率比较低,或者在某些特定的场 合才会用到,而有些函数可能要在程序运行一段时间后才会用到,这些函数可以等到他实际使用的时候再去加载对应的DLL,而没必要再程序一装载就初始化好. 这个机制听起来很诱人,因为他可以加快启动速度,我们应该如何利用这项机制呢?VC有一个选项,可以让我

PE文件结构详解(六)重定位

前面两篇 PE文件结构详解(四)PE导入表 和 PE文件结构详解(五)延迟导入表 介绍了PE文件中比较常用的两种导入方式,不知道大家有没有注意到,在调用导入函数时系统生成的代码是像下面这样的: 在这里,IE的iexplorer.exe导入了Kernel32.dll的GetCommandLineA函数,可以看到这是个间接call,00401004这个地址的内存里保存了目的地址, 根据图中显示的符号信息可知,00401004这个地址是存在于iexplorer.exe模块中的,实际上也就是一项IAT的

PE文件结构详解

1.定位标准PE头 DOS Stub长度不固定,所以DOS头不是一个固定大小的数据结构.DOS头位于PE的起始位置,通过DOS头去定位后面标准PE头的位置就是通过字段e_lfanew. e_lfanew字段的值是一个相对偏移量,绝对定位时需要加上DOS MZ头的基地址. 也就是PE头的绝对位置是: PE_start = DOS MZ 基地址+IMAGE_DOS_HEADER.e_lfanew 2.PE文件结构 在32位系统下,最重要的部分是PE头和PE数据区. 32位系统下的PE文件被划分为:D

PE文件结构详解(一)基本概念

PE(Portable Execute) 文件是Windows下可执行文件的总称,常见的有DLL,EXE,OCX,SYS等,事实上,一个文件是否是PE文件与其扩展名无关,PE文件可以是任 何扩展名.那Windows是怎么区分可执行文件和非可执行文件的呢?我们调用LoadLibrary传递了一个文件名,系统是如何判断这个文件是一个合 法的动态库呢?这就涉及到PE文件结构了. PE文件的结构一般来说如下图所示:从起始位置开始依次是DOS头,NT头,节表以及具体的节. DOS头是用来兼容MS-DOS操

PE 文件格式 详解 二

内容引用自:看雪<逆向工程原理>,http://www.blogfshare.com/pe-header-one.html .如有错误,欢迎留言. 1. 区块表(节表) 区块表紧跟在PE头后面,所有区块的属性都被定义在区块表中.区块表中的数据仅仅是因为属性相同被放到一起,对程序的各种方法.数据的追溯还是要用到DataDirectory. 区块表是由一组IMAGE_SETION_HEADER结构组成,每个结构描述一个区块,各结构的排列顺序与其所描述的区块在文件中的排列顺序是一致的. 区块表最后以

UINavigationController详解二(转)页面切换和SegmentedController

原文出自:http://blog.csdn.net/totogo2010/article/details/7682433,非常感谢. 1.RootView 跳到SecondView 首先我们需要新一个View.新建SecondView,按住Command键然后按N,弹出新建页面,我们新建SecondView 2.为Button 添加点击事件,实现跳转 在RootViewController.xib中和RootViewController.h文件建立连接 在RootViewController.m

Android 布局学习之——Layout(布局)详解二(常见布局和布局参数)

[Android布局学习系列]   1.Android 布局学习之——Layout(布局)详解一   2.Android 布局学习之——Layout(布局)详解二(常见布局和布局参数)   3.Android 布局学习之——LinearLayout的layout_weight属性   4.Android 布局学习之——LinearLayout属性baselineAligned的作用及baseline    Layout Parameters(布局参数): 在XML文件中,我们经常看到类似与lay