PE格式学习笔记 三
--PE头文件-Optional Header
PE可选头文件。
这里是头文件中最大的一部分。而且也是非常重要的。大部分PE文件相关内容、都是在这里记录的。前面说过MS的PE文件像一个垃圾回收站,在这里这种情况总算有所改变。
先列一下结构:
PImageOptionalHeader=^TImageOptionalHeader;
_IMAGE_OPTION_HEADER=packed record
{Standart Fields.}
Magic:Word;
MajorLinkerVersion:Byte;
MinorLinkerVersion:Byte;
SizeOfCode:DWORD;
SizeOfInitializedData:DWORD;
SizeOfUninitializedData:DWORD;
AddressOfEntryPoint:DWORD;
BaseOfCode:DWORD;
BaseOfData:DWORD;
{NT additional fields.}
ImageBase:DWORD;
SectionAlignment:DWORD;
FileAlignment:DWORD;
MajorOperationgSystemVersion:Word;
MinorOperationgSystemVersion:Word;
MajorImageVersion:Word;
MinorImageersion:Word;
MajorSubsystemVersion:Word;
MinorSubsystemVersion:Word;
Win32VersionValue:DWORD;
SizeOfImage:DWORD;
SizeOfHeaders:DWORD;
CheckSum:DWORD;
Subsystem:Word;
DllCharacteristics:Word;
SizeOfStackReserve:DWORD;
SizeOfStackCommit:DWORD;
SizeOfHeapReserve:DWORD;
SizeOfHeapCommit:DWORD;
LoaderFlags:DWORD;
NumberOfRvaAndSizes:DWORD;
DataDirectory: Packed array[0..IMAGE_NUMBEROF_DIRECTORY_ENTRIES-1] of TImageDataDirectory;
End;
一大堆的变量.到此为此,应该PE中的所有主要信息都包括全了吧!?
先记录一下各主要变量的表达意义:
Magic,可选头部的标志.其值通常为:$010B,占两个字节
接下来的两个字节是Link程序的主版本号和次版本号.基本上不用理会它.
SizeOfCode四个字节是代码段的大小,所有代码段总和的大小
下面八字节是已初始化和未初始化数据的大小;
AddressOfEntryPoint:装载器运行PE文件的第一条指令的虚拟地址;
BaseOfCode:代码节起始地址;
BaseOfData:数据节起始地址;
ImageBase:PE文件装载地址.装载器会试着将Exe装载到此地址来.
SectionAlignment:节的对齐值.保证内存中的Section从此值的整数倍的地址开始.
FileAlignment:文件对齐值.默认值为$200.保证Section的原始资料从此值的总数的地方开始.
下面六个参数为一些版本号信息.其中:MajorOperationgSystemVersion和MajorSubsystemVersion都默认为$40
再下面一个双字参数为保留项,一般不赋值,为0;
SizeOfImage:内存中PE映像的大小.此值总大或等于PE文件的实际大小.
SzieOfHeaders:PE头文件的总大小.此值即PE文件第一个节的偏移量.
CheckSum:CRC检验和.一般Exe文件中都为0.
Subsystem:识别PE文件属于哪个子系统.
DllCharacteristics:PE文件的Dll特征;
SizeOfStackReserve和SizeOfStackCommit:保留堆栈大小和提交堆栈大小.第二个总小于或等于第一个值。
SizeOfheapReserve和SizeOfheapCommit:为局部Heap保留的和提交的堆栈大小。第二个值总小于或等于第一个值;
LoaderFlags:用于调试一般为0.
NumberOfRvaAndSize:数据目录项数目,一般为16.
DataDirectory:IMAGE_DATA_DIRECTORY数组;
这里的定义的变量非常多.而且有很多是重要的数据,是一旦出错,会导致整个PE文件无法运行的变量.
定义是这样定义的,但是每个变量的作用还是要使用的时候才知道的.而且有一些并不是如定义中所说的那样的.
例如:读取跟在PE Option Header后面的节表.书上是这样读取的.
读取OptionHeader中的变量SizeOfHeaders根据此值来定位第一个节表的偏移,但实际上呢?你可以随便打开一个PE文件来看一下,此值并不一定与第一个节表的偏移对应.
所以我们读取第一个节表只有一个方法:
1.读取DosHeader中_lfanew.定位PE Header
2.读取PE Header中的所以变量数据.
3.读取完数据目录结构中最后一项时,此处就是第一个节表的起始位置;
在比较多的弄明白这里的各个变量所代表的意义以前,先来学习一下下面的内容--节.
在PE文件中,其数据是按类分别存放在不同的节之中的.我们可以把一个PE文件看成是一本条理很清楚的书.
前面的DosHeader和PEHeader都是前言和说明.它们对这本书的性质及大致内容做了一个定义.
而现在我们要了解的节表,可以看成是书的目录.他定义不同的节在PE文件里的偏移,以及此PE文件被装载到内存中后其内存位置的偏移.
再后面的节,就相当于书中的具体内容了.它的排列是按性质来排列的.如,所有代码内容一般全部放在第一个节中,数据内容,一般在第二个节中,再是资源......当然这里是没有顺序的,你可以按任何的次序来排放它们,当然他们的排放位置是有一定规则的.
SectionHeader结构列表:
PImageSectionHeader=^TImageSectionHeader;
_IMAGE_SECTION_HEADER=Packed record
Name:packed array[0..IMAGE_SIZEOF_SHORT_NAME-1] of Byte;
VirtualSize:DWORD;
VirtualAddress:DWORD;
SizeOfRawData:DWORD;
PointerToRawData:DWORD;
PointerToRelocaltions:DWORD;
PointerToLineNumbers:DWORD;
NumberOfRelocations:Word;
NumberOfLineNumbers:Word;
Characteristics:DWORD;
End;
依次意义如下:
Name:节名,八个字节,此值仅仅是一个标志.可随意修改,不会对程序运行造成任何影响.不同的编译器可能会有不同的定义规则.
VirtualSize:此节在内存中所占的大小;
VirtualAddress:引节在内存中的相对起始地址;
SizeOfRawData:经过对齐处理后此节的大小;
PointerToRawData:此节起始地址的物理偏移;
PointerToRelocations:OBJ文件所用,在PE文件中无意义;
PointerToLineNumbers:行号表的相对地址;
NumberOfRelocations:本节要重定位的数目;
NumberOfLineNumbers:本节在行号表中的数目;
Characteristics:节属性;
这里,目前我们要关心的是两个起始偏移,内存中的虚拟相对偏移,和PE文件的节起始偏移;
节表是一个数组,它成员的个数是由ImageFileHeader中的NumberOfSections决定的.
下面试着列出Windows装载PE文件各个节到内存的简单过程:
1.读取Dos Header,如果开头两个字节不是'MZ'则,退出.
2.读取Dos Header,最后一个变量,即PE Header的偏移.
3.根据读取的偏移跳过中间的Dos Stub,直接定位到PE Header起始位置.
4.验证起始四个字符是否'PE/0/0',如果不是则退出.
5.读取ImageFileHeader中的变量NumberOfSection.
6.读取ImageBase变量.此处为程序加载的基地址,以后的相对地址都是以此为偏移.
7.读取完所有的Header后,来到了节表的起始处.
8.读取节表的PointerToRawData变量,此为对应节在PE文件中的起始偏移.
9.读取节表中的SizeOfRawData
10.读取节表中的VirtualAddress
11.将PE文件偏移PointerToRawData处,SizeOfRawdata大小的数据加载到内存相对偏移VirtualAddress处.
判断加载的节个数是否等于NumberOfSection.如果小于此值,继续向下读重新8-11的操作.
这里的虚拟地址和相对地址是必须要搞清楚的.
虚拟地址是保护模式下的名词,如果你不是很清楚的话,可以理解为,Windows使用一定的方法,将不同大小的内存转化为同样的4G内存提供给不同的应用程序.
而相对地址则是一个偏移量,需要有一个基地址.其实际地址就是基地址加相对地址.
这样,我们大体上就了解了Windows加载一个PE文件的基本过程.
(摘自 http://tresss.com/myarticle/PeMakeLearn3.htm)
--PE头文件-Optional Header
PE可选头文件。
这里是头文件中最大的一部分。而且也是非常重要的。大部分PE文件相关内容、都是在这里记录的。前面说过MS的PE文件像一个垃圾回收站,在这里这种情况总算有所改变。
先列一下结构:
PImageOptionalHeader=^TImageOptionalHeader;
_IMAGE_OPTION_HEADER=packed record
{Standart Fields.}
Magic:Word;
MajorLinkerVersion:Byte;
MinorLinkerVersion:Byte;
SizeOfCode:DWORD;
SizeOfInitializedData:DWORD;
SizeOfUninitializedData:DWORD;
AddressOfEntryPoint:DWORD;
BaseOfCode:DWORD;
BaseOfData:DWORD;
{NT additional fields.}
ImageBase:DWORD;
SectionAlignment:DWORD;
FileAlignment:DWORD;
MajorOperationgSystemVersion:Word;
MinorOperationgSystemVersion:Word;
MajorImageVersion:Word;
MinorImageersion:Word;
MajorSubsystemVersion:Word;
MinorSubsystemVersion:Word;
Win32VersionValue:DWORD;
SizeOfImage:DWORD;
SizeOfHeaders:DWORD;
CheckSum:DWORD;
Subsystem:Word;
DllCharacteristics:Word;
SizeOfStackReserve:DWORD;
SizeOfStackCommit:DWORD;
SizeOfHeapReserve:DWORD;
SizeOfHeapCommit:DWORD;
LoaderFlags:DWORD;
NumberOfRvaAndSizes:DWORD;
DataDirectory: Packed array[0..IMAGE_NUMBEROF_DIRECTORY_ENTRIES-1] of TImageDataDirectory;
End;
一大堆的变量.到此为此,应该PE中的所有主要信息都包括全了吧!?
先记录一下各主要变量的表达意义:
Magic,可选头部的标志.其值通常为:$010B,占两个字节
接下来的两个字节是Link程序的主版本号和次版本号.基本上不用理会它.
SizeOfCode四个字节是代码段的大小,所有代码段总和的大小
下面八字节是已初始化和未初始化数据的大小;
AddressOfEntryPoint:装载器运行PE文件的第一条指令的虚拟地址;
BaseOfCode:代码节起始地址;
BaseOfData:数据节起始地址;
ImageBase:PE文件装载地址.装载器会试着将Exe装载到此地址来.
SectionAlignment:节的对齐值.保证内存中的Section从此值的整数倍的地址开始.
FileAlignment:文件对齐值.默认值为$200.保证Section的原始资料从此值的总数的地方开始.
下面六个参数为一些版本号信息.其中:MajorOperationgSystemVersion和MajorSubsystemVersion都默认为$40
再下面一个双字参数为保留项,一般不赋值,为0;
SizeOfImage:内存中PE映像的大小.此值总大或等于PE文件的实际大小.
SzieOfHeaders:PE头文件的总大小.此值即PE文件第一个节的偏移量.
CheckSum:CRC检验和.一般Exe文件中都为0.
Subsystem:识别PE文件属于哪个子系统.
DllCharacteristics:PE文件的Dll特征;
SizeOfStackReserve和SizeOfStackCommit:保留堆栈大小和提交堆栈大小.第二个总小于或等于第一个值。
SizeOfheapReserve和SizeOfheapCommit:为局部Heap保留的和提交的堆栈大小。第二个值总小于或等于第一个值;
LoaderFlags:用于调试一般为0.
NumberOfRvaAndSize:数据目录项数目,一般为16.
DataDirectory:IMAGE_DATA_DIRECTORY数组;
这里的定义的变量非常多.而且有很多是重要的数据,是一旦出错,会导致整个PE文件无法运行的变量.
定义是这样定义的,但是每个变量的作用还是要使用的时候才知道的.而且有一些并不是如定义中所说的那样的.
例如:读取跟在PE Option Header后面的节表.书上是这样读取的.
读取OptionHeader中的变量SizeOfHeaders根据此值来定位第一个节表的偏移,但实际上呢?你可以随便打开一个PE文件来看一下,此值并不一定与第一个节表的偏移对应.
所以我们读取第一个节表只有一个方法:
1.读取DosHeader中_lfanew.定位PE Header
2.读取PE Header中的所以变量数据.
3.读取完数据目录结构中最后一项时,此处就是第一个节表的起始位置;
在比较多的弄明白这里的各个变量所代表的意义以前,先来学习一下下面的内容--节.
在PE文件中,其数据是按类分别存放在不同的节之中的.我们可以把一个PE文件看成是一本条理很清楚的书.
前面的DosHeader和PEHeader都是前言和说明.它们对这本书的性质及大致内容做了一个定义.
而现在我们要了解的节表,可以看成是书的目录.他定义不同的节在PE文件里的偏移,以及此PE文件被装载到内存中后其内存位置的偏移.
再后面的节,就相当于书中的具体内容了.它的排列是按性质来排列的.如,所有代码内容一般全部放在第一个节中,数据内容,一般在第二个节中,再是资源......当然这里是没有顺序的,你可以按任何的次序来排放它们,当然他们的排放位置是有一定规则的.
SectionHeader结构列表:
PImageSectionHeader=^TImageSectionHeader;
_IMAGE_SECTION_HEADER=Packed record
Name:packed array[0..IMAGE_SIZEOF_SHORT_NAME-1] of Byte;
VirtualSize:DWORD;
VirtualAddress:DWORD;
SizeOfRawData:DWORD;
PointerToRawData:DWORD;
PointerToRelocaltions:DWORD;
PointerToLineNumbers:DWORD;
NumberOfRelocations:Word;
NumberOfLineNumbers:Word;
Characteristics:DWORD;
End;
依次意义如下:
Name:节名,八个字节,此值仅仅是一个标志.可随意修改,不会对程序运行造成任何影响.不同的编译器可能会有不同的定义规则.
VirtualSize:此节在内存中所占的大小;
VirtualAddress:引节在内存中的相对起始地址;
SizeOfRawData:经过对齐处理后此节的大小;
PointerToRawData:此节起始地址的物理偏移;
PointerToRelocations:OBJ文件所用,在PE文件中无意义;
PointerToLineNumbers:行号表的相对地址;
NumberOfRelocations:本节要重定位的数目;
NumberOfLineNumbers:本节在行号表中的数目;
Characteristics:节属性;
这里,目前我们要关心的是两个起始偏移,内存中的虚拟相对偏移,和PE文件的节起始偏移;
节表是一个数组,它成员的个数是由ImageFileHeader中的NumberOfSections决定的.
下面试着列出Windows装载PE文件各个节到内存的简单过程:
1.读取Dos Header,如果开头两个字节不是'MZ'则,退出.
2.读取Dos Header,最后一个变量,即PE Header的偏移.
3.根据读取的偏移跳过中间的Dos Stub,直接定位到PE Header起始位置.
4.验证起始四个字符是否'PE/0/0',如果不是则退出.
5.读取ImageFileHeader中的变量NumberOfSection.
6.读取ImageBase变量.此处为程序加载的基地址,以后的相对地址都是以此为偏移.
7.读取完所有的Header后,来到了节表的起始处.
8.读取节表的PointerToRawData变量,此为对应节在PE文件中的起始偏移.
9.读取节表中的SizeOfRawData
10.读取节表中的VirtualAddress
11.将PE文件偏移PointerToRawData处,SizeOfRawdata大小的数据加载到内存相对偏移VirtualAddress处.
判断加载的节个数是否等于NumberOfSection.如果小于此值,继续向下读重新8-11的操作.
这里的虚拟地址和相对地址是必须要搞清楚的.
虚拟地址是保护模式下的名词,如果你不是很清楚的话,可以理解为,Windows使用一定的方法,将不同大小的内存转化为同样的4G内存提供给不同的应用程序.
而相对地址则是一个偏移量,需要有一个基地址.其实际地址就是基地址加相对地址.
这样,我们大体上就了解了Windows加载一个PE文件的基本过程.
(摘自 http://tresss.com/myarticle/PeMakeLearn3.htm)