PE头文件

PE头文件

1.1 PE的数据据组织方式

PE的数据据组织是一种数据管理技术
PE的数据组织是大量字节码与数据结构的有机结合

1.2 PE相关基本概念

1.2.1 地址

PE中涉及的地址有四类:
1.虚拟内存地址(VA)
2.相对虚拟内存地址(RVA)
3.文件偏移地址(FOA)
4.特殊地址

1.虚拟内存地址
一个进程被操作系统加载到虚拟内存空间后,其相关的动态链接库也会被加载。这些同时加载到进程地址空间的文件称为模块。每一个模块在加载时都会有一个基地址,也就是预先告诉操作系统:它会占用4GB空间的哪个部分(即从哪里开始存储该模块)。不同模块的基地址一般是不同的,如果两个模块的基地址相同,就由操作系统来决定这两个模块在虚拟空间中的具体位置。

2.相对虚拟内存地址

相对虚拟内存地址 ( Reverse Virtual Address,RVA) 是相对于基地址的偏移,即 RVA 是虚拟内存中用来定位某个特定位置的地址,该地址的值是这个特定位置距离某个模块基地址的偏移量,所以说 RVA 是针对某个模块而存在的。

RVA是相对于模块而言的,VA是相对于整个地址空间而言的。

RVA与具体模块相关,它有一个范围,该范围从模块的开始到模块结束,脱离开这个范围的RVA是无效的,称为越界。 越界的 RVA 地址没有任何意义。

3.文件偏移地址
文件偏移地址(File Offset Address, FOA)和内存无关,它是指某个位置距离文件头的偏移。

4.特殊地址

在PE结构中还有一 种特殊地址,其计算方法并不是从文件头算起,也不是从内存的某个模块的基地址算起, 而是从某个特定的位置算起。 这种地址在PE结构中很少见,如在资源表里就出现过这样的地址。

1.2.2 指针

PE数据结构中的指针的定义:如果数据结构中某个字段存储的值为一个地址,那么这个字段就是一个指针。

1.2.3 数据目录

PE中有一个数据结构称为数据目录,其中记录了所有可能的数据类型。这些类型中, 目前已定义的有15种,包括导出表、 导入表、 资源表、异常表、属性证书表、重定位表、调试数据、Architecture、Global Ptr、线程局部存储 、加载配置表、绑定导入表、 IAT 、延迟导入表和CLR运行时头部 。

1.2.4 节

程序与数据的独立性
程序中的代码和数据通常是分开存放的
Windows操作系统通常对不同用途的数据设置不同的访问权限 。

Windows操作系统在加载可执行程序时,会为这些具有不同属性的数据分别分配标记有不同属性的页面(当然,相同属性的数据可能会被放到同一个页面中),以确保程序运行时的安全。正是基于这个原因,PE中才出现了所谓的节的概念。

节就是存放不同类型数据(比如代码、数据、常量、资源等)的地方,不同的节具有不同的访问权限。节是 PE 文件中存放代码或数据的基本单元。

1.2.5 对齐

PE中规定了三类对齐 :数据在内存中的对齐、数据在文件中的对齐、资源文件中资源数据的对齐。

1.内存对齐
由于Windows操作系统对内存属性的设置以页为单位,所以通常情况下,节在内存中的对齐单位必须至少是一 个页的大小。对32位的Windows XP 系统来说,这个值是4KB(1000h) ,而对于64位操作系统来说,这个值就是8KB (2000h) 。

如果内存对齐被定义为小于操作系统页的大小,则文件对齐和内存对齐的值必须一致

2.文件对齐
为了提高磁盘利用率,通常情况下,定义的节在文件中的对齐单位要远小于内存对齐的单位;通常会以一 个物理扇区的大小作为对齐粒度的值,即 512 字节,十六进制表示为200h。
数据段、代码段等起始地址都是200h的倍数的

3.资源数据对齐
资源文件中,资源字节码部分一般要求以双字 (4 个字节)方式对齐

1.2.6 Unicode字符串

Unicode是继ASCII字符编码后的另一种新型字符编码。严格意义上讲,ASCII码的每个字符使用7位表示,Unicode则使用全16位表示一个字符。Unicode 字符串中的每个字符均为双字节,所以又称为宽字符串。

一个字节来表示字符串中的字符,称为ANSI字符串。PE格式中涉及字符串的部分均采用ANSI字符串。然而,在资源表中,对菜单名、 对话框标题等的描述则全部使用Unicode字符串。

在汇编语言中,Unicode字符串被定义为一个结构体,它的定义如下:

typedef struct_UNICODE_STRING {
USHORT Length;        //字符串的长度(字节数)
USHORT MaximumLength; //字符亭缓冲区的长度(字节数)
PWSTR Buffer;         //字符串缓冲区
} UNICODE_STRING, *PUNICODE_STRING;

