Acquaintance PE file structure

Foreword

PE article on the current network file structure too much, your article simply record their own learning PE file structure, understand and summarize.

Basic concepts

PE (Portable Executable: portable executable) Win32 environment is brought their own executable file format. Some of its features inherited from the Unix Coff (Common Object File Format) file format. Portable executable file format means that this is a cross-win32 platform, even Windows running on a non-Intel CPU, PE loader any win32 platform can identify and use the file format. Of course, transplanted to a different CPU PE executable necessarily have to have some changes. In addition to the VxD and 16 Dll, all files are executed using win32 PE file format. Therefore, the study PE file format is Windows we know the structure of opportunity.

File Structure

Structure charts:

DOS header is used MS-DOS compatible operating system
NT header information Windows PE file containing the main
section table: PE file is described in the following section
Section: actually a section of each container can contain code, data and the like, each section can have independent authority memory, such as the default code section read / execute permissions, the name and number of sections can define their own

Address file

1, PE files on the hard disk and in memory is not exactly the same, after being loaded into the memory occupied by the virtual address space than occupy space on the hard disk larger because each section is continuous on the hard drive while in memory are page-aligned.

2, the internal structure of PE, the address indicating a location using two methods, the address on the hard disk for storing files, referred to as original or a physical memory address represents an offset address from the header; the other is for loading after the memory map of the address, called the relative virtual address (the RVA), a memory map showing the relative offset of the head.

3, certain instructions of the CPU is required to use an absolute address, such as taking the address of global variables, after compiling the address transfer functions certainly need to use the assembly instruction absolute addresses rather than relative offset image head so PE file recommend operating system loaded into a memory address (this is called the base address), this representation is called a virtual address (VA)

4, PE file can not be loaded into the expected address, the system will help him re-select a suitable base address to load him into here, this time on all existing VA fails, NT first save the file to load the required PE information before I do not know which PE is loaded into the base address, VA is invalid, so the PE file header to represent the most use RVA address

Executable header

1, PE file can be exported function so that other PE files can also be imported from some other PE files

2, PE file by deriving table indicates those functions derive their own, which indicates the need to import function module which import table.

3, DOS and NT head is the PE file header file header two important

DOS head

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;

Focus on field

e_magic: a WORD type, is a constant value 0x4D5A, see the value of the bit 'MZ' text editor, are executable must begin 'MZ'.

e_lfanew: an executable file for the extended 32-bit field, used to indicate the offset after the DOS header NT head relative file start address.

NT head

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

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

IMAGE_FILE_HEADER是PE文件头

typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;
    WORD    NumberOfSections;
    DWORD   TimeDateStamp;
    DWORD   PointerToSymbolTable;
    DWORD   NumberOfSymbols;
    WORD    SizeOfOptionalHeader;
    WORD    Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

PE文件头

Machine:该文件的运行平台,是x86、x64还是I64

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

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

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

NumberOfSymbols:符号表的数量。

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

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

PE可选头

typedef 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;

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。

SizeOfImage:映象的大小,PE文件加载到内存中空间是连续的,这个值指定占用虚拟空间的大小。
SizeOfHeaders:所有文件头(包括节表)的大小,这个值是以FileAlignment对齐的。

CheckSum:映象文件的校验和。

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

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

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

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

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

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

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

DataDirectory数据目录

 

#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

PE导出表

导出表是用来描述模块中的导出函数的结构,如果一个模块导出了函数,那么这个函数会被记录在导出表中,这样通过GetProcAddress函数就能动态获取到函数的地址。函数导出的方式有两种,一种是按名字导出,一种是按序号导出。这两种导出方式在导出表中的描述方式也不相同。

导出表定义:

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;
    DWORD   TimeDateStamp;
    WORD    MajorVersion;
    WORD    MinorVersion;
    DWORD   Name;
    DWORD   Base;
    DWORD   NumberOfFunctions;
    DWORD   NumberOfNames;
    DWORD   AddressOfFunctions;     // RVA from base of image
    DWORD   AddressOfNames;         // RVA from base of image
    DWORD   AddressOfNameOrdinals;  // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

图表:

 

PE导入表

IMAGE_DIRECTORY_ENTRY_IMPORT就是导入表,在PE文件加载时,会根据这个表里的内容加载依赖的DLL,并填充所需函数的地址

IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT叫做绑定导入表,在第一种导入表导入地址的修正是在PE加载时完成,如果一个PE文件导入的DLL或者函数多那么加载起来就会略显的慢一些,所以出现了绑定导入,在加载以前就修正了导入表,这样就会快一些。

IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT叫做延迟导入表,一个PE文件也许提供了很多功能,也导入了很多其他DLL,但是并非每次加载都会用到它提供的所有功能,也不一定会用到它需要导入的所有DLL,因此延迟导入就出现了,只有在一个PE文件真正用到需要的DLL,这个DLL才会被加载,甚至于只有真正使用某个导入函数,这个函数地址才会被修正。

