Dos头:
-
Dos头的前两个字节恒为4D5A(只是作为判断PE文件的第一个标志,并不能通过它就能判断是否是PE文件)
-
Dos头的最后四个字节是指向NT头的偏移量
只有前两个字节和后面四个字节关系到PE文件是否正常运行 -
NT头:
image.png -
前面四个字节恒为0x4550,用于判断是否为PE文件的第二个标志
-
Nt头后面就是各个区段信息
文件头
image.png-
文件头大小0x14个字节(由图可知:它是Nt头的第二个元素)
-
扩展头的大小就在里面
-
节的数量也在里面
-
文件头里面保存了PE文件的一些属性(这里只列举了部分):
1.是否是dll(0x0210),exe(0x010F)
2.是否可执行
扩展头
image.pngimage.png
扩展头详解:
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//
WORD Magic; //表示这是一个什么类型的PE文件,32位一般是0x010B,64位的文件一般是0x020B
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode; //所有代码区段(节)的总大小(基于文件对齐后的大小)
DWORD SizeOfInitializedData; //已经初始化的数据的总大小
DWORD SizeOfUninitializedData; //未初始化的数据的大小
DWORD AddressOfEntryPoint; //程序开始执行的相对虚拟地址,即OEP,这是一个RVA,要想得到VA,则必须要加上ImageBase(下面有介绍!!!!!)
DWORD BaseOfCode; //起始代码的相对虚拟地址(RVA),就是.text段的RVA
DWORD BaseOfData; // 其实数据的相对虚拟地址(RVA),就是.data段的RVA
//
// NT additional fields.
//
DWORD ImageBase; //默认加载地址(如果没有这个基址会发生重定位)
DWORD SectionAlignment; //块对齐数,一般是0x1000
DWORD FileAlignment; //文件对齐数,一般是0x200
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage; //把文件加载进内存,所需要的内存大小,是进行了内存对齐之后的大小
DWORD SizeOfHeaders; //所有头部大小(这是按照文件对齐后的大小),也是文件主体相对文件起始的偏移,是所有头+节表的大小
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics; //文件(包括exe和dll文件)特征标志(见下面一张图)
DWORD SizeOfStackReserve; //表示进程中栈可以增长到的最大值,一般1M
DWORD SizeOfStackCommit; //进程中栈的初始值,据说也是栈每次分配增长的值,一般4KB
DWORD SizeOfHeapReserve; //表示进程中堆可以增长到的最大值,一般1M
DWORD SizeOfHeapCommit; //进程堆的初始值
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes; //数据目录的个数,也就是下面那个数组中元素的个数
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];//数据目录表,比较重要!
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
image.png
扩展头里面比较重要的在上面已经做出注释
再小结一波:
ImageBase(映像基址,装载基址,它是一个VA值):如果没有加载到这个地址则会重定位(就是PE文件加载进内存之后,就相当于知道了Dos头的位置,然后就可以知道其他的位置了),就是PE装入内存的基地址,默认情况下,EXE文件在内存中的基地址是0X0040 0000,DLL文件为0x0100 0000,由编译器决定!
程序入口点(OEP)
映像大小(SizeOfImage)------------>把文件加载进内存,所需要的内存大小(注意是进行了块对齐之后)
代码大小(SizeOfCode)------>所有区段的总大小
代码基址(BaseOfCode)起始代码的 RVA---->.text的RVA
数据基址(BaseOfData)起始数据的RVA----->.data的RVA
头大小(SizeOfHeaders)------------>所有头部大小,就是文件主体相对文件起始的偏移
内存对齐(SectionAlignment)----------->为0x1000(4KB)
文件对齐(FileAlignment)---------------->200h(0x200)
DLL标记(DllCharacteristics)-------->指示Dll特征的标志
PE头部包含了Dos头,一直到节表的结束位置,.text区段开始之前
-
数据目录表
image.png
数据目录表也是一个结构体数组------>每一个结构体里面记录的是每个表所对应的RVA以及大小
(扩展:
- 区段头表(它是一个结构体数组)是由多个IMAGE_SECTION_HEADER这样的结构体组成,以一个全是0的结构体结尾;
- 导入表也是一个结构体数组(后面会重点讲),以一个全0元素结尾,导入表中的IMAGE_THUNK_DATA(文件没有加载的时候,OrignalFirstThunk与FirstThunk指向IMAGE_THUNK_DATA)也是一个结构体数组;
- 重定位表:它也是一个结构体数组,以全0元素结尾
- 资源表:它里面也包含结构体数组(更为详细的可以查阅相关文献)
)
PE中有结构体数组的结构的总结:
数录节入重!
- 节表(区段头表)
.text 段:代码段
.data段:数据段
.bss段:表示未初始化的数据,比如Static变量
.rdata 段:表示只读的数据,比如字符串
……
.relcoc段:存储重定位信息的区段
各变量存放于哪个区:
常量 ------------------>.rdata区
静态变量------------->.bss区
全局变量-------------->.data 区
节表里面的几个重要数据:
VirtualAddress:这个区段的相对虚拟地址
SizeofRawData:这个区段在磁盘中的大小,进行了文件对齐
PointerToRawData:区段的文件偏移,就是这个区段在磁盘文件中的起始位置
一个重要的公式:
offset(转)=RVA(需要转换的RVA)-RVA(所在区段的RVA)+offset(就是PointerToRawData)
网友评论