PE文件格式学习(二):总体结构

1.概述

PE文件分为几个部分,分别是:

  • DOS头
  • DOS Stub
  • NT头(PE头)
    • 文件头
    • 可选头
  • 区段头(一个数组,每个元素都是一个结构体,称之为IMAGE_SECTION_HEADER)
  • .text
  • .rdata
  • .data
  • .rsrc
  • .reloc
  • ...

本篇文章主要围绕整体结构做一个梳理,不会做太多细节的讲解。(比如说可选头里的数据目录表,它包含16种表,会在接下来的系列文章分别进行讲解,熟悉了数据目录表就可以说熟悉了PE)

2.DOS头和DOS Stub

DOS头和DOS Stub在现在的Windows系统上基本都是摆设(对于软件安全还是有一定价值的,PE的变形、压缩、植入都可能利用Stub部分,以后有机会我会写一个系列专门讲解PE文件操作相关的应用技术),它存在的原因是为了向下兼容、适配DOS系统,对于DOS头和它的Stub部分就只要稍微了解一些主要结构体字段就可以了。

PE文件开头的两个字节,在上篇文章介绍过,是字符“MZ”,也就是0x5A4D,它就是DOS头的开端,从这里开始的64个字节大小都是DOS头的范围。

DOS头的结构体是:

struct _IMAGE_DOS_HEADER{
    0X00 WORD magic;
    0X02 WORD e_cblp; 
    0X04 WORD e_cp;    
    0X06 WORD e_crlc;   
    0X08 WORD e_cparhdr;  
    0X0A WORD e_minalloc; 
    0X0C WORD e_maxalloc; 
    0X0E WORD e_ss;
    0X10 WORD e_sp;       
    0X12 WORD e_csum;     
    0X14 WORD e_ip;      
    0X16 WORD e_cs;       
    0X18 WORD e_lfarlc;   
    0X1A WORD e_ovno;     
    0x1C WORD e_res[4];  
    0x24 WORD e_oemid;   
    0x26 WORD e_oeminfo;  
    0x28 WORD e_res2[10]; 
    0x3C DWORD e_lfanew;
};

DOS头的结构体字段虽然很多,但是绝大部分现在都可以不用了解(除非你想做计算机历史学家,^_^)。对于不重要的字段就不解释了,下面出现的结构体也只会摘出重要的字段做出解释。

magic:它几乎永远是5A4D,也就是字符“MZ”。

e_lfanew:指向PE头的文件偏移。下图中e_lfanew的值是0x100,它也几乎总是这个值。PE头就在地址0x100处开始,它通常是0x00004550,也就是PE\0\0。

下图显示了一些信息,其中0x3c处就是e_lfanew!在十六进制工具中明明显示的是00010000,也就是0x10000啊!怎么会是0x100呢?这就需要了解一些小端排序的知识了。

保存在Windows系统中的数据基本上都是小端排序,数据读取的顺序不再是从左往右,而是从高地址往低地址,一个字节一个字节的读。上图中的e_flanew从高地址到低地址依次是:00 00 01 00,因此它其实是0x100。小端排序是一种CPU遵循的标准或者说格式,为了使CPU能够正确读数据,系统中的大部分程序都被编译成了小端排序(Java这样的基于虚拟机编写的程序除外)。

DOS头的大小是64个字节也就是0x40,从0x40开始到e_flanew指向的0x100为止的部分就是DOS Stub了。

DOS Stub是DOS系统的可执行文件,在DOS系统中会执行里面的代码,只需要将一个包含完整DOS头和DOS Stub的Win32程序放到DOS系统中运行就可以执行里面的代码了。

DOS头和DOS Stub就介绍到这里吧。

3.NT头

上面说道e_lfanew指向的文件偏移是0x100,它指向的位置的值是0x00004550,也几乎总是这个值,它转成ASCII码就是PE\0\0。这里就是NT头的开始。下面介绍一下NT头的结构体。

struct _IMAGE_NT_HEADERS{
    0x00 DWORD Signature;
    0x04 _IMAGE_FILE_HEADER FileHeader;
    0x18 _IMAGE_OPTIONAL_HEADER OptionalHeader;
};

Signatue:也就是“PE\0\0”,占4个字节。
FileHeader:文件头,本身也是一个结构体,后面会说
OptionalHeader:可选头,本身也是一个结构体,后面会说

3.1 文件头

文件头占20个字节,文件头也是一个结构体。

struct _IMAGE_FILE_HEADER{
    0x00 WORD Machine;
    0x02 WORD NumberOfSections;
    0x04 DWORD TimeDateStamp; 
    0x08 DWORD PointerToSymbolTable; 
    0x0c DWORD NumberOfSymbols;  
    0x10 WORD SizeOfOptionalHeader;   
    0x12 WORD Characteristics;
};

