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

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

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

一、DOS头

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

  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文件中最有用的头,定义如下:

  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语言的定义是这样的:

  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等等,可以是下面值里的某一个。

  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:可执行文件的属性,可以是下面这些值按位相或。

  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位。

  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:表示可选头的类型。

  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文件所需的子系统,可以是下面定义中的某一个:

  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文件有效,可以是下面定义中某些的组合:

  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:数据目录,这是一个数组,数组的项定义如下:

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

VirtualAddress:是一个RVA。

Size:是一个大小。

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

  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文件头学习完了,又“从天而降”一大波的结构。不用紧张,有了前面的知识,后面的部分就迎刃而解了。下一篇开始将沿着这个数据目录分解其余部分,继续关注哦~

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

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

    PE文件结构详解(二)可执行文件头的最后展示了一个数组,PE文件结构详解(三)PE导出表中解释了其中第一项的格式,本篇文章来揭示这个数组中的第二项:IMAGE_DIRECTORY_ENTRY_IMPO ...

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

    上篇文章 PE文件结构详解(二)可执行文件头 的结尾出现了一个大数组,这个数组中的每一项都是一个特定的结构,通过函数获取数组中的项可以用RtlImageDirectoryEntryToData函数,D ...

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

    前面两篇 PE文件结构详解(四)PE导入表 和 PE文件结构详解(五)延迟导入表 介绍了PE文件中比较常用的两种导入方式,不知道大家有没有注意到,在调用导入函数时系统生成的代码是像下面这样的: 在这里 ...

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

    PE文件结构详解(四)PE导入表讲 了一般的PE导入表,这次我们来看一下另外一种导入表:延迟导入(Delay Import).看名字就知道,这种导入机制导入其他DLL的时机比较“迟”,为什么要迟呢?因 ...

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

    PE(Portable Execute) 文件是Windows下可执行文件的总称,常见的有DLL,EXE,OCX,SYS等,事实上,一个文件是否是PE文件与其扩展名无关,PE文件可以是任 何扩展名.那 ...

  6. PE文件详解二

    本文转自小甲鱼的PE文件相关教程,原文传送门 咱接着往下讲解IMAGE_OPTIONAL_HEADER32 结构定义即各个属性的作用! 接着我们来谈谈 IMAGE_OPTIONAL_HEADER 结构 ...

  7. PE文件结构详解

    (注:最左边是文件头的偏移量.) IMAGE_DOS_HEADER STRUCT { +0h WORD e_magic // Magic DOS signature MZ(4Dh 5Ah) DOS可执 ...

  8. PE文件结构详解(三)

    0x01 前言 上一篇讲到了数据目录表的结构和怎找到到数据目录表(DataDirectory[16]),这篇我们我来讲讲数据目录表后面的另一个结构——区块表. 0x01 区块 区块就是PE载入器将PE ...

  9. PE文件格式详解,第一讲,DOS头文件格式

    PE文件格式详解,第一讲,DOS头文件格式 今天讲解PE文件格式的DOS头文件格式 首先我们要理解,什么是文件格式,我们常说的EXE可执行程序,就是一个文件格式,那么我们要了解它里面到底存了什么内容 ...

随机推荐

  1. php面向对象的基础:创建OOP的方法

    方法的创建 class Computer{ public function _run(){ return '我是类的一个公共方法'; } } $computer = new Computer(); / ...

  2. pop()实现逐个删除数组最后一位并输出

    使用pop()循环输出数组的最后一个元素 var a = []; a.push(1);a.push(3.1415926);a.push("number");a.push(" ...

  3. Java线程面试题 Top 50(转载)

    原文链接:http://www.importnew.com/12773.html 本文由 ImportNew - 李 广 翻译自 javarevisited.欢迎加入Java小组.转载请参见文章末尾的 ...

  4. Windows 命令大全

    打开控制面板的方法:输入control,回车即可打开. 以下是“运行”里常见的命令: gpedit.msc-----组策略 sndrec32-------录音机 Nslookup-------IP地址 ...

  5. 简单版解决IE兼容性问题

    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> 在访问页面时, 如 ...

  6. zedboard U盘挂载+交叉编译helloworld

    交叉编译环境见http://blog.csdn.net/xiabodan/article/details/22717175 1:编写hello.c文件 #include<stdio.h> ...

  7. CSS精粹之布局技巧

    1.若有疑问立即检测 在出错时若能对原始代码做简单检测可以省去很多头痛问题.W3C对于XHTML与CSS都有检测工具可用,请见http://validator.w3.org 请注意,在文件开头的错误, ...

  8. NSS_07 extjs中grid在工具条上的查询

    碰到的每个问题, 我都会记下走过的弯路,尽量回忆白天的开发过程, 尽量完整, 以使自己以后可以避开这些弯路. 这个问题在系统中应用得比较多, 在一个gridpanel的工具条上有俩搜索框, panel ...

  9. 【转】让iframe在iOS设备手机浏览器上支持滚动

    HTML代码 在使用IFRAME或者其他HTML元素时,你需要使用一个元素(如DIV)来包装他们: <div class="scroll-wrapper"> <i ...

  10. Template、ItemsPanel、ItemContainerStyle、ItemTemplate

    先来看一张图(网上下的图,加了几个字) 1.Template是指控件的样式 在WPF中所有继承自contentcontrol类的控件都含有此属性,(继承自FrameworkElementdl类的Tex ...