PE文件结构祥解.docx
《PE文件结构祥解.docx》由会员分享,可在线阅读,更多相关《PE文件结构祥解.docx(17页珍藏版)》请在冰点文库上搜索。
PE文件结构祥解
PE文件结构祥解
1摘要
WindowsNT引入了一种名为PE文件格式的新可执行文件格式。
PE文件格式的标准包括在了MSDN的CD中(SpecsandStrategy,Specifications,WindowsNTFileFormatSpecifications),可是它超级之晦涩。
但是这一的文档并未提供足够的信息,因此开发者们无法专门好地弄懂PE格式。
本文旨在解决这一问题,它会对整个的PE文件格式作一个十分完全的说明,另外,本文中还带有对所有必需结构的描述和示范如何利用这些信息的源码例如。
为了取得PE文件中所包括的重要信息,我编写了一个名为的动态链接库,本文中所有显现的源码例如亦均摘自于此。
那个DLL和它的源代码都作为PEFile例如程序的一部份包括在了CD中(译注:
例如程序请在MSDN中寻觅,本站恕不提供),你能够在你自己的应用程序中利用那个DLL;一样,你亦能够依你所愿地利用并构建它的源码。
在本文末尾,你会找到的函数导出列表和一个如何利用它们的说明。
我感觉你会发觉这些函数会让你从容应付PE文件格式的。
2介绍
Windows操作系统家族最近增加的WindowsNT为开发环境和应用程序本身带来了专门大的改变,这当中一个最为重大的当属PE文件格式了。
新的PE文件格式要紧来自于UNIX操作系统所通用的COFF标准,同时为了保证与旧版本MS-DOS及Windows操作系统的兼容,PE文件格式也保留了MS-DOS中那熟悉的MZ头部。
在本文当中,PE文件格式是以自顶而下的顺序说明的。
在你从头开始研究文件内容的进程当中,本文会详细讨论PE文件的每一个组成部份。
很多解决PE文件格式的工作和直接观看数据有关。
例如,要弄懂导入地址名称表是如何组成的,我就得同时查看.idata段头部、导入映像数据目录、可选头部和当前的.idata段实体,而确实是查看这些信息的最正确例如。
在针对PE文件的有关编程中,你可能用到以下一些数据结构:
IMAGE_DOS_HEADER
IMAGE_IMPORT_DESCRIPTOR
IMAGE_NT_HEADERS
IMAGE_SECTION_HEADER
IMAGE_OPTIONAL_HEADER
IMAGE_DATA_DIRECTORY
IMAGE_FILE_HEADER
3PE文件结构图
RVA记录在可选头部中
(3)
IMAGE_NT_HEADERS
(1)
(2)
(4)
IMAGE_SECTION_HEADER
lfnew-DOS头部长度
IMAGE_DOS_HEADER
DWORDSignature;
IMAGE_FILE_HEADER
IMAGE_OPTIONAL_HEADER
长度:
dosHeader->elfnew---dosHeader长度
图1PE文件结构
从MS-DOS文件头结构开始,我将依照PE文件格式各成份的显现顺序依次对其进行讨论,而且讨论的大部份是以例如代码为基础来示范如何取得文件的信息的。
MS-DOS头部/实模式头部
PE文件格式的第一个组成部份是MS-DOS头部。
在PE文件格式中,它并非一个新概念,因为它与MS-DOS以来就已有的MS-DOS头部是完全一样的。
保留那个相同结构的最要紧缘故是,当你尝试在Windows以下或MS-DOS以上的系统下装载一个文件的时候,操作系统能够读取那个文件并明白它是和当前系统不相兼容的。
换句话说,当你在MS-DOS下运行一个WindowsNT可执行文件时,你会取得如此一条消息:
“ThisprogramcannotberuninDOSmode.”若是MS-DOS头部不是作为PE文件格式的第一部份的话,操作系统装载文件的时候就会失败,并提供一些完全没用的信息,例如:
“Thenamespecifiedisnotrecognizedasaninternalorexternalcommand,operableprogramorbatchfile.”
MS-DOS头部占据了PE文件的头64个字节,描述它内容的结构,即图1中的
(1)部份的概念如下:
typedefstruct_IMAGE_DOS_HEADER
{XE头部
USHORTe_magic;5A作
打开文件:
hFile=CreateFile(lpFileName,…);
MS-DOS头部地址:
dos_head=(IMAGE_DOS_HEADER*)basepointer;
打印MS-DOS头部信息:
实模式残余程序
实模式残余程序是一个在装载时能够被MS-DOS运行的实际程序。
关于一个MS-DOS的可执行映像文件,应用程序确实是从那个地址执行的。
关于Windows、OS/二、WindowsNT这些操作系统来讲,MS-DOS残余程序就代替了主程序的位置被放在那个地址。
这种残余程序通常什么也不做,而只是输出一行文本,例如:
“ThisprogramrequiresMicrosoftWindowsorgreater.”固然,用户能够在此放入任何的残余程序,这就意味着你可能常常看到像如此的东西:
“Youcan''trunaWindowsNTapplicationonOS/2,it''ssimplynotpossible.”
当为Windows构建一个应用程序的时候,链接器将向你的可执行文件中链接一个名为的默许残余程序。
你能够用一个基于MS-DOS的有效程序取代WINSTUB,而且用STUB模块概念语句指示链接器,如此就能够够取代链接器的默许行为。
为WindowsNT开发的应用程序能够通过利用-STUB:
链接器选项来实现。
不同的文件,其大小不一样,即图1中
(2)部份的大小由MS-DOS头的域e_lfnew来确信。
PE文件头部与标志
PE文件头部的地址(peheader)是由MS-DOS头部的e_lfanew域定位的,那个域只是给出了文件的偏移量,因此要确信PE头部的实际内存映射地址,就需要添加文件的内存映射基地址。
Peheader=dos_head+dos_head->e_lfanew。
PE文件头部的概念,即图1中(3)部份的概念如下:
TheIMAGE_NT_HEADERSstructurerepresentsthePEheaderformat.
typedefstruct_IMAGE_NT_HEADERS{DWORD;IMAGE_FILE_HEADER;IMAGE_OPTIONAL_HEADER;
}IMAGE_NT_HEADERS,*PIMAGE_NT_HEADERS;
其中,文件头的结构体概念如下:
TheIMAGE_FILE_HEADERstructurerepresentstheCOFFheaderformat.
typedefstruct_IMAGE_FILE_HEADER{WORD;WORD;DWORD;DWORD;DWORD;WORD;WORD;
}IMAGE_FILE_HEADER,*PIMAGE_FILE_HEADER;
那个文件头结构中一个有效的入口是NumberOfSections域,它表示若是你要方便地提取文件信息的话,就需要了解多少个段,更明确一点来讲,有多少个段头部和多少个段实体。
每一个段头部和段实体都在文件中持续地排列着,因此要决定段头部和段实体在哪里终止的话,段的数量是必需的。
以下的语句从PE文件头中提取了段的数量:
numberofsection=peHeader->;
PE可选头部
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{WORD;BYTE;BYTE;DWORD;DWORD;DWORD;DWORD;TheentrypointfunctionisoptionalforDLLs.WhennoentryDWORD;DWORD;DWORD;Thisvalueisamultipleof64Kbytes.ThedefaultvalueforDLLsisThedefaultvalueforapplicationsis0x00400000.
DWORD;ThisvalueThedefaultvalueistheDWORD;WORD;WORD;WORD;WORD;WORD;WORD;DWORD;DWORD;MustbeaDWORD;DWORD;WORD;WORD;DWORD;DWORD;DWORD;DWORD;DWORD;DWORD;IMAGE_DATA_DIRECTORY[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
}IMAGE_OPTIONAL_HEADER,*PIMAGE_OPTIONAL_HEADER;
其中NumberOfRvaAndSizes那个域标识了接下来的DataDirectory数组。
请注意它被用来标识那个数组,而不是数组中的各个入口数字,这一点超级重要。
DataDirectory。
数据目录表示文件中其它可执行信息重要组成部份的位置。
它事实上确实是一个IMAGE_DATA_DIRECTORY结构的数组,位于可选头部结构的末尾。
TheIMAGE_DATA_DIRECTORYstructurerepresentsthedatadirectory.
typedefstruct_IMAGE_DATA_DIRECTORY{DWORD;DWORD;
}IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY;
当前的PE文件格式概念了16种可能的数据目录,这当中的11种此刻在利用中。
数据目录的各个元素依次如下所示:
Thefollowingisalistofthedatadirectories:
Offset
Description
96
Exporttableaddressandsize
104
都是用于库函数的引入
Importtableaddressandsize
typedefstruct_IMAGE_SECTION_HEADER{BYTE[IMAGE_SIZEOF_SHORT_NAME];union{DWORDPhysicalAddress;DWORDVirtualSize;};DWORD;DWORD;ThisvalueIfthesectioncontainsonlyuninitializeddata,thememberisDWORD;ThisvaluemustIf/本节在文件中的偏移量
DWORD;Iftherearenorelocations,thisvalueiszero.
DWORD;IftherearenoCOFFlinenumbers,thisvalueiszero.
WORD;ThisvalueWORD;DWORD;
}IMAGE_SECTION_HEADER,*PIMAGE_SECTION_HEADER;
段头部信息在文件中的位置已在图1的(4)部份模拟出,要取得所有段头部的总大小:
SecAllSize=numberofsection*sizeof(IMAGE_SECTION_HEADER);由于PE文件标志占4个字节,通常有如此的宏概念:
#defineSIZE_OF_NT_SIGNATURE4,即可按如下方式取得:
那么第一个段头部的偏移量可按如下方式取得:
SecOffset=dos_head->e_lfanew+SIZE_OF_NT_SIGNATURE+sizeof(IMAGE_FILE_HEADER)+peHeader->;
PE的所有段头部是线性排列,且各段头部大小相同,因此可将指向第一个段的指针向前移动sizeof(IMAGE_SECTION_HEADER)多个单元,以指向下一个段头部。
*(section_header+1)与section_header[1]指向同一个单元。
5PE文件的输入表
输入表的结构:
输入表是以一个IMAGE_IMPORT_DESCRIPTOR(IID)数组开始,一个程序要挪用几个dll就会有几个IID项,即每一个IID对应于一个dll。
IID结构:
typedefstruct_IMAGE_IMPORT_DESCRIPTOR
{
Union
{DWORDCharacteristics;Union
irtualAddress处,这是一个相对虚拟地址。
要定位到文件的真实物理地址,或定位到文件映射后在内存中的地址,只需要将OffSet转化为真实物理地址的偏移地址,即:
RVA-->RAW,假设是后者,加上文件映射后的基址就能够够准确信位到相应的内存单元,以操作IED结构数据。
准确信位后,只要读取sizeof(IMAGE_EXPORT_DIRECTORY)字节的数据,即可获取输出表的描述信息。
说明:
pBuffer为文件映射后的基址。
输出表指向一个IMAGE_EXPORT_DIRECTORY(IED),其结构概念如下:
typedefstructIMAGE_EXPORT_DIRECTORY
{
ULONGCharateristics;未利用,总为0
ULONGTimeDateStamp;文件生成时刻
USHORTMajorVersion;主版本号,一样为0
USHORTMinorVersion;次版本号,一样为0
ULONGName;模块中的真实名称
ULONGBase;基数,加上序数确实是函数地址数组的索引值
以上描述的地址均是RVA
02.第i个函数名称与第i个输出序号对应,但与第i个函数地址不对应。
他们之间存在如下的关系:
第i个函数名称对应的地址=(RVA1—>RAW)+第i个输出序号。
6PE文件的重定位表
为方便明白得,作如下概念:
PIMAGE_DOS_HEADERpDosHeader;VirtualAddress),判定RelocTableRVA是不是为0即可。
假设不为0,那么重定位表存在。
重定位表的数据组织方式是由许多重定位块串接而成。
每一个块是必需以4字节对齐,用0填补空缺。
重定位块的结构如下:
typedefstruct_IMAGE_BASE_RELOCATION{
DWORDVirtualAddress;
DWORDSizeOfBlock;
src。
目前咱们只考虑这种情形。
Section的结构说明如下:
typedefstruct_IMAGE_SECTION_HEADER
{
BYTEName[IMAGE_SIZEOF_SHORT_NAME];
Union
{
DWORDPhysicalAddress;
DWORDVirtualSize;
}Misc;
DWORDVirtualAddress;
DWORDSizeOfRawData;
DWORDPointerToRawData;
DWORDPointerToRelocations;
DWORDPointerToLinenumbers;
WORDNumberOfRelocations;
WORDNumberOfLinenumbers;
DWORDCharacteristics;
}IMAGE_SECTION_HEADER,*PIMAGE_SECTION_HEADER;
获取资源节表的首地址:
IMAGE_DOS_HEADER*dosHeadA=(IMAGE_DOS_HEADER*)pFileSource;src节
for(inti=0;iFileHeader.NumberOfSections;i++,secHeadA++)
{
if(strcmp((char*)secHeadA->Name,".rsrc")==0)
{src节
……
break;
}
}
下面是几个需要用到的结构与相关的说明:
typedefstruct_IMAGE_RESOURCE_DIRECTORY
{整个资源的结构就仿佛一棵树型,不同资源如:
menu,icon,dialog,cursor等。
都犹如每根树枝,树枝的Characteristics会标识不同的资源类型,而每根树枝又会有子树枝。
如此一直循环,直到IMAGE_RESOURCE_DIRECTORY的NumberOfIdEntries为0时才终止。
通常情形,子树都分为三层。
每一个子树的类型由IMAGE_RESOURCE_DIRECTORY中的Characteristics来标识。
如:
当第一层的Characteristics==3时,那么说明此结构为ICON资源。
Characteristics类型概念如下(可在中找到):
/*
*PredefinedResourceTypes
*/
#defineRT_CURSORMAKEINTRESOURCE
(1)
#defineRT_BITMAPMAKEINTRESOURCE
(2)
#defineRT_ICONMAKEINTRESOURCE(3)
#defineRT_MENUMAKEINTRESOURCE(4)
#defineRT_DIALOGMAKEINTRESOURCE(5)
#defineRT_STRINGMAKEINTRESOURCE(6)
#defineRT_FONTDIRMAKEINTRESOURCE(7)
#defineRT_FONTMAKEINTRESOURCE(8)
#defineRT_ACCELERATORMAKEINTRESOURCE(9)
#defineRT_RCDATAMAKEINTRESOURCE(10)
#defineRT_MESSAGETABLEMAKEINTRESOURCE(11)
要取得每一个子资源的入口地址。
那个地址要用到的一个结构是:
typedefstruct_IMAGE_RESOURCE_DIRECTORY_ENTRY
{
Union
{
struct
{
DWORDNameOffset:
31;
DWORDNameIsString:
1;
};
DWORDName;
WORDId;
};
Union
{
DWORDOffsetToData;也能够
//明白得为IMAGE_RESOURCE_DIRECTORY_ENTRY
//DirectoryEntries[NumberOfIdEntries];,
}IMAGE_RESOURCE_DIRECTORY,*PIMAGE_RESOURCE_DIRECTORY;
那如此一看来,IMAGE_RESOURCE_DIRECTORY_ENTRY的第一个地址等于父树地址加上IMAGE_RESOURCE_DIRECTORY结构的大小即可。
最后一个是IMAGE_RESOURCE_DATA_ENTRY结构,比较简单,大伙儿看一下就明白了。
typedefstruct_IMAGE_RESOURCE_DATA_ENTRY
{
DWORDOffsetToData;//相对虚拟地址,相关于根结点位置的偏移量
DWORDSize;
DWORDCodePage;
DWORDReserved;
}IMAGE_RESOURCE_DATA_ENTRY,*PIMAGE_RESOURCE_DATA_ENTRY;
该结构用于描述每一个叶子结点。
好了,讲了这么多,此刻咱们能够开始计算了,(咱们以读取第三层第一个ICON为例<通常资源都为三层>):
前面咱们已经取得根资源的地址:
dirResourceA
IMAGE_RESOURCE_DIRECTORY*dirResourceA=(IMAGE_RESOURCE_DIRECTORY*)((char*)pFileA+secHeadA->PointerToRawData);//根
IMAGE_RESOURCE_DIRECTORY_ENTRY*entryResourceA=(IMAGE_RESOURCE_DIRECTORY_ENTRY*)((DWORD)dirResourceA+sizeof(IMAGE_RESOURCE_DIRECTORY));
IMAGE_RESOURCE_DIRECTORY*dirTemp;//第二层
IMAGE_RESOURCE_DIRECTORY_ENTRY*entryTemp;
IMAGE_RESOURCE_DIRECTORY*dirTempICON;//第三层
IMAGE_RESOURCE_DIRECTORY_ENTRY*entryTempICON;
IMAGE_RESOURCE_DATA_ENTRY*entryData;//资源入口结构
for(i=0;i<(dirResourceA->NumberOfIdEntries+dirResourceA->NumberOfNamedEntries);i++,
entryResourceA++)
{//所有资源类型
if(entryResourceA->Name==3)
{//ICON
dirTemp=(IMAGE_RESOURCE_DIRECTORY*)((char*)dirResourceA+
entryResourceA->OffsetToDirectory);
entryTemp=(IMAGE_RESOURCE_DIRECTORY_ENTRY*)((char*)dirTemp+
sizeof(IMAGE_RESOURCE_DIRECTORY));
for(intk=0;k<(dirTemp->NumberOfIdEntries+dirTemp->NumberOfNamedEntries);
k++,entryTemp++)
{//每一类资源的资源个数:
子