PE文件结构

PE(Portable Execute)文件是Windows下可执行文件的总称,常见的有DLL,EXE,OCX,SYS等。

PE文件的结构一般来说如下图所示:从起始位置开始依次是DOS头,NT头,节表以及具体的节。

·  DOS头是用来兼容MS-DOS操作系统的,目的是当这个文件在MS-DOS上运行时提示一段文字,大部分情况下是:This program cannot be run in DOS mode.还有一个目的,就是指明NT头在文件中的位置。

·  NT头包含windows PE文件的主要信息,其中包括一个‘PE’字样的签名,PE文件头(IMAGE_FILE_HEADER)和PE可选头(IMAGE_OPTIONAL_HEADER32),头部的详细结构以及其具体意义在PE文件头文章中详细描述。

·  节表:是PE文件后续节的描述,windows根据节表的描述加载每个节。

·  节:每个节实际上是一个容器,可以包含代码、数据等等,每个节可以有独立的内存权限,比如代码节默认有读/执行权限,节的名字和数量可以自己定义,未必是上图中的三个。



DOS头:

PE文件的开头:DOS MZ header结构。

定义:

  1. typedef struct _IMAE_DOS_HEADER {       //DOS .EXE header                                    位置  
  2.     WORD e_magic;                       //Magic number; 解析时作为是否是PE文件的第一个标志    0x00  
  3.     WORD e_cblp;                        //Bytes on last page of file                         0x02  
  4.     WORD e_cp;                          //Pages in file                                      0x04  
  5.     WORD e_crlc;                        //Relocations                                        0x06  
  6.     WORD e_cparhdr;                     //Size of header in paragraphs                       0x08  
  7.     WORD e_minalloc;                    //Minimum extra paragraphs needed                    0x0A  
  8.     WORD e_maxalloc;                    //Maximum extra paragraphs needed                    0x0C  
  9.     WORD e_ss;                          //Initial (relative) SS value                        0x0E  
  10.     WORD e_sp;                          //Initial SP value                                   0x10  
  11.     WORD e_csum;                        //Checksum                                           0x12  
  12.     WORD e_ip;                          //Initial IP value                                   0x14  
  13.     WORD e_cs;                          //Initial (relative) CS value                        0x16  
  14.     WORD e_lfarlc;                      //File address of relocation table                   0x18  
  15.     WORD e_ovno;                        //Overlay number                                     0x1A  
  16.     WORD e_res[4];                      //Reserved words                                     0x1C  
  17.     WORD e_oemid;                       //OEM identifier (for e_oeminfo)                     0x24  
  18.     WORD e_oeminfo;                     //OEM information; e_oemid specific                  0x26   
  19.     WORD e_res2[10];                    //Reserved words                                     0x28  
  20.     LONG e_lfanew;                      //File address of new exe headerPE解析时用它找到PE头位置                                                                                                                     0x3C  
  21. } IMAGE_DOS-HEADER, *PIMAGE_DOS_HEADER;

说明:
1.文件的第一个字节便是这个结构体。
2.首尾两个成员有用。
3.e_magic,就是一个标记,DOS头标志位,其值恒为4D5A,在系统中用宏定义为:IMAGE_DOS_SIGNATURE,翻译:DOS标志。
4.e_lfanew,意思是心EXE文件的偏移,它表示NT头部在文件中的偏移。

使用:
1.e_magic的作用为判断这个文件是否是PE文件,如果从这个文件开头用PIMAGE_DOS_HEADER解析,e_magic成员不是IMAGE_DOS_SIGNATURE,那么这就不是PE文件。

判断是否是DOS头:

if ((PIMAGE_DOS_HEADERpFile->e_magic != IMAGE_DOS_SIGNATURE)

{

    //不是DOS头,返回

    return;

}

2.e_lfanew,用于计算出偏移,找出后面的NT头在文件中的位置。

假设已将文件读入内存,首地址是pFilevoid类型的指针

longpFile + PIMAGE_DOS_HEADER)pFile->e_lfanew;

以上计算结果就是NT头的位置。
3.在加载器中,这不问的用途就是使加载器找到NT头。



