PE文件结构详解.docx
《PE文件结构详解.docx》由会员分享,可在线阅读,更多相关《PE文件结构详解.docx(22页珍藏版)》请在冰豆网上搜索。
PE文件结构详解
PE文件结构详解
1摘要
WindowsNT3.1引入了一种名为PE文件格式的新可执行文件格式。
PE文件格式的规范包含在了MSDN的CD中(SpecsandStrategy,Specifications,WindowsNTFileFormatSpecifications),但是它非常之晦涩。
然而这一的文档并未提供足够的信息,所以开发者们无法很好地弄懂PE格式。
本文旨在解决这一问题,它会对整个的PE文件格式作一个十分彻底的解释,另外,本文中还带有对所有必需结构的描述以及示范如何使用这些信息的源码示例。
为了获得PE文件中所包含的重要信息,我编写了一个名为PEFILE.DLL的动态链接库,本文中所有出现的源码示例亦均摘自于此。
这个DLL和它的源代码都作为PEFile示例程序的一部分包含在了CD中(译注:
示例程序请在MSDN中寻找,本站恕不提供),你可以在你自己的应用程序中使用这个DLL;同样,你亦可以依你所愿地使用并构建它的源码。
在本文末尾,你会找到PEFILE.DLL的函数导出列表和一个如何使用它们的说明。
我觉得你会发现这些函数会让你从容应付PE文件格式的。
2介绍
Windows操作系统家族最近增加的WindowsNT为开发环境和应用程序本身带来了很大的改变,这之中一个最为重大的当属PE文件格式了。
新的PE文件格式主要来自于UNIX操作系统所通用的COFF规范,同时为了保证与旧版本MS-DOS及Windows操作系统的兼容,PE文件格式也保留了MS-DOS中那熟悉的MZ头部。
在本文之中,PE文件格式是以自顶而下的顺序解释的。
在你从头开始研究文件内容的过程之中,本文会详细讨论PE文件的每一个组成部分。
很多解决PE文件格式的工作和直接观看数据有关。
例如,要弄懂导入地址名称表是如何构成的,我就得同时查看.idata段头部、导入映像数据目录、可选头部以及当前的.idata段实体,而EXEVIEW.EXE就是查看这些信息的最佳示例。
在针对PE文件的有关编程中,你可能用到以下一些数据结构:
IMAGE_DOS_HEADER
IMAGE_IMPORT_DESCRIPTOR
IMAGE_NT_HEADERS
IMAGE_SECTION_HEADER
IMAGE_OPTIONAL_HEADER
IMAGE_DATA_DIRECTORY
IMAGE_FILE_HEADER
3PE文件结构图
图1PE文件结构
从MS-DOS文件头结构开始,我将按照PE文件格式各成分的出现顺序依次对其进行讨论,并且讨论的大部分是以示例代码为基础来示范如何获得文件的信息的。
3.1MS-DOS头部/实模式头部
PE文件格式的第一个组成部分是MS-DOS头部。
在PE文件格式中,它并非一个新概念,因为它与MS-DOS2.0以来就已有的MS-DOS头部是完全一样的。
保留这个相同结构的最主要原因是,当你尝试在Windows3.1以下或MS-DOS2.0以上的系统下装载一个文件的时候,操作系统能够读取这个文件并明白它是和当前系统不相兼容的。
换句话说,当你在MS-DOS6.0下运行一个WindowsNT可执行文件时,你会得到这样一条消息:
“ThisprogramcannotberuninDOSmode.”如果MS-DOS头部不是作为PE文件格式的第一部分的话,操作系统装载文件的时候就会失败,并提供一些完全没用的信息,例如:
“Thenamespecifiedisnotrecognizedasaninternalorexternalcommand,operableprogramorbatchfile.”
MS-DOS头部占据了PE文件的头64个字节,描述它内容的结构,即图1中的
(1)部分的定义如下:
typedefstruct_IMAGE_DOS_HEADER
{//DOS的.EXE头部
USHORTe_magic;//魔术数字
USHORTe_cblp;//文件最后页的字节数
USHORTe_cp;//文件页数
USHORTe_crlc;//重定义元素个数
USHORTe_cparhdr;//头部尺寸,以段落为单位
USHORTe_minalloc;//所需的最小附加段
USHORTe_maxalloc;//所需的最大附加段
USHORTe_ss;//初始的SS值(相对偏移量)
USHORTe_sp;//初始的SP值
USHORTe_csum;//校验和
USHORTe_ip;//初始的IP值
USHORTe_cs;//初始的CS值(相对偏移量)
USHORTe_lfarlc;//重分配表文件地址
USHORTe_ovno;//覆盖号
USHORTe_res[4];//保留字
USHORTe_oemid;//OEM标识符(相对e_oeminfo)
USHORTe_oeminfo;//OEM信息
USHORTe_res2[10];//保留字
LONGe_lfanew;//新exe头部的文件地址
}IMAGE_DOS_HEADER,*PIMAGE_DOS_HEADER;
第一个域e_magic,被称为魔术数字,它被用于表示一个MS-DOS兼容的文件类型。
所有MS-DOS兼容的可执行文件都将这个值设为0x5A4D,表示ASCII字符MZ。
MS-DOS头部之所以有的时候被称为MZ头部,就是这个缘故。
还有许多其它的域对于MS-DOS操作系统来说都有用,但是对于WindowsNT来说,这个结构中只有一个有用的域——最后一个域e_lfnew,一个4字节的文件偏移量,PE文件头部就是由它定位的。
且MS_DOS头部(dos_head)的地址即为文件映像后的基地址,对于WindowsNT的PE文件来说,PE文件头部是紧跟在MS-DOS头部和实模式程序残余之后的。
.操作
打开文件:
hFile=CreateFile(lpFileName,…);
MS-DOS头部地址:
dos_head=(IMAGE_DOS_HEADER*)basepointer;
打印MS-DOS头部信息:
3.2实模式残余程序
实模式残余程序是一个在装载时能够被MS-DOS运行的实际程序。
对于一个MS-DOS的可执行映像文件,应用程序就是从这里执行的。
对于Windows、OS/2、WindowsNT这些操作系统来说,MS-DOS残余程序就代替了主程序的位置被放在这里。
这种残余程序通常什么也不做,而只是输出一行文本,例如:
“ThisprogramrequiresMicrosoftWindowsv3.1orgreater.”当然,用户可以在此放入任何的残余程序,这就意味着你可能经常看到像这样的东西:
“Youcan''trunaWindowsNTapplicationonOS/2,it''ssimplynotpossible.”
当为Windows3.1构建一个应用程序的时候,链接器将向你的可执行文件中链接一个名为WINSTUB.EXE的默认残余程序。
你可以用一个基于MS-DOS的有效程序取代WINSTUB,并且用STUB模块定义语句指示链接器,这样就能够取代链接器的默认行为。
为WindowsNT开发的应用程序可以通过使用-STUB:
链接器选项来实现。
不同的文件,其大小不一样,即图1中
(2)部分的大小由MS-DOS头的域e_lfnew来确定。
3.3PE文件头部与标志
PE文件头部的地址(peheader)是由MS-DOS头部的e_lfanew域定位的,这个域只是给出了文件的偏移量,所以要确定PE头部的实际内存映射地址,就需要添加文件的内存映射基地址。
Peheader=dos_head+dos_head->e_lfanew。
PE文件头部的定义,即图1中(3)部分的定义如下:
TheIMAGE_NT_HEADERSstructurerepresentsthePEheaderformat.
typedefstruct_IMAGE_NT_HEADERS{
DWORDSignature;
IMAGE_FILE_HEADERFileHeader;
IMAGE_OPTIONAL_HEADEROptionalHeader;
}IMAGE_NT_HEADERS,*PIMAGE_NT_HEADERS;
其中,文件头FileHeader的结构体定义如下:
TheIMAGE_FILE_HEADERstructurerepresentstheCOFFheaderformat.
typedefstruct_IMAGE_FILE_HEADER{
WORDMachine;
WORDNumberOfSections;
DWORDTimeDateStamp;
DWORDPointerToSymbolTable;
DWORDNumberOfSymbols;
WORDSizeOfOptionalHeader;
WORDCharacteristics;
}IMAGE_FILE_HEADER,*PIMAGE_FILE_HEADER;
这个文件头结构中一个有用的入口是NumberOfSections域,它表示如果你要方便地提取文件信息的话,就需要了解多少个段,更明确一点来说,有多少个段头部和多少个段实体。
每一个段头部和段实体都在文件中连续地排列着,所以要决定段头部和段实体在哪里结束的话,段的数目是必需的。
以下的语句从PE文件头中提取了段的数目:
numberofsection=peHeader->FileHeader.NumberOfSections;
3.4PE可选头部
PE可执行文件中接下来的224个字节组成了PE可选头部。
虽然它的名字是“可选头部”,但是请确信:
这个头部并非“可选”,而是“必需”的。
可选头部的偏移量即为:
offset=dos_head->e_lfanew+SIZE_OF_NT_SIGNATURE(即:
4)+sizeof(IMAGE_FILE_HEADER)。
可选头部包含了很多关于可执行映像的重要信息,例如初始的堆栈大小、程序入口点的位置、首选基地址、操作系统版本、段对齐的信息等等。
IMAGE_OPTIONAL_HEADER结构如下:
TheIMAGE_OPTIONAL_HEADERstructurerepresentstheoptionalheaderformat.
typedefstruct_IMAGE_OPTIONAL_HEADER{
WORDMagic;
BYTEMajorLinkerVersion;
BYTEMinorLinkerVersion;
DWORDSizeOfCode;//Sizeofthecodesection,inbytes,orthesumofallsuchsections//iftherearemultiplecodesections.
DWORDSizeOfInitializedData;//Sizeoftheinitializeddatasection,inbytes,or//thesumofallsuchsectionsiftherearemultipleinitializeddatasections.
DWORDSizeOfUninitializedData;//Sizeoftheuninitializeddatasection,inbytes,orthesumofallsuchsectionsiftherearemultipleuninitializeddatasections.
DWORDAddressOfEntryPoint;//Pointertotheentrypointfunction,relativetothe//imagebaseaddress.TheentrypointfunctionisoptionalforDLLs.Whennoentry//pointispresent,thismemberiszero.
DWORDBaseOfCode;//Pointertothebeginningofthecodesection,relativetothe//imagebase.
DWORDBaseOfData;//Pointertothebeginningofthedatasection,relativetothe//imagebase.
DWORDImageBase;//Preferredaddressofthefirstbyteoftheimagewhenitisloaded//inmemory.Thisvalueisamultipleof64Kbytes.ThedefaultvalueforDLLsis//0x10000000.Thedefaultvalueforapplicationsis0x00400000.
DWORDSectionAlignment;//Alignmentofsectionsloadedinmemory,inbytes.Thisvalue//mustbegreaterthanorequaltotheFileAlignmentmember.Thedefaultvalueisthe//pagesizeforthesystem.
DWORDFileAlignment;
WORDMajorOperatingSystemVersion;
WORDMinorOperatingSystemVersion;
WORDMajorImageVersion;
WORDMinorImageVersion;
WORDMajorSubsystemVersion;
WORDMinorSubsystemVersion;
DWORDWin32VersionValue;
DWORDSizeOfImage;//Sizeoftheimage,inbytes,includingallheaders.Mustbea//multipleofSectionAlignment.
DWORDSizeOfHeaders;//CombinedsizeoftheMS-DOSstub,thePEheader,andthesection//headers,roundedtoamultipleofthevaluespecifiedintheFileAlignmentmember.
DWORDCheckSum;
WORDSubsystem;
WORDDllCharacteristics;
DWORDSizeOfStackReserve;
DWORDSizeOfStackCommit;
DWORDSizeOfHeapReserve;
DWORDSizeOfHeapCommit;
DWORDLoaderFlags;
DWORDNumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORYDataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
}IMAGE_OPTIONAL_HEADER,*PIMAGE_OPTIONAL_HEADER;
其中NumberOfRvaAndSizes这个域标识了接下来的DataDirectory数组。
请注意它被用来标识这个数组,而不是数组中的各个入口数字,这一点非常重要。
DataDirectory。
数据目录表示文件中其它可执行信息重要组成部分的位置。
它事实上就是一个IMAGE_DATA_DIRECTORY结构的数组,位于可选头部结构的末尾。
TheIMAGE_DATA_DIRECTORYstructurerepresentsthedatadirectory.
typedefstruct_IMAGE_DATA_DIRECTORY{
DWORDVirtualAddress;
DWORDSize;
}IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY;
当前的PE文件格式定义了16种可能的数据目录,这之中的11种现在在使用中。
数据目录的各个元素依次如下所示:
Thefollowingisalistofthedatadirectories:
Offset
Description
96
Exporttableaddressandsize
104
Importtableaddressandsize//输入表(引入表)
112
Resourcetableaddressandsize
120
Exceptiontableaddressandsize
128
Certificatetableaddressandsize
136
Baserelocationtableaddressandsize//重定位信息
144
Debugginginformationstartingaddressandsize
152
Architecture-specificdataaddressandsize
160
Globalpointerregisterrelativevirtualaddress
168
Threadlocalstorage(TLS)tableaddressandsize
176
Loadconfigurationtableaddressandsize
184
Boundimporttableaddressandsize//绑定输入表(引入表)
192
Importaddresstableaddressandsize
200
Delayimportdescriptoraddressandsize
208
Reserved
由上表可知,有如下定义#defineIMAGE_NUMBEROF_DIRECTORY_ENTRIES16。
4PE文件节表(段头部)
PE文件规范由目前为止定义的那些头部以及一个名为“段”的一般对象组成。
段包含了文件的内容,包括代码、数据、资源以及其它可执行信息,每个段都有一个头部和一个实体(原始数据)。
我将在下面描述段头部的有关信息,但是段实体则缺少一个严格的文件结构。
因此,它们几乎可以被链接器按任何的方法组织,只要它的头部填充了足够能够解释数据的信息。
段头部定义如下:
TheIMAGE_SECTION_HEADERstructurerepresentstheimagesectionheaderformat.
typedefstruct_IMAGE_SECTION_HEADER{
BYTEName[IMAGE_SIZEOF_SHORT_NAME];
union{
DWORDPhysicalAddress;//Fileaddress.
DWORDVirtualSize;//Totalsizeofthesectionwhenloadedintomemory,inbytes.//ifthisvalueisgreaterthantheSizeOfRawDatamember,thesectionisfilledwith//zeroes.
}Misc;
DWORDVirtualAddress;//Addressofthefirstbyteofthesectionwhenloadedintomemory,//relativetotheimagebase.
DWORDSizeOfRawData;//Sizeoftheinitializeddataondisk,inbytes.Thisvalue//mustbeamultipleoftheFileAlignmentmemberoftheIMAGE_OPTIONAL_HEADERstructure.//IfthisvalueislessthantheVirtualSizemember,theremainderofthesectionis//filledwithzeroes.Ifthesectioncontainsonlyuninitializeddata,thememberis//zero.
DWORDPointerToRawData;//FilepointertothefirstpagewithintheCOFFfile.Thisvaluemust//beamultipleoftheFileAlignmentmemberoftheIMAGE_OPTIONAL_HEADERstructure.If//asectioncontainsonlyuninitializeddata,thismemberiszero.//本节在文件中的偏移量
DWORDPointerToRelocations;//Filepointertothebeginningoftherelocationentries//forthesection.Iftherearenorelocations,thisvalueiszero.
DWORDPointerToLinenumbers;//Filepointertothebeginningoftheline-number//entriesforthesection.IftherearenoCOFFlinenumbers,thisvalueiszero.
WORDNumberOfRelocations;//Numberofrelocationentriesforthesection.Thisvalue//iszeroforexecutableimages.
WORDNumberOfLinenumbers;//Numberofline-numberentriesforthesection.
DWORDCharacteristics;
}IMAGE_SECTION_HEADER,*PIMAGE_SECTIO