IMAGE_DIRECTORY_ENTRY_IAT是导入地址表,前面的三个表其实是导入函数的描述,真正的函数地址是被填充在导入地址表中的。

重定位

Windows使用重定位机制保证代码无论模块加载到哪个基址都能正确被调用。

编译的时候由编译器识别出哪些项使用了模块内的直接VA,比如push一个全局变量、函数地址,这些指令的操作数在模块加载的时候就需要被重定位。

链接器生成PE文件的时候将编译器识别的重定位的项纪录在一张表里,这张表就是重定位表,保存在DataDirectory中,序号是 IMAGE_DIRECTORY_ENTRY_BASERELOC。

PE文件加载时,PE 加载器分析重定位表,将其中每一项按照现在的模块基址进行重定位。

每个重定位项应该是一个DWORD,里面保存需要重定位的RVA,这样只需要简单操作便能找到需要重定位的项。

然而,Windows并没有这样设计,原因是这样存放太占用空间了,试想一下,加入一个文件有n个重定位项,那么就需要占用4*n个字节。

所以Windows采用了分组的方式,按照重定位项所在的页面分组,每组保存一个页面起始地址的RVA,页内的每项重定位项使用一个WORD保存重定位项在页内的偏移,这样就大大缩小了重定位表的大小。

定义:

typedef struct _IMAGE_BASE_RELOCATION {
    DWORD   VirtualAddress;
    DWORD   SizeOfBlock;
//  WORD    TypeOffset[1];
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;

VirtualAddress:页起始地址RVA。

SizeOfBlock:表示该分组保存了几项重定位项。

TypeOffset:这个域有两个含义,页内偏移用12位就可以表示,剩下的高4位用来表示重定位的类型。而事实上,Windows只用了一种类型IMAGE_REL_BASED_HIGHLOW数值是 3。

哪些项目需要被重定位呢??

1.代码中使用全局变量的指令,因为全局变量一定是模块内的地址,而且使用全局变量的语句在编译后会产生一条引用全局变量基地址的指令。

2.将模块函数指针赋值给变量或作为参数传递,因为赋值或传递参数是会产生mov和push指令,这些指令需要直接地址。

3.C++中的构造函数和析构函数赋值虚函数表指针,虚函数表中的每一项本身就是重定位项

区段名及其含义

.text默认的代码区块,它的内容全是指令代码,链接器把所有目标文件的text块连接成一个大的.text块,

.data默认的读/写数据块,全局变量,静态变量一般放在这个区段

.rdata默认只读数据区块,但程序中很少用到该块中的数据,一般两种情况用到,一是MS 的链接器产生EXE文件中用于存放调试目录,二是用于存放说明字符串,如果程序的DEF文件中指定了DESCRIPTION,字符串就会出现在rdata中

.idata包含其他外来的DLL的函数及数据信息,即输入表,将.idata区块合并成另一个区块已成为一种惯例

.edata输出表,当创建一个输出API或数据的可执行文件时,连接器会创建一个.EXP文件,这个.EXP文件包含一个.edata区块,其会被加载到可执行文件中,经常被合并到.text或.rdata 区块中

.rsrc资源,包括模块的全部资源,如图标,菜单,位图等,这个区块是只读的,无论如何不应该把它命名为.rsrc以外的名字,也不能合并到其他的区块里

.bss未初始化的数据,很少在用,取而代之的是执行文件的.data区块的的VirtualSize被扩展大的空间里用来装未初始化的数据.

.crt用于C++ 运行时(CRT)所添加的数据

.tlsTLS的意思是线程局部存储器,用于支持通过_declspec(thread)声明的线程局部存储变量的数据,这包括数据的初始化值,也包括运行时所需要的额外变量

.reloc可执行文件的基址重定位,基址重定位一般仅Dll需要的

.sdata相对于全局指针的可被定位的 短的读写数据

.pdata异常表,包含CPU特定的IAMGE_RUNTIME_FUNTION_ENTRY结构数组,DataDirectory中的IMAGE_DIRECTORY_ENTRY_EXCEPTION指向它.

.didat延迟装入输入数据,在非Release模式下可以找到

装载PE文件的主要步骤 

第一:当PE文件被执行,PE装载器检查DOS MZ header里的PE header偏移量。如果找到,则跳转到PE header。

第二:PE装载器检查PE header的有效性。如果有效,就跳转到PE header的尾部。

第三:紧跟PE header的是节表。PE装载器读取其中的节索引信息,并采用文件映射方法将这些节映射到内存,同时附上节表里指定的节属性。

第四:PE文件映射入内存后,PE装载器将处理PE文件中类似import table(引入表)逻辑部分。

 

Guess you like

Origin www.cnblogs.com/qftm/p/11611038.html