NT 头:
1
2
3
4
5
typedefstruct _IMAGE_NT_HEADERS {
    DWORD Signature;  //标记(判断是否为PE文件的第二个标志
    IMAGE_FILE_HEADER FileHeader;  //文件头(存储着PE文件的基本信息
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;  //扩展头(存储关于PE文件加载时加载的信息
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

说明:
1.NT头由一个简单的标记,一个不太复杂的文件头和一个比较复杂的扩展头组成。
2.如果是PE文件,标记的值恒为0X00004550,ASCII为PE00,在系统中用宏定义为IMAGE_NT_SIGNATURE.
3.另外两个成员是两个结构体,里面存储的信息非常有用。

使用:
1.Signature的作用于DOS头中的e_magic差不多,也是判断这是否是一个PE文件。

假设已将文件读入内存,获取NT头部方法:

PIMAGE_NT_HEADERS32    pNTHeader = (PIMAGE_NT_HEADERS32)((long)pFile + (PIMAGE_DOS_HEADER)pFile->e_lfanew);

//判断是否NT

if (pNTHeader->Signature  != IMAGE_NT_SIGNATURE)

{

    //不是NT头,说明不是PE文件,返回

    return;

}


        由于另外两个成员也是结构体,并且比较复杂,需要单独取出。可以用指针直接指向数据成员,也可以用结构体变量接收。这里使用指针。

PIMAGE_FILE_HEADER    pFileHeader = &(pNTHeader->FileHeader);

PIMAGE_OPTIONAL_HEADER32    pOptionalHeader = &(pNTHeader->OptionalHeader);


文件头:

NT头的第二个成员
1
2
3
4
5
6
7
8
9
typedefstruct _IMAGE_FILE_HEADER {
    WORD    Machine;  //文件的运行平台 
    WORD    NumberOfSections;  //区段的数量
    DWORD   TimeDateStamp;  //文件创建时间
    DWORD   PointerToSymbolTable;  //符号表偏移
    DWORD   NumberOfSymbols;  //符号个数
    WORD    SizeOfOptionalHeader;  //扩展头的大小
    WORD    Characteristics;  //PE文件的一些属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

Characteristic:




使用:
1.假设已将文件读入内存,首地址是pFile,void类型指针

PIMAGE_NT_HEADERS32    pNTHerader = (PIMAGE_NT_HEADERS32)((long)pFile + (PIMAGE_DOS_HEADER)pFile->e_lfanew);

PIMAGE_FILE_HEADER    pFileHeader = &(pNTHeader->FileHeader);

pFileHeader所指出的数据都可以显示出来。

2.关于时间的转换的代码

tm * FileTime = gmtime((time_t *pFileHeader->TimeDataStamp);

月从0开始,日从1开始。



扩展头: 

1.虚拟地址与相对虚拟地址
虚拟地址(VA):4GB虚拟空间中的地址。
相对虚拟地址(RVA):相对于加载基址的偏移。
即:相对虚拟地址(RVA)=虚拟地址(VA)- 基址 (ImageBase)
PE文件有一个默认的加载基址,是扩展头的第十个数据成员ImageBase(映像基址)。

2.文件偏移
文件偏移指PE文件各部分在存储设备中相对于文件起始的偏移。指的是未加载前的PE文件。

3.对齐
区块需要对齐。PE文件头里的FileAligment定义了磁盘区块的对齐值。

typedefstruct _IMAGE_OPTIONAL_HEADER 
{//// Standard fields.  //
+18h    WORD   1 Magic;// 标志字,普通可执行文件32位(010B),64位(020B),ROM 映像(0107h)
+1Ah    BYTE   2 MajorLinkerVersion;// 链接程序的主版本号
+1Bh    BYTE   3 MinorLinkerVersion;// 链接程序的次版本号
+1Ch    DWORD  4 SizeOfCode;// 所有含代码的节的总大小
+20h    DWORD  5 SizeOfInitializedData;// 所有含已初始化数据的节的总大小
+24h    DWORD  6 SizeOfUninitializedData;// 所有含未初始化数据的节的大小
+28h    DWORD  7 AddressOfEntryPoint;// 程序执行入口的相对虚拟地址RVA,也称OEP,Orginal Entry Point,源入口点
+2Ch    DWORD  8 BaseOfCode;// 代码的区块的起始RVA
+30h    DWORD  9 BaseOfData;// 数据的区块的起始RVA//// NT additional fields.    以下是属于NT结构增加的领域。//
+34h    DWORD  10 ImageBase;//默认加载基址(如果没有加载到这个地址,会发生重定位)
+38h    DWORD  11 SectionAlignment;// 内存中的区块的对齐大小,一般为0x1000
+3Ch    DWORD  12 FileAlignment;// 文件中的区块的对齐大小,一般为0x200
+40h    WORD   13 MajorOperatingSystemVersion;// 要求操作系统最低版本号的主版本号
+42h    WORD   14 MinorOperatingSystemVersion;// 要求操作系统最低版本号的副版本号
+44h    WORD   15 MajorImageVersion;// 可运行于操作系统的主版本号
+46h    WORD   16 MinorImageVersion;// 可运行于操作系统的次版本号
+48h    WORD   17 MajorSubsystemVersion;// 要求最低子系统版本的主版本号
+4Ah    WORD   18 MinorSubsystemVersion;// 要求最低子系统版本的次版本号
+4Ch    DWORD  19 Win32VersionValue;// 莫须有字段,不被病毒利用的话一般为0
+50h    DWORD  20 SizeOfImage;// 映像装入内存后的总尺寸
+54h    DWORD  21 SizeOfHeaders;// 所有头 + 区块表的尺寸大小,一般也是文件主体相对文件起始的偏移
+58h    DWORD  22 CheckSum;// 映像的校检和
+5Ch    WORD   23 Subsystem;// 可执行文件期望的子系统
+5Eh    WORD   24 DllCharacteristics;// DllMain()函数何时被调用,默认为 0
+60h    DWORD  25 SizeOfStackReserve;// 初始化时的栈大小,栈可以增长的最大值,一般1MB
+64h    DWORD  26 SizeOfStackCommit;// 初始化时实际提交的栈大小,每次增长的值,一般4KB
+68h    DWORD  27 SizeOfHeapReserve;// 初始化时保留的堆大小,进程中对可以增长的最大值一般1MB
+6Ch    DWORD  28 SizeOfHeapCommit;// 初始化时实际提交的堆大小,即堆的初始值
+70h    DWORD  29 LoaderFlags;// 与调试有关,默认为 0 
+74h    DWORD  30 NumberOfRvaAndSizes;// 下边数据目录的项数,这个字段自Windows NT 发布以来一直是16
+78h   31 IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];// 数据目录表
} IMAGE_OPTIONAL_HEADER32,*PIMAGE_OPTIONAL_HEADER32;

说明:
1.扩展头属于NT头部的第三部分,紧随文件头结构之后, 存储着加载文件时的一些初始化信息,大小一般为E0。
扩展头一般被翻译为可选头,但是它是必选的。
2.IMAGE_NUMBEROF_DIRECTORY_ENTRIES 这是个宏定义,值是0x10,即16个数据目录。

数据目录的定义:

typedef    struct_IMAGE_DATA_DIRECTORY {

    DWORD    VirtualAddress;    //数据的相对虚拟地址(RVA

    DWORD    Size;                     //数据的大小

IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

表示这段数据存储的相对虚拟地址(RVA)以及这段数据的大小。

数据目录的名字:

#define IMAGE_DIRECTORY_ENTRY_EXPORT    0                            //索引导出表

#define IMAGE_DIRECTORY_ENTRY_IMPORT    1                             //索引导入表

#define IMAGE_DIRECTORY_ENTRY_RESOURCE  2                         //索引资源

#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3                          //索引异常处理程序表

#define IMAGE_DIRECTORY_ENTRY_SECURITY  4                           //索引安全结构

#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5                        //索引基址重定位信息

#define IMAGE_DIRECTORY_ENTRY_DEBUG 6                                 //索引调试信息

#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE  7                  //版权

#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8                         //全局指针目录,用在64位平台

#define IMAGE_DIRECTORY_ENTRY_TLS   9                                      //指向线程局部存储初始化节

#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG   10                 //载入配置

#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT  11                //绑定输入目录

#define IMAGE_DIRECTORY_ENTRY_IAT   12                                     //导入地址表   

#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT  13                 //延迟载入描述

#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR    14         //COM信息

    最后15是预留位置。


使用:
1.显示扩展头的哪些信息,可以自己随便定。
假设已将将文件读入内存,首地址是pFile,void类型的指针
PIMAGE_NT_HEADERS32  pNTHedaer = (PIMAGE_NT_HEADERS32)((long)pFile + (PIMAGE_DOS_HEADER)pFile->e_lfanew);
PIMAGE_OPTIONAL_HEADER32 pOptionalHeader = &(pNTHeader->OptionalHeader);
想显示什么,直接用pOptionalHeader指针指出来就行了。

2.读入数据目录表的信息
假设已将将文件读入内存,首地址是pFile,void类型的指针,这个是伪代码

PIMAGE_NT_HEADERS32    pNTHeader = (PIMAGE_NT_HEADERS32)((long)pFile + (PIMAGE_DOS_HEADER)pFile->e_lfanew);    //获取NT

PIMAGE_OPTIONAL_HEADER32    pOptionalFileHeader = &(pNTHeader->OptionalHeader);    //获取扩展头

PIMAGE_DATA_DIRECTORY    pDataDirectory = pFileHeader->DataDirectory;    //获取数据目录表


//循环读取出数据目录中的相对虚拟地址(RVA)和大小。

DWORDi = 0;

while (i != 0x10)

{

    显示pDataDirectory[i].VirtualAddress;    //相对虚拟地址

    显示pDataDirectory[i].Size;                     //大小

}




区段(节)头表:

区段头表存储着PE文件主体的一些属性,区段头表是由若干个结构体依次排列组成(它就是一个结构体数组),每一个结构体代表着PE文件主体中一段数据的属性,也就是每一个区段头都对应PE文件主体的一段数据,这段数据叫做区段或者节,区段头规定了区段(节)的属性。

区段头结构:
typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];    1.//区段的名字
    union {
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    } Misc;                                                   2.//这个区段的大小
    DWORD   VirtualAddress;                    3.//这个区段起始的相对虚拟地址(RVA)
    DWORD   SizeOfRawData;                   4.//区段在文件中的大小 
    DWORD   PointerToRawData;             5.//区段的文件偏移
    DWORD   PointerToRelocations;        6
    DWORD   PointerToLinenumbers;      7   
    WORD    NumberOfRelocations;         8
    WORD    NumberOfLinenumbers;       9
    DWORD   Characteristics;                    10.//这一区段的属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

1.区段头表是由多个这个结构体构成,以一个全是0的结构体结尾
2.关于区段名字的规定:
.text段:一般是代码段,非常重要
.data段:一般是数据段
.bss段:表示未初始化的数据,比如static变量,可能在进入一个函数的时候才被初始化
.rdata段:表示只读的数据,比如字符串
.textbss段:和代码有关
.idata和edata:存储导入表和到处表的信息
.rsrc段:存储资源的区段(节)
.relcoc段:存储重定位信息的区段(节)
3.VirtualSize:这个区段在虚拟内存中使用的总大小,没有经过对齐。注意这是一个联合体。
4.VirtualAddress:这个区段起始的相对虚拟地址。也就是这个区段加载时是以PE文件加载基地址与这个数据成员的和为起点的。
5.SizeOfRawData:这个区段在磁盘文件中的大小。这个值进行了文件对齐。
6.PointerToRawData:区段的文件偏移,就是这个区段在磁盘文件中的起始位置。
7.Characteristics:这一区段(节)的属性。

8.一共10个成员,这个结构的大小是40字节,或者0x28字节。

使用:
1.系统提供了一个宏来方便找到它的位置:IMAGE_FIRST_SECTION(pNTHeader),参数是NT头的指针。
2.用于相对虚拟地址(RVA)与文件偏移(Offectset)的转换,这是解析数据目录表的基础。
Offect(转)=RVA转 - RVA(区段)+ Offect(区段)

转换函数:假设已将将目标文件读入内存,首地址是pFile,void类型的指针,可以将pFile看成一个全局变量,实际上,在写工具的时候,它是PE处理类中的一个数据成员。

DWORD    CalcOffect(DWORD Rva)

{

    //1.获取NT

    PIMAGE_NT_HEADERS32    pNTHeader = (PIMAGE_NT_HEADERS32)((long)pFile + (PIMAGE_DOS_HEADER)pFile->e_lfanew);

    //2.获取区段头表

    PIMAGE_SECTION_HEADER    pSectionHeader = IMAGE_FIRST_SECTION(pNTHeader);

    //3.循环比较它在哪一个区段中,不在这个区段就继续循环,注意假如VirtualAddressSizeOfRawData大得话,说明大出来的数据是未初始化的数据,所以这里要用SizeOfRawData

    while (!(Rva >= pSectionHeader->VirtualAddress && Rva < pSectionHeader->VirtualAddress + pSetionHeader->SizeOfRawData))

    {

        ++pSectionHeader;

        //防止错误的PE文件引发崩溃

        if (0 == pSectionHeader->PointerToRawData)

            return 0;

    }

    return    Rva - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;

}


3.区段信息的循环获取,和上面的代码相似,只是在循环体中输出数据即可

void ViewSecitonInfo()

{

    //1.获取NT

    PIMAGE_NT_HEADERS32    pNTHeader = (PIMAGE_NT_HEADERS32)((long)pFile + (PIMAGE_DOS_HEADER)pFile->e_lfanew);

        //2.获取区段头表

        PIMAGE_SECTION_HEADER    pSectionHeader = IMAGE_FIRST_SECTION(pNTHeader);

        //3.循环输出区段表的信息,可以用两种方式判断结束,一个是头文件给出了区段的数量,可以用那个,也可以判断最后一个全零的区段头

        while (pSectionHeader->PointerToRawData)

        {

            //输出或者获取每一个pSectionHeader中的成员

            ++pSectionHeader;

        }

}









  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值