1.3 PE文件结构

1.3.1 16位系统下的PE结构

为了兼容16位系统,在PE里依旧保留了16位系统下的标准可执行程序执行时所必需的文件头部(DOSMZ头)和指令代码(DOS Stub)。
在16位系统下,PE结构可以大致划分为两部分:DOS头和冗余数据,

在这里插入图片描述

在16位系统下,PE的四部分内容被重新组合成两部分一可以在16位系统下运行的DOS头和冗余数据。把Windows下的PE文件存储到DOS系统并运行,它就是DOS系统下的一个EXE文件。
DOS头分为两部分,DOSMZ头和DOS Stub(即指令字节码)。大部分情况下,这些指令实现的功能都非常简单,根本不会涉及重定位信息。再往后的PE头和PE数据区可以看做是16位系统下的可执行文件的冗余数据。
1.DOS MZ头
在Windows的PE格式中,DOSMZ头的定义如下:

在这里插入图片描述

加粗部分在16位系统下是没有定义的。由于其开始的标志字为“MZ "(Mark Zbikowski,他是DOS操作系统的开发者之一),所以称它为 “DOS MZ 头”。

2.DOS Stub
由于DOS Stub 的大小不固定, 因此DOS头的大小也是不固定的。DOS Stub部分是该程序在DOS系统下运行的指令字节码。
DOS Stub 程序的功能很简单,通过调用 int21中断的9号功能,实现在屏幕输出一段字符串。这段字符串提示用户该程序为32位的 PE 文件,不能运行在DOS环境下。

1.3.2 32位系统下的PE结构

在32位系统中,刚好相反,即DOS头成为冗余数据。所谓冗余,是针对DOS头不参与 32 位系统运行过程而言的。尽管该部分不参与运行,但也不能把这些数据从PE结构中除去。因为在DOS MZ头中有一个字段非常重要,即IMAGE_DOS_HEADER.e_lfanew , 没有它操作系统就定位不到标准的PE头部,可执行程序也就会袚操作系统认为是非法的PE映像。

1.定位标准PE头
字段e_lfanew的值是一个相对偏移值,绝对定位时需要加上DOSMZ头的基地址。也就是说 ,通过以下公式可以得出PE头的绝对位置:

PE_start=DOS MZ基地址+IMAGE_DOS_HEADER.e_lfanew
通过字段IMAGE_DOS_HEADER.e_lfanew的值可以定位到PE头的起始位置 。

2.PE文件结构
在32位系统下,最重要的部分就是PE头和PE数据区。

32 位系统下的 PE 文件结构被划分为 5 个部分,包括:
DOSMZ头、DOS Stub、PE头、节表和节内容
DOS MZ头的大小是64个字节,PE头的大小实际由字段
IMAGE_FILE_HEADER.SizeOfDptionalHeader来确定,节表的大小不固定,每个节的描述信息则是个固定值,共40个字节,节表是由不确定数量的节描述信息组成的,其大小等于节的数量 x40(乘40),节的数量由字段IMAGE_FILE_HEADER. NumberOfSections来定义。
DOS Stub和节内容都是大小不确定的。

PE文件头部等于DOS 头+PE头。

1.3.3 程序员眼中的PE结构

在这里插入图片描述

一个标准的PE文件一般由四大部分组成:
1.DOS头
2.PE头(IMAGE_NT_HEADERS)
3.节表(多个IMAGE_SECTION_HEADER结构)
4.节内容

PE头的数据结构最为复杂。简单来说,PE头包含:
1.4个字节的标识符号(Signature)
2.20个字节的基本头信息(IMAGE_FILE_HEADER)
3.216个字节的扩展头字节(IMAGE_OPTIONAL_HEADER32)

如果按照 “ 头部 +身体” 的信息组织方式来看:
PE文件头部=DOS头+PE头+节表
PE文件身体=节内容

1.4 PE文件头部解析

1.4.1 DOS MZ头IMAGE_DOS_HEADER

