PE文件结构(懒的写博客我直接贴代码还不行么!)

时间:2022-06-01 19:43:00
1.常见PE文件
可执行文件:EXE,SCR,COM
驱动程序:SYS,VSD
库文件:DLL,OCX,CPL,DRV
对象文件:OBJ

2.PE32(32位)、PE32+或者PE+(64位)

3.PE组成:
DOS头
DOS存根
NT头
节区头(代码段)
节区头(数据段)
节区头(资源段)
节区(代码段)
节区(数据段)
节区(资源段)

4.VA(virtual address:虚拟地址)&RVA(relative virtual address:相对虚拟地址)
VA=RVA+ImageBase(映象基址)

5.DOS头
IMAGE_DOS_HEADER STRUCT

{

+0h WORD e_magic    // Magic DOS signature MZ(4Dh 5Ah) DOS可执行文件标记

+2h WORD e_cblp    // Bytes on last page of file 最后一页大小

+4h WORD e_cp    // Pages in file 文件页数

+6h WORD e_crlc    // Relocations

+8h WORD e_cparhdr   // Size of header in paragraphs 头的段大小

+0ah WORD e_minalloc   // Minimun extra paragraphs needs 最小额外段需求

+0ch WORD e_maxalloc  // Maximun extra paragraphs needs 最大额外段需求

+0eh WORD e_ss            // intial(relative)SS value DOS代码的初始化堆栈SS

+10h WORD e_sp     // intial SP value DOS代码的初始化堆栈指针SP

+12h WORD e_csum     // Checksum 校验和

+14h WORD e_ip     // intial IP value DOS代码的初始化指令入口[指针IP]

+16h WORD e_cs     // intial(relative)CS value DOS代码的初始堆栈入口

+18h WORD e_lfarlc     // File Address of relocation table 重定位表的文件地址

+1ah WORD e_ovno         // Overlay number

+1ch WORD e_res[4]      // Reserved words 保留字

+24h WORD e_oemid      // OEM identifier(for e_oeminfo)

+26h WORD e_oeminfo   // OEM information;e_oemid specific

+29h WORD e_res2[10]        // Reserved words 保留字

+3ch DWORD e_lfanew         //Offset to start of PE header NT头地址

} IMAGE_DOS_HEADER ENDS


6.DOS存根
DOS存根是16位的汇编指令,用以在DOS中运行

