1、主要结构体
DOS MZ文件头的内存大小为64个字节
DOS Stub的大小不确定,因为这段是给连接器用的,即使这段数据被删改也不影响运行
通过DOS MZ文件头尾到PE文件头的内存确定大小
DOS部分属于是历史遗留问题,用于DOS 操作系统与exe程序运行无关,只是保留在PE中
PE文件头由三个部分组成,PE标识(PE指纹)类型为DWORD 占四个字节、标准PE头和扩展PE头
标准PE头的大小为20个字节,扩展PE头是224个字节(32位)(主要原因是其中包括了一个结构体数组)
扩展PE头可以产生变化,其主要取决于结构体中的 WORD SizeOfOptionalHeader 变量
之后就是节表,其所占字节为40个字节。其中包含了几个节数据,在节数据之后的全都是编译器插入的其他数据(节数据数量不定,可以进行修改)
扩展PE头中有一个结构体变量是DWORD SizeOfHeaders 这个变量的数值代表着DOS 部分、PE文件头和节表在文件对齐后的大小。 文件对齐对应的变量是DWORD FileAlignment 在头之后,就是各个节数据
扩展PE头中还有一个DWORD SectionAlignment 这个代表的内存对齐,一般来说与文件对齐数据不同。 两种状态不同,主要是指的是 头跟节,节跟节之间的距离不同,一般来说在内存中都是拉伸之后的。
为什么在文件中的对齐参数和在内存中不同?
内存对齐是为了提高内存利用效率,充分利用系统总线。 文件对齐是为了提高解析的效率
为什么PE文件在设计的时候要分块?
分块的一个原因是节省硬盘,而分块的另一个目的是节省内存空间。
一个PE文件加载进内存中可能大于在硬盘上的大小,并且无论是在内存中还是硬盘上,都是是分块管理(分节),一块和一块存储空间之间是空隙。在硬盘上空隙有可能小于内存中空隙;在内存中空隙较大(相较于硬盘)。而存在间隙的原因则是分块管理。 分块的一个原因是节省硬盘:比如notepad.exe,由于是早期的程序,当时硬盘容量比较小,编译器在生成可执行文件时,不仅要考虑效率问题使得内存对齐/文件对齐,还需要设计成节省硬盘空间的结构。所以这种结构遵循的对齐原则:内存对齐(1000H)和硬盘对齐(200H),对齐的补充数据(0X0000)便是间隙。硬盘的对齐值较小,补充间隙自然小,因此同一个可执行程序在内存中可能比在硬盘上大。但是现如今的硬盘空间更大,所以编译器生成的可执行程序在硬盘上与内存中对齐方式都是1000H。统一对齐为1000H的目的依旧是提高效率。 而分块的另一个目的是节省内存空间,比如同时在电脑上运行登录多个QQ账号,就需要运行多次QQ可执行程序。而代码段为只读数据需要一份即可,数据段则需要为每个账号均开辟一份,,多个QQ程序共享代码块,单独使用数据块,这样就节省了多份代码块的内存。(这些块是使用结构体来维护的,分块即创建结构体)。
2、DOS头属性说明
DOS部分由DOS块和DOS MZ文件头组成,DOS块不是结构体而是由单个字节组成的数据可以填写任何内容 ,而DOS MZ文件头是一个结构体 ,该结构体如下图组成
但该结构体是运行在16位操作系统上的,如今不再使用
但有其中两个成员是例外 WORD e_magic和WORD e_lfanew
即为第一和最后一个成员,如果将他们中间的成员都删除也不影响程序的使用,主要是因为第一个成员是一个标识,其代表的是PE指纹其内容应该是MZ,操作系统以此来判断一个程序是否是PE,最后一个成员指明的了PE的位置 ,操作系统以这两个特征来标识是PE文件,而如果进行了修改,再去点击该程序就会出现下面的情况
DOS块中的数据是由连接器进行填写的,将其中的进行修改也不会影响运行
3、标准PE头属性说明
PE文件头(_IMAGE_NT HEADERS)部分结构体分为了三个部分分别是:
typedef struct_IMAGE_NT_HEADERS{
DWORD Signature; //PE标识
IMAGE_FILE_HEADER FileHeader; //标准PE头
IMAGE_OPTIONAL_HEADER32 OptionalHeader; //扩展PE头
}IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
PE标识:PE标识不能破坏,操作系统在启动一个程序的时候会检测这个标识。
typedef struct_IMAGE_FILE_HEADER (
WORD Machine;
//可以运行在什么样的CPU上 任意: O Intel 386以及后续: 14C x64: 8664
WORD NumberOfSections;//表示节的数量
DWORD TimeDateStamp;//编译器填写的时间截与文件属性里面(创建时间、修改时间)无关
DWORD PointerToSymbolTable; //调试相关
DWORD NumberOfSymbols;//调试相关
WORD SizeOfOptionalHeader; //可选PE头的大小(32位PE文件: OxEO 64位PE文件: OxFO)
WORD Characteristics;//文件属性
}IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
标准PE头的第一个成员是代表了可以运行在什么样的CPU上,即在PE标识后面的一个四个字节的数据,如果是0000,即可以运行在任意CPU上,如果是Intel386以及后续的是014C(包含X86),X64的是8664
第二个成员指的是节的数量(即节数据的个数)
第三个成员为四个字节,其中填写的是时间戳,即从1970.1.1 0时0分开始 ,每过一秒编译器就往里面添1。(其与文件创建时间和修改时间无关。我们也可以通过自己的需求进行修改
第四个和第五个是与调试相关的
第六个成员指的是扩展PE头的大小。如果是32位的PE文件,则该成员的数据是0xE0 64位的数据是0xF0
最后一个成员指的是文件属性,是两个字节,16位的数据
十六位的数据其中每个位有其代表的含义,如上图所示。
其中第五个数据位的代表,应用程序可处理大于2GB的地址。我们知道的是在32位操作系统中,应用能够处理的一共是4GB的地址,其中高2G的地址由内核部分使用,低2G的地址是由应用程序使用。故由此可知,此数据位代表的是应用程序是64位的。
4、扩展PE头属性说明
typedef struct _IMAGE_NT_HEADERS{
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
}IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
typedef struct _IMAGE_NT_HEADERS64 {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER64 OptionalHeader;
}IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
PE头在32位和64位是不同的,上述代码上面的是32位,下面的是64位,且不同的地方主要在于扩展PE头,主要差异是该结构体里面少了几个成员,同时部分成员由2个字节变为4个字节。
我们这里主要研究32位的扩展PE头,64位的大同小异
//大小: 32bit(0xE0) 64bit(0xF0)
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic; //文件类型: 10bh为32位PE文件 / 20bh为64位PE文件 !!!!!****************!!!!!
BYTE MajorLinkerVersion; //链接器(主)版本号 对执行没有任何影响
BYTE MinorLinkerVersion; //链接器(次)版本号 对执行没有任何影响
DWORD SizeOfCode; //包含代码的节的总大小.文件对齐后的大小.编译器填的没用
DWORD SizeOfInitializedData; //包含已初始化数据的节的总大小.文件对齐后的大小.编译器填的没用.
DWORD SizeOfUninitializedData; //包含未初始化数据的节的总大小.文件对齐后的大小.编译器填的没用.(未初始化数据,在文件中不占用空间;但在被加载到内存后,PE加载程序会为这些数据分配适当大小的虚拟地址空间).
DWORD AddressOfEntryPoint; //程序入口(RVA) !!!!!****************!!!!!
DWORD BaseOfCode; //代码的节的基址(RVA).编译器填的没用(代码节起始的RVA,表示映像被加载进内存时代码节的开头相对于ImageBase的偏移地址,节的名称通常为".text")
DWORD BaseOfData; //数据的节的基址(RVA).编译器填的没用(数据节起始的RVA,表示映像被加载进内存时数据节的开头相对于ImageBase的偏移地址,节的名称通常为".data")
DWORD ImageBase; //内存镜像基址 !!!!!****************!!!!!
DWORD SectionAlignment; //内存对齐大小 !!!!!****************!!!!!
DWORD FileAlignment; //文件对齐大小 !!!!!****************!!!!!
WORD MajorOperatingSystemVersion; //标识操作系统主版本号
WORD MinorOperatingSystemVersion; //标识操作系统次版本号
WORD MajorImageVersion; //PE文件自身的主版本号
WORD MinorImageVersion; //PE文件自身的次版本号
WORD MajorSubsystemVersion; //运行所需子系统主版本号
WORD MinorSubsystemVersion; //运行所需子系统次版本号
DWORD Win32VersionValue; //子系统版本的值.必须为0,否则程序运行失败.
DWORD SizeOfImage; //内存中整个PE文件的映射尺寸.可比实际的值大.必须是SectionAlignment的整数倍 !!!!!****************!!!!!
DWORD SizeOfHeaders; //所有头+节表按照文件对齐后的大小.
!!!!!****************!!!!!
DWORD CheckSum; //校验和.大多数PE文件该值为0.在内核模式的驱动程序和系统DLL中,该值则是必须存在且是正确的.在IMAGEHLP.DLL中函数CheckSumMappedFile就是用来计算文件头校验和的,对于整个PE文件也有一个校验函数MapFileAndCheckSum.
!!!!!****************!!!!!
WORD Subsystem; //文件子系统 驱动程序(1) 图形界面(2) 控制台/DLL(3)
WORD DllCharacteristics; //文件特性.不是针对DLL文件的
DWORD SizeOfStackReserve; //初始化时保留的栈大小.该字段默认值为0x100000(1MB),如果调用API函数CreateThread时,堆栈参数大小传入NULL,则创建出来的栈大小将是1MB.
DWORD SizeOfStackCommit; //初始化时实际提交的栈大小.保证初始线程的栈实际占用内存空间的大小,它是被系统提交的.这些提交的栈不存在与交换文件里,而是在内存中.
DWORD SizeOfHeapReserve; //初始化时保留的堆大小.用来保留给初始进程堆使用的虚拟内存,这个堆的句柄可以通过调用函数GetProcessHeap获得.每一个进程至少会有一个默认的进程堆,该堆在进程启动时被创建,而且在进程的生命期中不会被删除.默认值为1MB.
DWORD SizeOfHeapCommit; //初始化时实践提交的堆大小.在进程初始化时设定的堆所占用的内存空间,默认值为PAGE_SIZE.
DWORD LoaderFlags; //调试相关
DWORD NumberOfRvaAndSizes; //目录项数目,默认为10h.
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];//结构数组 数组元素个数由IMAGE_NUMBEROF_DIRECTORY_ENTRIES定义
} IMAGE_OPTIONAL_HEADER32, * PIMAGE_OPTIONAL_HEADER32;
上面结构体即是32位的扩展PE头,其中的成员不必全部记忆,只用记忆注释中带有符号的 “ !!!!!****!!!!!”
第一个成员 WORD Magic; 这个成员是 识别该文件是32位还是64位的文件最为准确的方式,如果其中的数值是10B即为32位的文件,而如果其中的数值是20B就是64位的文件。
往后五个都是有关于链接器和编译器的,前两个分别是版本号,后三个是由编译器填写的一些统计数据,如果进行修改,其实对于运行也并不影响。
我们先来看这个DWORD ImageBase(内存镜像基址),因为每一个进程都有自己对应的4个G的虚拟内存,因为程序在硬盘中和内存中的状态不同,在内存中会根据文件对齐而展开,而这个ImageBase则决定了从哪里开始展开。
而在内存中包含了数据和程序,DWORD AddressOfEntryPoint则代表了该对应的程序是从哪里开始的,其代表了程序的入口。
然后,DWORD FileAlignment和DWORD SectionAlignment则已经在前面介绍过了,其代表了PE文件在内存和硬盘中的状态。
DWORD SizeOfImage这个成员代表了,PE文件在内存中映射的大小,一般来说都是比硬盘中的实际大小要大,因为内存对齐的原因,所以其大小需要是SectionAlignment的整数倍。DWORD SizeOfHeaders而这个就代表了是PE文件在硬盘中按照内存对齐后的大小。
DWORD CheckSum即为所有的文件内容加上文件长度,最后得到的一个四个字节的数据,以此来判断系统文件是否被修改。但是这样并不能保证文件是否真正未被修改,因为当我们修改文件的时候我们也会同时将该数据进行修改。
WORD DllCharacteristics则是文件特性,但不是针对DLL文件的
因为是WORD的大小,所以是一个字节的大小,一个字节16位,每一位都有它对应的含义。
DWORD NumberOfRvaAndSizes代表了有多少个表,而这些表则代表了之后的结构体数组的长度。
其他的成员,就不过多赘述,因为重要性相较于上面的几个不是那么大。做简单的了解就可以了。
标签:文件,IMAGE,PE,学习,内存,对齐,DWORD From: https://www.cnblogs.com/cyxyrq-code-loading/p/17547800.html