IMAGE_DOS_HEADER的具体定义如下:

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header   
0000h    WORD   e_magic;                // Magic number -EXE标志。“MZ”
0002h    WORD   e_cblp;                 // Bytes on last page of file -最后部分业中的字节数
0004h    WORD   e_cp;                   // Pages in file -文件中的全部和邻分页数
0006h    WORD   e_crlc;                 // Relocations -重定位表中的指针数
0008h    WORD   e_cparhdr;              // Size of header in paragraphs -头部尺寸,以段落为单位
000Ah    WORD   e_minalloc;             // Minimum extra paragraphs needed -所需的最小附加段
000Ch    WORD   e_maxalloc;             // Maximum extra paragraphs needed -所需的最大附加段
000Eh    WORD   e_ss;                   // Initial (relative) SS value -初始的SS值(相对偏移量)
0010h    WORD   e_sp;                   // Initial SP value -初始的SP值
0012h    WORD   e_csum;                 // Checksum  -校验和
0014h    WORD   e_ip;                   // Initial IP value -初始的IP值
0016h    WORD   e_cs;                   // Initial (relative) CS value -初始的CS值(相对偏移量)
0018h    WORD   e_lfarlc;               // File address of relocation table  -重分配表文件地址
001Ah    WORD   e_ovno;                 // Overlay number -覆盖号
001Ch    WORD   e_res[4];               // Reserved words -保留字
0024h    WORD   e_oemid;                // OEM identifier (for e_oeminfo) -OEM标识符(相对e_oeminfo)
0026h    WORD   e_oeminfo;              // OEM information; e_oemid specific -OEM信息
0028h    WORD   e_res2[10];             // Reserved words -保留字
003Ch    LONG   e_lfanew;               // File address of new exe header -新exe头部的文件地址
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER; 

偏移是基于IMAGE_DOS_HEADER头的
整个DOS_Stub是一个字节块,其内容随若链接时使用 的链接器不同而不同,PE中并没有与之对应的相关结构。

1.4.2 PE头标识Signature

PE头部信息中有一个四字节的标识,该标识位于指针IMAGE_DOS_HEADER.e_lfanew指向的位置。其内容固定,对应于ASCII码的字符串
“ PE\0\0 "

1.4.3 标准PE头 IMAGE_FILE_HEADER

位于IMAGE_DOS_HEADER.e_lfanew的e的e_lfanew值+4的位置

typedef struct _IMAGE_FILE_HEADER {
0004h 	WORD Machine;                   //可以运行在什么样的CPU上任意: 0 Intel 3861以及后续: 14C x64: 8664
0006h	WORD NumberOfSections;          //表示节的数量
0008h	DWORD TimeDateStamp;            //编译器填写的时间戳与文件属性的(创建时间、修改时间)无关,从1970年1月1日至今
000Ch	DWORD PointerToSymbolTable;     //调试相关(指向符号表)
0010h	DWORD NumberOfSymbols;          //调试相关(符号表中的符号数量)
0014h	WORD SizeOfOptionalHeader;      //可选(扩展)PE头的大小(32位PE文件: 0xE0 64位PE文件: 0xF0)
0016h	WORD Characteristics;           //文件属性
} IMAGE_ FILE_ _HEADER, *PIMAGE_ FILE_ HEADER;

偏移是基于IMAGE_DOS_HEADER头的

该结构常用于判断PE文件是EXE类型还是DLL类型,可以通过该结构得到PE文件中节的总量,还可以当成对节区信息进行遍历操作时的循环次数

1.4.4 扩展PE头IMAGE_OPTIONAL_HEADER32

定义如下:

typedef struct _IMAGE_OPTIONAL_HEADER 
{
    //
    // Standard fields.  
    //
0018h    WORD    Magic;                   // 标志字, ROM 映像(0107h),普通可执行文件(010Bh)
001Ah    BYTE    MajorLinkerVersion;      // 链接程序的主版本号
001Bh    BYTE    MinorLinkerVersion;      // 链接程序的次版本号
001Bh    DWORD   SizeOfCode;              // 所有含代码的节的总大小
0020h    DWORD   SizeOfInitializedData;   // 所有含已初始化数据的节的总大小
0024h    DWORD   SizeOfUninitializedData; // 所有含未初始化数据的节的大小
0028h    DWORD   AddressOfEntryPoint;     // 程序执行入口RVA
002Ch    DWORD   BaseOfCode;              // 代码的区块的起始RVA
0030h    DWORD   BaseOfData;              // 数据的区块的起始RVA
    //
    // NT additional fields.    以下是属于NT结构增加的领域。
    //
0034h    DWORD   ImageBase;               // 程序的首选装载地址
0038h    DWORD   SectionAlignment;        // 内存中的区块的对齐大小
003Ch    DWORD   FileAlignment;           // 文件中的区块的对齐大小
0040h    WORD    MajorOperatingSystemVersion;  // 要求操作系统最低版本号的主版本号
0042h    WORD    MinorOperatingSystemVersion;  // 要求操作系统最低版本号的副版本号
0044h    WORD    MajorImageVersion;       // 可运行于操作系统的主版本号
0046h    WORD    MinorImageVersion;       // 可运行于操作系统的次版本号
0048h    WORD    MajorSubsystemVersion;   // 要求最低子系统版本的主版本号
004Ah    WORD    MinorSubsystemVersion;   // 要求最低子系统版本的次版本号
004Ch    DWORD   Win32VersionValue;       // 莫须有字段,不被病毒利用的话一般为0
0050h    DWORD   SizeOfImage;             // 映像装入内存后的总尺寸
0054h    DWORD   SizeOfHeaders;           // 所有头 + 区块表的尺寸大小
0058h    DWORD   CheckSum;                // 映像的校检和
005Ch    WORD    Subsystem;               // 可执行文件期望的子系统
005Eh    WORD    DllCharacteristics;      // DllMain()函数何时被调用,默认为 0
0060h    DWORD   SizeOfStackReserve;      // 初始化时的栈大小
0064h    DWORD   SizeOfStackCommit;       // 初始化时实际提交的栈大小
0068h    DWORD   SizeOfHeapReserve;       // 初始化时保留的堆大小
006Ch    DWORD   SizeOfHeapCommit;        // 初始化时实际提交的堆大小
0070h    DWORD   LoaderFlags;             // 与调试有关,默认为 0 
0074h    DWORD   NumberOfRvaAndSizes;     // 下边数据目录的项数,这个字段自Windows NT 发布以来一直是16
0078h    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];   
// 数据目录表
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