Machine:目标平台,对照下面的表

#define IMAGE_FILE_MACHINE_UNKNOWN           0
#define IMAGE_FILE_MACHINE_I386              0x014c  // Intel 386.(常见)
#define IMAGE_FILE_MACHINE_R3000             0x0162  // MIPS little-endian, 0x160 big-endian
#define IMAGE_FILE_MACHINE_R4000             0x0166  // MIPS little-endian
#define IMAGE_FILE_MACHINE_R10000            0x0168  // MIPS little-endian
#define IMAGE_FILE_MACHINE_WCEMIPSV2         0x0169  // MIPS little-endian WCE v2
#define IMAGE_FILE_MACHINE_ALPHA             0x0184  // Alpha_AXP
#define IMAGE_FILE_MACHINE_SH3               0x01a2  // SH3 little-endian
#define IMAGE_FILE_MACHINE_SH3DSP            0x01a3
#define IMAGE_FILE_MACHINE_SH3E              0x01a4  // SH3E little-endian
#define IMAGE_FILE_MACHINE_SH4               0x01a6  // SH4 little-endian
#define IMAGE_FILE_MACHINE_SH5               0x01a8  // SH5
#define IMAGE_FILE_MACHINE_ARM               0x01c0  // ARM Little-Endian
#define IMAGE_FILE_MACHINE_THUMB             0x01c2
#define IMAGE_FILE_MACHINE_AM33              0x01d3
#define IMAGE_FILE_MACHINE_POWERPC           0x01F0  // IBM PowerPC Little-Endian
#define IMAGE_FILE_MACHINE_POWERPCFP         0x01f1
#define IMAGE_FILE_MACHINE_IA64              0x0200  // Intel 64
#define IMAGE_FILE_MACHINE_MIPS16            0x0266  // MIPS
#define IMAGE_FILE_MACHINE_ALPHA64           0x0284  // ALPHA64
#define IMAGE_FILE_MACHINE_MIPSFPU           0x0366  // MIPS
#define IMAGE_FILE_MACHINE_MIPSFPU16         0x0466  // MIPS
#define IMAGE_FILE_MACHINE_AXP64             IMAGE_FILE_MACHINE_ALPHA64
#define IMAGE_FILE_MACHINE_TRICORE           0x0520  // Infineon
#define IMAGE_FILE_MACHINE_CEF               0x0CEF
#define IMAGE_FILE_MACHINE_EBC               0x0EBC  // EFI Byte Code
#define IMAGE_FILE_MACHINE_AMD64             0x8664  // AMD64 (K8)
#define IMAGE_FILE_MACHINE_M32R              0x9041  // M32R little-endian
#define IMAGE_FILE_MACHINE_CEE               0xC0EE

NumberOfSections:区段表数量

SizeOfOptionalHeader:可选头的大小(字节数):32位默认E0H,64位默认F0H(可修改)

Characteristics:文件类型,对照下面的表

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

3.2 可选头

介绍完NT头的Signature和文件头还有最后一个字段:可选头。虽然它叫可选头,但是它非常的重要,叫“必选头”才对!可选头包含了非常多的信息,它在32位系统中的大小是E0h(224个字节),64位系统中的大小是F0h(240个字节)。

它的结构体:

struct _IMAGE_OPTIONAL_HEADER {
    WORD    Magic;
    BYTE    MajorLinkerVersion;   
    BYTE    MinorLinkerVersion;       
    DWORD   SizeOfCode;           
    DWORD   SizeOfInitializedData;   
    DWORD   SizeOfUninitializedData;   
    DWORD   AddressOfEntryPoint;     
    DWORD   BaseOfCode;            
    DWORD   BaseOfData;           
    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;           
    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;

Magic:0x10B是32位PE,0x20B是64位PE,0x107是ROM映像

#define IMAGE_NT_OPTIONAL_HDR32_MAGIC      0x10b  // 32位PE可选头
#define IMAGE_NT_OPTIONAL_HDR64_MAGIC      0x20b  // 64位PE可选头
#define IMAGE_ROM_OPTIONAL_HDR_MAGIC       0x107  // ROM映像

AddressOfEntryPoint://程序入口的RVA值,程序最先执行代码的地址

BaseOfImage:PE文件的装载地址

SizeOfImage:内存中整个PE映像体的尺寸

SectionAlignment:区段在内存中的对齐值,一般为0x1000

FileAlignment:区段在文件中的对齐值,一般为0x200

SizeOfHeaders:所有头+节表的大小,即整个PE头的大小

DllCharacteristics:DLL文件的属性值,DLL文件才会生效,参见下面的表

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

NumberOfRvaAndSizes:数据目录成员的数量,一般为0x10

DataDirectory:数据目录表数组,每个结构给出一个重要数据结构的RVA和size,参见下面的表

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

数据目录表的结构体是这样的:

struct _IMAGE_DATA_DIRECTORY {
    DWORD VirtualAddress;
    DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

数据目录表的解析后面的文章会逐一介绍,它是PE文件格式的重中之重。

4.区段头和区段

区段头是一个数组,里面的每个元素都是一个结构体,这个结构体叫做IMAGE_SECTION_HEADER,每个结构体的大小是40个字节,如下:

struct _IMAGE_SECTION_HEADER {          
    BYTE Name[8];         
    union {                                     
        DWORD PhysicalAddress;              
                WORD VirtualSize;
    } Misc
    DWORD VirtualAddress;   
    DWORD SizeOfRawData;                        
    DWORD PointerToRawData;                     
    DWORD PointerToRelocations;                 
    DWORD PointerToLinenumbers;                 
    WORD NumberOfRelocations;                   
    WORD NumberOfLinenumbers;                   
    DWORD Characteristics;                                              
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; 

Name:区段名

PhysicalAddress:物理地址,它的值一般跟VirtualSize相同,所以它有些不准确。

VirtualSize:真实大小,区段未做对齐处理之前的大小,跟PhysicalAddress同属一个联合体,可以使用任何一个,一般使用VirtualSize。

VirtualAddress:区段在内存中的RVA

SizeOfRawData:区段在磁盘中的大小

PointerToRawData:区段在文件中的偏移

PointerToRelocations:在obj文件中使用,指向重定位表的指针

PointerToLinenumbers:行号表的位置,供调试使用,一般为0x0000

NumberOfRelocations:在obj文件中使用,此区段重定位表项的数量

NumberOfLinenumbers:行号表中的行号数量

Characteristics:区段属性,参见下表

IMAGE_SCN_CNT_CODE          0x00000020      该节区含代码
IMAGE_SCN_MEM_SHARED        0x10000000      该节区为可共享
IMAGE_SCN_MEM_EXECUTE       0x20000000      该节区为可执行
IMAGE_SCN_MEM_READ          0x40000000      该节区为可读
IMAGE_SCN_MEM_WRITE         0x80000000      该节区为可写

区段头介绍完了,已经说明过区段头是一个数组,文件头中有区段的数量,这个数量就是区段头数组的大小,紧接着区段头后面的就是各种区段,常见的区段见下面的表:

 .text: 默认的代码区块,内容都是指令代码 
 .data:默认的读写数据块,全局变量、静态变量一般放在这里。
 .rdata: 默认的只读数据块  一般很少用到。
 .idata:包含外来的DLL数据及数据信息,也就是输入表 ,通常情况下把他合并到.rdata中。
 .edata: 当创建一个用于输出数据的可执行文件时(输出表),数据会放在这里,通常情况下会被合并到.text 或.tdata中。
 .rsrs:资源块 包含一切图标菜单等。

5.总结

本文对PE的结构有了一个总体的介绍,到此应该对PE的结构有了一个整体性的了解。PE文件看似简单,实际上使用的时候灵活度很大,对PE文件的变形压缩等操作的前提是要对PE每一个字段、每一处结构有充分的了解,所以PE文件中重要的字段要非常的熟练。

文件结构中有一些字段需要记忆,以加深对PE的印象。

比如说,有大量对属性、类型的定义:

  1. DOS头中的magic字段:4D5A,"MZ"
  2. 文件头中的 Machine:目标平台的类型
  3. 文件头中的Characteristics:文件的类型
  4. 可选头中的Magic: 文件的类型,重点分别32位还是64位
  5. 可选头中的 DllCharacteristics:只有DLL文件才有效,定义DLL的一些属性
  6. 区段头中的Characteristics: 区段的属性

还有各个结构中的重点字段:

  1. DOS头中的magic、e_lfanew
  2. 文件头中的machine、characteristics、numberOfSections、sizeOfOptionalHeader
  3. 可选头中的magic、DllCharacteristics、addressOfEntry、imageOfBase、sizeOfHeaders、sizeOfImage
  4. 区段头中的VirtualSize、VirtualAddress、PointerToRawData、sizeOfRawData、numberOfRelocations、pointerToRelocations、numberOfLinenumbers、pointerToLinenumbers、name、Characteristics

下面的图标明了PE文件各部分的范围:

上图中,区段头有五个成员,每一个长度都是28h,意味着这个文件有5个区段。

区段头的后面5个区段依次排列。每个区段的起始点在区段头的pointerToRawdata中记录,长度在sizeOfRawData中记录。

再后面的内容存储了很多表的内容以及杂项。这些表都在可选头的数据目录表里记录,起始地址就保存在数据目录表里,下篇文章开始介绍数据目录表里的内容。

猜你喜欢

转载自www.cnblogs.com/tutucoo/p/9927304.html