PE格式详解Word格式文档下载.docx
《PE格式详解Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《PE格式详解Word格式文档下载.docx(20页珍藏版)》请在冰豆网上搜索。
这并不影响我们学习PE格式。
首先两个概念,PE文件被加载器加载到内存后被称为一个模块(module),存储模块首地址的称为模块句柄(handleofmodule),简写为HMODULE。
然后我们看一下PE格式的总体结构(这张图MS已经遍布全球了):
PICTUREMISSING
前4块我们后面会细讲,就区段来说(Section),一个PE文件至少要有2个区段,代码区段用来存储程序代码,以及数据区段用来存储各种数据。
NT为PE预定义了9个区段:
.text,.bss,.rdata,.data,.rsrc,.edata,.idata,.pdata,.debug。
一个PE既可以选择其中的几个区段,也可以自己定义额外的区段满足特别需要。
一般来说,.text是代码区段,.data、.rdata、.bss是数据区段,.rsrc是资源区段,.edata与.idata分别是输出输入表区段,.debug是调试信息区段。
这些名字(.text,.data…)实际上是给程序员看着方便的,执行程序时,系统会完全忽略它们。
最后一点,就是PE文件在硬盘中的结构顺序和当它们被加载到内存中后的顺序并非一样,记录硬盘上排列的是FileAlignment域(域是含有特定信息的二进制片段),记录内存中排列的是SectionAlignment域,这既是PE加载器调整的结果,也是虚拟内存机制的作用。
关于Windows虚拟内存机制,请参考相关资料,或者我的另一篇文章。
就是有一点要注意的是,PE文件中的每一个区段总是在一个新的页面中存放的。
一个PE程序总是以一个64字节的DOSheader结构开头,目的就是为了如果程序在DOS中运行,DOS会识别它为正确的EXE并进而运行DOSstub,它的作用就是输出字符串”ThisprogramcannotruninDOSmode.”然后退出。
下面就是DOS Header在C语言中的结构表示(来自MinGW的winnt.h)
typedefstruct_IMAGE_DOS_HEADER{
WORDe_magic;
//MagicDOS签名MZ(4Dh5Ah)
WORDe_cblp;
//文件最后一页的字节数
WORDe_cp;
//文件中的页数
WORDe_crlc;
//重定位的个数
WORDe_cparhdr;
//段落中头的长度
WORDe_minalloc;
//额外段落需要的最小分配值
WORDe_maxalloc;
// 额外段落需要的最大分配值
WORDe_ss;
//初始SS寄存器值
WORDe_sp;
//初始SP寄存器值
WORDe_csum;
//校验和
WORDe_ip;
//初始IP值
WORDe_cs;
//初始CS值
WORDe_lfarlc;
//重定位表在文件中的地址
WORDe_ovno;
//覆盖数量
WORDe_res[4];
//保留字
WORDe_oemid;
//OEM标识符
WORDe_oeminfo;
//OEM信息
WORDe_res2[10];
// 保留字
LONGe_lfanew;
//PEheader的起始偏移量
}IMAGE_DOS_HEADER,*PIMAGE_DOS_HEADER;
首先声明, WORD表示unsignedshortint(2bytes),而LONG和后面会出现的DWORD表示unsignedint(4bytes),其定义符合C99标准。
我们只关心其中两个成员,e_magic是MagicNumber,它总是等于0x4d5a,也就是MZ这两个字符,这是为了几纪念MarkZbikowsky,MS-DOS设计者之一。
e_lfanew指向的是PE header,这才是PE文件真正的开始,Win32EXE加载器会读取其中的地址并找到PEheader,DOSstub因此被跳过。
我们可以来实践一下下,用WinHEX打开我们的范例程序testPE.exe。
PICTUREMISSING
可以看到前两个byte,确实是MZ表示一个合法的EXE文件,图中的前四行(0h~3Fh)共64字节即为IMAGE_DOS_HEADER结构,e_lfanew是最后四个字节,从图中我们可以读出,位于3Ch~3Fh的依次是D8,00,00,00,因为机器中整型存放遵循高位在高地址处的原则,所以实际的值是00,00,00,D8,我们顺势找到D8h,马上就发现了PE两个字,从这里开始便是真正的PEheader。
那么位于40h到D7h的东西从PE结构图就可以看出是DOS stub,其实就是一段汇编代码,我们就不管了。
PEheader对应C中的IMAGE_NT_HEADER32结构,如下:
typedefstruct_IMAGE_NT_HEADERS{
DWORDSignature;
// PESignaturePE..(50h45h00h00h)
IMAGE_FILE_HEADERFileHeader;
// PE的文件头信息
IMAGE_OPTIONAL_HEADEROptionalHeader;
// 可选头
}IMAGE_NT_HEADERS32,*PIMAGE_NT_HEADERS32;
再是真实PE文件的截图:
(一)中我们已经得到PEheader的起始地址D8h,直到DBh,共4个字节,就是结构中的Signature成员,数据为50h45h00h00h,对应值为00004550h(注,以后凡说到值,均指按高位在高地址的原则倒过来后的实际值,不再说明),对应winnt.h中的常数IMAGE_NT_SIGNATURE,如果Signature等于454Ch表示是IMAGE_VXD_SIGNATURE,即Win3.X中的VirtualDeviceDriver;
等于454Eh表示IMAGE_OS2_SIGNATURE,即OS2的程序等等,请自行查阅winnt.h。
IMAGE_NT_HEADERS32结构只有三个成员,比较简单,但是展开后两个成员的结构体IMAGE_FILE_HEADER,IMAGE_OPTIONAL_HEADER,就会让人绝倒了。
。
我们首先看一下IMAGE_FILE_HEADER结构:
typedefstruct_IMAGE_FILE_HEADER{
WORDMachine;
// 支持机器的类型
WORDNumberOfSections;
// 区段的个数
DWORDTimeDateStamp;
// 连接器创建EXE时的日期时间
DWORDPointerToSymbolTable;
//旧文件中,COFF符号表地址,没有是0
DWORDNumberOfSymbols;
// COFF符号表中符号的个数
WORDSizeOfOptionalHeader;
//Optionalheader的长度(32位EXE中是224字节)
WORDCharacteristics;
// 见下面的说明
}IMAGE_FILE_HEADER,*PIMAGE_FILE_HEADER;
结合图,分析如下:
Machine值为014Ch,对应IMAGE_FILE_32BIT_MACHINE常数,即32位机器,其他值请查阅winnt.h。
接着两个字节表示NumberOfSections区段的个数,值为0007h,也就是我们的testPE.exe有7个区段。
SizeOfOptionalHeader位于ECh,EDh,值为00E0h,即后面的OptionalHeader结构总长为224字节。
再看一个Characteristics,表示这个PE文件的类型,图中位于EEh和EFh,值为0102h,是使用二进制位来标记的,二进制为100000010 =IMAGE_FILE_32BIT_MACHINE|IMAGE_FILE_EXECUTABLE_IMAGE,表示该文件是32位的EXE文件,如果它的值是2XXXh,则表示是一个DLL,具体还请查阅winnt.h。
(唉。
这句话打的好累啊,以后就说自行查阅了)
特地把Optionalheader放一篇文章,是因为它比较复杂与庞大,也因为它比较重要。
老样子,先是IMAGE_OPTIONAL_HEADER结构:
typedefstruct_IMAGE_OPTIONAL_HEADER{
WORDMagic;
// 又是Magic
BYTEMajorLinkerVersion;
// 链接器主要版本
BYTEMinorLinkerVersion;
// 链接器次要版本
DWORDSizeOfCode;
// 所有代码区段的长度之和
DWORDSizeOfInitializedData;
// 同上
DWORDSizeOfUninitializedData;
//代码起始执行处,对DLL可选,没有是0
DWORDAddressOfEntryPoint;
DWORDBaseOfCode;
// 载入内存后代码的起始地址,相对ImageBase而言
DWORDBaseOfData;
DWORDImageBase;
// 期望的内存中映像载入地址
DWORDSectionAlignment;
// 内存中区段排列基数
DWORDFileAlignment;
// 磁盘上区段排列基数
//OS最低主要版本号
WORDMajorOperatingSystemVersion;
//OS最低次要版本号
WORDMinorOperatingSystemVersion;
WORDMajorImageVersion;
// 映像文件的主要版本号
WORD