偏移是基于IMAGE_NT_HEADER头的
文件执行时的入口地址、 文件被操作系统装入内存后的默认基地址,以及节在磁盘和内存中的对齐单位等信息均可以在此结构中找到

1.4.5 PE头的IMAGE_NT_HEADERS

这个结构是广义上的PE头,在标准的PE文件中其大小为456个字节 F8

在这里插入图片描述

定义如下:

typedef struct IMAGE_NT_HEADERS {
0000h  DWORD Signature;                           //PE头文件标识“PE\0\0"
0004h  IMAGE_FILE_HEADER FileHeader;              //PE标准头
0018h  IMAGE_OPTIONAL_HEADER32 OptionalHeaderPE;  //PE扩展头
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

1.4.6 数据目录项IMAGE_DATA_DIRECTORY

IMAGE_OPTIONAL_HEADER32结构的最后一个字段为DataDirectory,该字段定义了PE文件中出现的所有不同类型的数据的目录信息。

结构具体定义如下:

typedef struct IMAGE_DATA_DIRECTORY {
0000h    DWORD VirtualAddress;   //数据的起始RVA
0004h    DWORD Size;            //数据块的长度
} IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY;

总的数据目录一共由 16个相同的IMAGE_DATA_DIRECTORY结构排列组合在一起组成
在这里插入图片描述

在这个数据目录结构体中只有两个成员VirtualAddress和Size,这两个成员的含义比较简单,VirtualAddress指定了数据块的相对虚拟地址(RVA)。Size则指定了该数据块的大小,有时并不是该类型数据的总大小,可能只是该类型数据一个数据项的大小。这两个成员(主要是VirtualAddress)成为了定位各种表的关键,所以一定要知道每个数组元素所指向的数据块类型,以下表格就是它的对应关系:

在这里插入图片描述

16个数据目录项依次展开后的新结构 :

在这里插入图片描述

偏移是基于IMAGE_NT_HEADER头的

1.4.7 节表项IMAGE_SECTION_HEADER

每个节表项记录了PE中与某个特定的节有关的信息,如节的属性、节的大小,在文件和内存中的起始位置等
参考文档:https://docs.microsoft.com/zh-cn/windows/win32/api/winnt/ns-winnt-image_section_header?redirectedfrom=MSDN

具体定义:

typedef struct _IMAGE_SECTION_HEADER {
       BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
    union {
           DWORD PhysicalAddress;//不用关心,始终是NULL
0008h      DWORD VirtualSize; //指出实际的、被使用的区块的大小(也就是区块的数据没有对齐处理的实际大小)16H个
    }     Misc;
000Ch     DWORD VirtualAddress;      //节区装载到内存中的RVA
0010h     DWORD SizeOfRawData;       //在磁盘文件中所占的大小
0014h     DWORD PointerToRawData;    //在磁盘文件中的偏移
0018h     DWORD PointerToRelocations;//在EXE文件中无意义
001ch     DWORD PointerToLinenumbers;//行号表的位置(调试相关)
0020h     WORD NumberOfRelocations;  //由pointerToRelocations指向的重定位的数目
0022h     WORD NumberOfLinenumbers;  //行号表中行号的数量
0024h     DWORD Characteristics;     //节属性
};

偏移是基于IMAGE_SECTION_HEADER头的

1.5 数据结构字段详解

1.5.1 PE头IMAGE_NT_HEADER的字段

1.Signature字段

+0000h,双字,在一个有效的PE文件里,Signature 字段被设置为00004550h, ASCII 码字符是“PE00”。标志着PE文件头的开始。
“PE00” 字符串是PE文件头的开始,DOS头部的e_lfanew字段正是指向这里。

2.IMAGE_NT_HEADER.FileHeader

+0004h,结构。该结构指向IMAGE_FILE_HEADER,由于PE扩展自通用COFF规范,标准COFF头

3.IMAGE_NT_HEADER.OptionalHeader

+0018h,结构。该结构指向IMAGE_OPTIONAL_HEADER32 ,可选头 ,可选头又分为两部分,前10个字段原属于COFF,用来加载和运行一个可执行文件,后21个字段则是通过链接器追加的。它们作为PE扩展的部分,用干描述可执行文件的一些信息,供PE加载器加载使用。

1.5.2 标准PE头IAMGE_FILE_HEADER的字段

  1. IMAGE_FILE_HEADER.Machine
    +0004h,单字。用来指定PE文件运行的平台。

在这里插入图片描述

5.IMAGE_FILE_HEADER.NumberOfSections

+0006h,单字。文件中存在的节的总数。PE中增加或者修改节。必须更改此处的值
这个值既不能比实际内存中存在的节多,也不能比它少,否则装载时会发生错误, 提示不是有效的Win32应用程序。

6.IMAGE_FILE_HEADER.TimeDateStamp

+0008h,双字。编译器创建此文件时的时间戳。
低32位存放的值是自1970年1月1日00:00时开始到创建时间为止的总秒数
该数值可以随意修改而不会影响程序运行

7.IMAGE_FILE_HEADER.PointerToSymbolTable

+000Ch,双字。COFF符号表的文件偏移。
如果不存在COFF符号表,此值为0。对于映像文件来说,此值为0,因为微软已经不赞成在PE中使用COFF调试信息了。

8.IMAGE_FILE_HEADER.NumberOfSymbols

+0010h,双字C 符号表中元素的数目。
由于字符串表紧跟在符号表后,所以可以利用这个值来定位字符串表。对于映像文件来说,此值为0,主要用于调试。

9.SizeOfOptionalHeader

+0014h,单字。IMAGE_OPTIONAL_HEADER32结构的大小(即多少字节)。
事实上PE文件的大部分重要的域都在IMAGE_OPTIONAL_HEADER结构里。(对于32位PE文件,这个值通常是00E0h;对于64位PE32+文件,这个值通常是00F0h)。
用户可以自己定义这个值的大小,不过需要注意两点:
(1)更改完以后,需要自行将文件中IMAGE_OPITONAL_HEADER32的大小扩充为你指定的值( 一 般以 0 补足)。
(2)扩充完以后,要维持文件中的对齐特性(比如在HelloWorld.exe中,此处增加了8个字节后 , 定在后面相应地删除 8 个字节,以保证text节起始位置处于0400h

10.IMAGE_FILE_HEADER.Characteristics

+0016h,单字。文件属性标志字段,它的不同数据位定义了不同的文件属性。
这是一个很重要的字段,不同的定义将影响系统对文件的装入方式。
在这里插入图片描述

​ 属性位含义

1.5.3 扩展PE头IMAGE_OPTIONAL_HEADER32 的字段

11.IMAGE_OPTIONAL_HEADER32.Magic

+0018h,单字。幻数,说明文件的类型,如果为010BH,则表示该文件为PE32 : 如果为0107h,则表示文件为 RO M 映像 ;如果为 0208 H , 则表示该文件为PE32+,即64位下的PE文件

12.IMAGE_OPTIONAL_HEADER32.MajorlinkerVersion

13.IMAGE_OPTIONAL_HEADER32.MinorlinkerVersion

+001Ah,单字。这两个字段都是字节型,指定链接器版本号,对执行没有任何影响

14.IMAGE_OPTIONAL_HEADER32.SizeOfCode

+001Ch,双字。所有代码节的总和(以字节计算 ),该 大小是基千文件对齐后的大小, 而非内存对齐后的大小。

15.IMAGE_OPTIONAL_HEADER32.SizeOflnitializedData

+0020h,双字。所有包含已经初始化的数据的节的总大小。

16.IMAGE_OPTIONAL_HEADER32.SizeOfUninitializedData

+0024h,双字。所有包含未初始化的数据的节的总大小。这些数据被定义为未初始化,在文件中不占用空间;但在袚加载到内存以后, PE 加载程序应该为这些数据分配适当大小的虚拟地址空间。

17.IMAGE_OPTIONAL_HEADER32.AddressOfEntryPoint

+0028h,双字。该字段的值是一个RVA,它记录了启动代码距离该PE加载后的起始位置到底有多少个字节

18.IMAGE_OPTIONAL_HEADER32.BaseOfCode

+002Ch,双字。代码节的起始RVA,表示映像被加载进内存时代码节的开头相对于映像基址的偏移地址。一般情况下,代码节紧跟在PE头部后面,节的名称通常为" \text”

19.IMAGE_OPTIONAL_HEADER32.BaseOfData

+0030h,双字。数据节的起始RVA,表示映像被加载进内存时数据节的开头相对于映像基地址的偏移地址。一般情况下,数据节位于文件末尾,节的名称通常为“.data”。

20.IMAGE_OPTIONAL_HEADER32.lmageBase

+0034h,双字。该字段指出了PE映像的优先装入地址。也就是在IMAGE_OPTIONAL_ HEADER32.AddressOfEintryPoint中说的程序被加载到内存后的起始VA。
如果操作系统也是按照这个地址加载机器码到内存中,那么指令中的许多重定位信息就不需要修改了,这样运行速度就会更快一些。优先装入的地址通常不会被其他模块占据。
可以自己定义这个值,但取值有限制:
1.取值不能超出边界,即取的值必须在进 程地址空间中;
2.该值必须是64KB的整数倍

21.IMAGE_OPTIONAL_HEADER32.SectionAlignment

+0038h,双字。内存中节的对齐粒度,该字段指定了节被装入内存后的对齐单位。

22.IMAGE_OPTIONAL_HEADER32.FileAlignment

+003Ch,双字。文件中节的对齐粒度。

23.IMAGE_OPTIONAL_HEADER32.MajorOperatingSystemVersion

24.IMAGE_OPTIONAL_HEADER32.MinorOperatingSystemVersion

+0040h,23和24标注的两个字段都为单字,共计为双字。标识操作系统的版本号,分为主版本号和次版本号两部分。

25.IMAGE_OPTIONAL_HEADER32.MajorlmageVersion

26.IMAGE_OPTIONAL_HEADER32.MinorlmageVersion
+0044h,双字。本PE文件映像的版本号。

27.IMAGE一OPTIONAL_HEADER32.MajorSubsystemVersion

28.IMAGE_OPTIONAL_HEADER32.MinorSubsystemVersion
+0048h,双字。运行所需要的子系统的版本号。

29.IMAGE_OPTIONAL_HEADER32.Win32VersionValue

+004ch,双字。子系统版本的值。

30.IMAGE_OPTIONAL_HEADER32.SizeOflmage
+0050h,双字。内存中整个PE文件的映射尺寸。

31.IMAGE_OPTIONAL_HEADER32.SizeOfHeaders
+0054h,双字。所有头+节表按照文件对齐粒度对齐后的大小(即含补足的0),

32.IMAGE_OPTIONAL_HEADER32.CheckSum
+0058h,双字。校验和,在大多数的PE文件中,该值是0,但在一些内核模式的驱动程序和系统DLL中,该值则是必须存在且是正确的,

33.IMAGE_OPTIONAL_HEADER32.Subsystem
+005ch,单字。指定使用界面的子系统。这个字段决定了系统 如何为程序建立初始的界面。

34.IMAGE_OPTIONAL_HEADER32.DIICharacteristics
+005Eh,单字。DLL文件属性。它是一个标志集,不是针对DLL文件的,而是针对所有 PE文件的。这个字段定义了 PE文件装载时的一些特性

35.IMAGE_OPTIONAL_HEADER32.SizeOfStackReserve
+0060h,双字。初始化时保留栈的大小。该字段表示为初始线程的栈而保留的虚拟内存数量,然而并不是留出的所有虚拟内存都可以做栈(真正的栈大小由下一个字段SizeOfStackCommit决定)。该字段的默认值为0x100000 (1MB)。
如:数据位为6
常量符号为:IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
为1时含义DLL可以在加载时被重定位

36.IMAGE_OPTIONAL_HEADER32.SizeOfStackCommit
+0064h,双字。初始化时实际提交的栈大小。保证初始线程的栈实际占用内存空间的大小, 它是被系统提交的。

37.IMAGE_OPTIONAL_HEADER32.SizeOfHeapReserve
+0068h,双字。初始化时保留的堆大小。

38.IMAGE_OPTIONAL_HEADER32.SizeOfHeapCommit
+006ch,双字。初始化时实际提交的堆大小,在进程初始化时设定的堆所占用的内存空 间。默认值为1页。

39.IMAGE_OPTIONAL_HEADER32丄oaderFlags
+0070h,双字。加载标志。

40.IMAGE_OPTIONAL_HEADER32.NumberOfRvaAndSize
+0074h,双字。定义数据目录结构的数量,一般为00000010h,即16个。该值由字段SizeOfOptionalHeaders决定,实际应用中可以取2~16的值。

41.IMAGE_OPTIONAL_HEADER32.DataDirectory
+0078h,结构。由16个IMAGE_DATA_DIRECTORY结构线性排列而成,用于定义PE中的16种不同类别的数据所在的位置和大小。

1.5.4 数据目录项 IMAGE_DATA_DIRECTORY 的字段

42.IMAGE_DATA_DIRECTORY.VirtualAddress
+0000h,双字。如上所述,这个字段记录了特定类型数据的起始RVA。当然,针对不同 的数据结构,该字段包含的数据含义并不一样,有的数据甚至还不是RVA (如属性证书数据中该字段的值表示的是FOA)。

43.IMAGE_DATA_DIRECTORY.isize
+0004h,双字。该字段记录了特定类型的数据块的长度。

1.5.5 节表项 IMAGE_SECTION_HEADER 的字段

44.IMAGE_SECTION_HEADER.Name1
+0000h,8字节。该字段一共8个字节,一般情况下是一个以“\0”结尾的ASCII码字符 串来标识节的名称,内容可以自行定义。
该名称并不遵循Ansi字符串必须以“\0”结尾的规律,如果不以“\0”结尾,系统依然 会认为它是一个字符串,但会根据8个字节的长度对其进行截断处理。

45.IMAGE_SECTION_HEADER.Misc
+0008h,双字。该字段是一个union型的数据,这是节的数据在没有对齐前的真实尺寸, 不过很多PE文件里该值并不准确。

46.IMAGE_SECTION_HEADER.VirtualAddress
+000ch,双字。节区的RVA地址。

47.IMAGE_SECTION_HEADER.SizeOfRawData
+0010h,双字。节在文件中对齐后的尺寸。在HelloWorld.exe中,数据量不大的节,其 大小一般为200h。

48.IMAGE_SECTION_HEADER.PointerToRawData
+0014h,双字。节区起始数据在文件中的偏移。比如我们的例子屮,.text节是在偏移 0x00000400 处。

49.IMAGE_SECTION_HEADER.PointerToRelocations
+00l8h,双字。在“.obj”文件中使用,指向重定位表的指针。

50.IMAGE_SECTION_HEADER.PointerToLinenumbers
+001Ch,双字。行号表的位置(供调试用)。

51.IMAGE_SECTION_HEADER.NumberOfRelocations
+0020h,单字。重定位表的个数(在OBJ文件屮使用)。

52.IMAGE_SECTION_HEADER.NumberOfLinenumbers
+0022h,单字。行号表中行号的数量。

53.IMAGE_SECTION_HEADER.Characteristics
+0024h,双字。节的属性。

Hex

4D 5A 90 00 ODS头
行0030h C到F 为e_lfanew 位置
0040h 后为 DOS Stub
50 54 00 00 PE头 2E 74 65 78 74 .text
2E 64 61 74 61 .data
2E 72 64 61 61 .rdata
2E 72 73 72 63 .rsrc

1.6 PE内存映像

OD打开 ctrl+m 查看内存
文件字节码在内存中的大致分布:
PE文件头+代码+输入表+数据

1.7 PE文件头编程

1.7.1 RVA与FOA的转换

RVA是相对虚拟地址,FOA是文件偏移

PE文件头和PE内存映像的文件头大小都是一样的,它们受对齐粒度不同的影响;节的数据在内存和磁盘文件的大小是不一样的,节表项记录了内存映像中节的起始RVA,也同样记录了本节在文件中的起始偏移。节表项IMAGE_SECTION_HEADER是我们可以找到这两个字段之间存在关联的唯一地方。

节在内存中是线性排列的

当我们获取到某个RVA在某个节中距离节头的偏移off以后,就可以通过该节在文件中 的偏移求出这个RVA在文件中的偏移了。大致的步骤如下:
步骤1:判断指定的RVA落在哪个节内。
步骤2:求出该节的起始 RVAO=IMAGE_SECTION_HEADER.VirtualAddress。
步骤3:求出偏移量offset1=RVAO-RVA。
步骤4:求出该RVA相对于磁盘文件头的偏移 FOA=IMAGE_SECTION HEADER.PointerToRawData+off

具体计算
1.内存转文件偏移计算
1.1 计算RVA

这一个讲的就是内存转文件偏移,就是知道一个内存地址,我们要看看在文件中是哪里存储的。
第一步: 我们知道PE在内存中展开是在ImageBase位置展开的,头跟文件是一样的,只不过节数据展开位置不一样.
所以首先就是内存地址-Image得出RVA
下方我们的内存地址就设为x了.

x - ImageBase == RVA 得出了我们的x位置在内存中的相对偏移.相对偏移就是我们计算的这个地址在开始位置的什么地方.
ImageBase是在扩展头中存放的
注意都是16进制进行加算的。

根据上方我们得出的RVA,然后我们就在文件中从开头数RVA个字节,去寻找我们的这个数据,这样是不行的。因为文件对齐跟内存对齐是不一样的,所以我们要考虑对齐方式,如果文件对齐跟内存对齐一样,那么这样就可以去找。

2.寻址FOA

既然找到了RVA了,那么就找一下FOA在哪里,也就是文件偏移在哪里,寻找这个值很简单,需要以下几个步骤:

2.1 判断RVA属于哪个节/头.

如果RVA属于头(DOS+NT)那么不需要进行计算了,因为头在文件中根内存中都是一样展开的,直接从开始位置寻找到RVA个字节即可.
如果不在头,就要判断在那个节里面。判断节开始位置,跟结束位置,我们的RVA在这个值里面。

其中节虚拟地址结束位置就是用节数据对齐后的大小+虚拟地址大小,具体可以参考节表解析。
公式: RVA >= 节.VirtualAddress && RVA <= (节.VirtualAddress + 节.SizeofRawData)

2.2 计算差值偏移,虚拟地址距离节数据的开始位置的偏移,然后计算差值偏移:
差值 = RVA - 节.VirtuallAddress

差值偏移:

为什么要计算差值,因为我们计算的差值偏移就是我们的RVA距离我们节数据开始位置的偏移是多少,因为这个位置是不会改变的。

例如: 节数据开始位置是 0x1000 我们的 RVA = 0x1024 那么差值是0x24。 如果文件中节数据开始的位置是0x400, 那么我们的差值偏移是不会变的,那么文件偏移 + 差值偏移,那么就是在文件中的位置。例如:0x424
2.3 计算FOA

FOA很好计算。差值偏移已经得出来了,就知道我们的RVA距离节数据开始位置的偏移,那么加上文件偏移就是FOA

公式: FOA = 差值偏移 + 节.PointToRawData

内存转文件偏移总结:

1.计算RVA 公式: x - ImageBase = RVA
  2.计算差值偏移.:RVA - 节.VirtualAddress = 差值偏移.
  3.计算FOA 差值偏移 + 节.PointerToRawData = FOA

2.文件偏移转内存虚拟地址

上面讲解了我们根据虚拟地址可以定位到在文件中的那个位置.那么反之.我们也可以通过文件位置.定位到虚拟地址.

需要理解的还是差值偏移. 只不过角色互换了. .

设x 为节数据的任意一位置
  1.计算差值偏移: x - 节.PointerToRawData(节数据在文件中开始的位置) = 差值偏移.
  2.计算RVA: 差值偏移 + 节.VirtuallAddress(节数据在内存中展开的位置) = RVA
  3.计算虚拟地址: RvA + ImageBase = VA
需要注意的就是我们的 x在哪一个节中, x <= 节.PointerToRawData + 节.SizeofRawData

1.7.2 数据定位

在实现PE文件头部的编码过程中,大部分都要经过3个步骤:定位、取数据和操作。而定位包括:PE头定位、数据目录表项定位、节表项定位

数据定位函数会在两种情况下被调用,第一种情况是针对PE映像文件的信息存取,第二种情况是针对内存映射文件的信息存取。

PE中任何数据的定位必须首先找到PE头部标识

1.PE头定位
2.数据目录表项定位
3.节表项定位

1.7.3 标志位操作

标志位的操作不是以字节、字或双字为单位进行存取,而是以位来操作,所以需要用到汇编语言中的位操作符,如and、or、not等。

1.标志位

一个字的标志位如下:

在这里插入图片描述
在这里插入图片描述

2.读标志位
3.写标志位

1.7.4 PE校验和

校验和是一个WORD值。它是通过对一段数据进行一定的算法进行计算以后生成的值, 通常作为判断这段数据是否被非法修改的依据。
PE文件头部的校验和的算法很简单,共分三步:
步骤1:将文件头部的字段 IMAGE_OPTIONAL_HEADER32.CheckSum 清0。
步骤2:以WORD为单位对数据块进行带进位的累加,大于WORD部分自动溢出。
步骤3:将累加和加上文件的长度。
函数checkSuml通过调用动态链接库IMAGEHLP.DLL的MapFileAndCheckSum函数来获取校验和,而函数checkSum2则按照校验和算法规则来自定义校验和的获取

检验程序的流程是:
1、打开一个文件;
2、在内存中创建该文件的内存映射;
3、得到映射后的初地址;
4、根据初地址、文件的长度计算文件的校验和。

猜你喜欢

转载自blog.csdn.net/weixin_51732593/article/details/122022570
今日推荐