7.NT头
typedef struct _IMAGE_NT_HEADERS {  
    +00h DWORD Signature;  //PE签名 固定为 "PE"00(0X50450000)
    +04h IMAGE_FILE_HEADER FileHeader;  //文件头 大小为0xF8
    +18h IMAGE_OPTIONAL_HEADER32 OptionalHeader;//可选头
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32; 

//文件头
typedef struct _IMAGE_FILE_HEADER {  
    +00h WORD    Machine;  //CPU机器码,I386对应为0x014c
    +02h WORD    NumberOfSections; //节区数量
    +04h DWORD   TimeDateStamp;  //PE文件的创建时间,一般有连接器填写。
    +08h DWORD   PointerToSymbolTable; //COFF文件符号表在文件中的偏移。
    +0ch DWORD   NumberOfSymbols;  //符号表的数量。
    +10h WORD    SizeOfOptionalHeader;  //可选头大小
    +12h WORD    Characteristics;  //文件属性标志(用或计算) 常见:0x0002:可执行文件 0x2000:DLL文件
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;  

//可选头,以32位下可选头为例
typedef struct _IMAGE_OPTIONAL_HEADER {  
   +00h WORD    Magic;  //可选头类型
   +02h BYTE    MajorLinkerVersion; //连接器版本号 
   +03h BYTE    MinorLinkerVersion;  //连接器版本号 
   +04h DWORD   SizeOfCode;  //代码段的长度,如果有多个代码段,则是代码段长度的总和。
   +08h DWORD   SizeOfInitializedData;  //初始化的数据长度。
   +0ch DWORD   SizeOfUninitializedData;  //未初始化的数据长度。
   +10h DWORD   AddressOfEntryPoint;  //程序的EP(RVA表示)
   +14h DWORD   BaseOfCode;  //代码段起始地址的RVA。
   +18h DWORD   BaseOfData;  //数据段起始地址的RVA。
   +1ch DWORD   ImageBase;  //映象(加载到内存中的PE文件)的基地址,这个基地址是建议,对于DLL来说,如果无法加载到这个地址,系统会自动为其选择地址。
   +20h DWORD   SectionAlignment; //节对齐,PE中的节被加载到内存时会按照这个域指定的值来对齐,比如这个值是0x1000,那么每个节的起始地址的低12位都为0。 
   +24h DWORD   FileAlignment;//节在文件中按此值对齐,SectionAlignment必须大于或等于FileAlignment。 
   +28h WORD    MajorOperatingSystemVersion;  //操作系统版本号
   +2ah WORD    MinorOperatingSystemVersion;  //操作系统版本号
   +2ch WORD    MajorImageVersion;  //映象的版本号,这个是开发者自己指定的,由连接器填写。
   +2eh WORD    MinorImageVersion;  //映象的版本号,这个是开发者自己指定的,由连接器填写。
   +30h WORD    MajorSubsystemVersion;  //所需子系统版本号。
   +32h WORD    MinorSubsystemVersion;  //所需子系统版本号。
   +34h DWORD   Win32VersionValue;  //保留,必须为0。
   +38h DWORD   SizeOfImage;  //映象的大小,PE文件加载到内存中空间是连续的,这个值指定占用虚拟空间的大小。
   +3ch DWORD   SizeOfHeaders;  //所有文件头(包括节表)的大小,这个值是以FileAlignment对齐的。
   +40h DWORD   CheckSum;  //映象文件的校验和。
   +44h WORD    Subsystem;  //运行该PE文件所需的子系统(1.系统驱动,2.窗口应用程序,3.控制台应用)
   +46h WORD    DllCharacteristics;  //DLL的文件属性
   +48h DWORD   SizeOfStackReserve;  //运行时为每个线程栈保留内存的大小。
   +4ch DWORD   SizeOfStackCommit;  //运行时每个线程栈初始占用内存大小。
   +50h DWORD   SizeOfHeapReserve;  //运行时为进程堆保留内存大小。
   +54h DWORD   SizeOfHeapCommit;  //运行时进程堆初始占用内存大小。
   +58h DWORD   LoaderFlags;  //保留,必须为0。
   +5eh DWORD   NumberOfRvaAndSizes;  //数据目录的项数,即下面这个数组的项数
   +60h IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];  //数据目录,这是一个数组,第i项含义在下边
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;  

typedef struct   {  
    +00h DWORD   VirtualAddress;  //RVA
    +04h DWORD   Size; //大小 
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; 

#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 

8.节区头
typedef struct _IMAGE_SECTION_HEADER {
    +00h BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];      //8个字节的区块名
    +08h union {                                     //区块尺寸
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    } Misc;
    +0ch DWORD   VirtualAddress;                     //区块的RVA地址
    +10h DWORD   SizeOfRawData;                      //文件对齐后的尺寸
    +14h DWORD   PointerToRawData;                   //文件偏移
    +18h DWORD   PointerToRelocations;
    +1ch DWORD   PointerToLinenumbers;
    +20h WORD    NumberOfRelocations;
    +22h WORD    NumberOfLinenumbers;
    +24h DWORD   Characteristics;                    //区块的属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

RVA与RAW换算(内存地址与文件偏移的转换)
RAW=RVA-VirtualAddress+PointerToRawData

9.IAT(Import Address Table:导入地址表):
INT和IAT是以NULL为结束的长整型数组,保存的是_IMAGE_IMPORT_BY_NAME, IMAGE_THUNK_DATA 的地址
_IMAGE_IMPORT_DESCRIPTOR:结构体中记录着PE文件要导入哪些库文件
typedef struct _IMAGE_IMPORT_DESCRIPTOR {   
    union {
        DWORD   Characteristics;            // 
        DWORD   OriginalFirstThunk;         // INT地址(RVA)
    };
    DWORD   TimeDateStamp;                  //
    DWORD   ForwarderChain;                 // 
    DWORD   Name;                           // RVA,指向字符串,是这个可执行文件的名字。例如"ACE.dll"
    DWORD   FirstThunk;                     // IAT地址(RVA)
} IMAGE_IMPORT_DESCRIPTOR;

typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD    Hint; ///该函数的导出序数
    BYTE    Name[1]; ///该函数的名字
} `, *PIMAGE_IMPORT_BY_NAME;

typedef struct _IMAGE_THUNK_DATA32 {
    union {
        DWORD ForwarderString;      // 一个RVA地址,指向forwarder string 
        DWORD Function;             // PDWORD,被导入的函数的入口地址
        DWORD Ordinal;              // 该函数的序数
        DWORD AddressOfData;        // 一个RVA地址,指向IMAGE_IMPORT_BY_NAME
    } u1;
} IMAGE_THUNK_DATA32;
IMAGE_THUNK_DATA64与IMAGE_THUNK_DATA32的区别,仅仅是把DWORD换成了64位整数。


10.EAT

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD Characteristics;
    DWORD TimeDateStamp;
    WORD MajorVersion;
    WORD MinorVersion;
    DWORD Name;                     //模块的内部名称,如果DLL文件的名字被用户改了,那么PE加载器会使用这个内部名称
    DWORD Base;                     //序号的起始编号,注意:起始序号可以不为0
    DWORD NumberOfFunctions;        //导出函数的个数
    DWORD NumberOfNames;            //导出函数中具名函数个数
    DWORD AddressOfFunctions;       //导出函数地址表所在地址(大小等于NumberOfNames)
    DWORD AddressOfNames;           //导出函数名称表所在地址(大小等于NumberOfNames)
    DWORD AddressOfNameOrdinals;    //导出函数序数表所在地址(大小等于NumberOfNames)
} IMAGE_EXPORT_DIRECTORY,*PIMAGE_EXPORT_DIRECTORY;