您好,欢迎访问三七文档
PE文件格式详解摘要WindowsNT3.1引入了一种名为PE文件格式的新可执行文件格式。PE文件格式的规范包含在了MSDN的CD中(SpecsandStrategy,Specifications,WindowsNTFileFormatSpecifications),但是它非常之晦涩。然而这一的文档并未提供足够的信息,所以开发者们无法很好地弄懂PE格式。本文旨在解决这一问题,它会对整个的PE文件格式作一个十分彻底的解释,另外,本文中还带有对所有必需结构的描述以及示范如何使用这些信息的源码示例。我为了获得PE文件中所包含的重要信息,编写了一个名为PEFILE.DLL的动态链接库,本文中所有出现的源码示例亦均摘自于此。这个DLL和它的源代码都作为PEFile示例程序的一部分包含在了CD中(译注:示例程序请在MSDN中寻找),你可以在你自己的应用程序中使用这个DLL;同样,你亦可以依你所愿地使用并构建它的源码。在本文末尾,你会找到PEFILE.DLL的函数导出列表和一个如何使用它们的说明。我觉得你会发现这些函数会让你从容应付PE文件格式的。介绍Windows操作系统家族最近增加的WindowsNT为开发环境和应用程序本身带来了很大的改变,这之中一个最为重大的当属PE文件格式了。新的PE文件格式主要来自于UNIX操作系统所通用的COFF规范,同时为了保证与旧版本MS-DOS及Windows操作系统的兼容,PE文件格式也保留了MS-DOS中那熟悉的MZ头部。在本文之中,PE文件格式是以自顶而下的顺序解释的。在你从头开始研究文件内容的过程之中,本文会详细讨论PE文件的每一个组成部分。许多单独的文件成分定义都来自于MicrosoftWin32SDK开发包中的WINNT.H文件,在这个文件中你会发现用来描述文件头部和数据目录等各种成分的结构类型定义。但是,在WINNT.H中缺少对PE文件结构足够的定义,在这种情况下,我定义了自己的结构来存取文件数据。你会在PEFILE.DLL工程的PEFILE.H中找到这些结构的定义,整套的PEFILE.H开发文件包含在PEFile示例程序之中。本文配套的示例程序除了PEFILE.DLL示例代码之外,还有一个单独的Win32示例应用程序,名为EXEVIEW.EXE。创建这一示例目的有二:首先,我需要测试PEFILE.DLL的函数,并且某些情况要求我同时查看多个文件;其次,很多解决PE文件格式的工作和直接观看数据有关。例如,要弄懂导入地址名称表是如何构成的,我就得同时查看.idata段头部、导入映像数据目录、可选头部以及当前的.idata段实体,而EXEVIEW.EXE就是查看这些信息的最佳示例。闲话少叙,让我们开始吧。PE文件结构PE文件格式被组织为一个线性的数据流,它由一个MS-DOS头部开始,接着是一个是模式的程序残余以及一个PE文件标志,这之后紧接着PE文件头和可选头部。这些之后是所有的段头部,段头部之后跟随着所有的段实体。文件的结束处是一些其它的区域,其中是一些混杂的信息,包括重分配信息、符号表信息、行号信息以及字串表数据。我将所有这些成分列于图1。图1.PE文件映像结构从MS-DOS文件头结构开始,我将按照PE文件格式各成分的出现顺序依次对其进行讨论,并且讨论的大部分是以示例代码为基础来示范如何获得文件的信息的。所有的源码均摘自PEFILE.DLL模块的PEFILE.C文件。这些示例都利用了WindowsNT最酷的特色之一——内存映射文件,这一特色允许用户使用一个简单的指针来存取文件中所包含的数据,因此所有的示例都使用了内存映射文件来存取PE文件中的数据。注意:请查阅本文末尾关于如何使用PEFILE.DLL的那一段。(未完待续)MS-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个字节,描述它内容的结构如下:WINNT.Htypedefstruct_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文件头部就是由它定位的。对于WindowsNT的PE文件来说,PE文件头部是紧跟在MS-DOS头部和实模式程序残余之后的。实模式残余程序实模式残余程序是一个在装载时能够被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:链接器选项来实现。PE文件头部与标志PE文件头部是由MS-DOS头部的e_lfanew域定位的,这个域只是给出了文件的偏移量,所以要确定PE头部的实际内存映射地址,就需要添加文件的内存映射基地址。例如,以下的宏是包含在PEFILE.H源文件之中的:PEFILE.H#defineNTSIGNATURE(a)((LPVOID)((BYTE*)a+\((PIMAGE_DOS_HEADER)a)-e_lfanew))在处理PE文件信息的时候,我发现文件之中有些位置需要经常查阅。既然这些位置仅仅是对文件的偏移量,那么用宏来实现这些定位就比较容易,因为它们较之函数有更好的表现。请注意这个宏所获得的是PE文件标志,而并非PE文件头部的偏移量。那是由于自Windows与OS/2的可执行文件开始,.EXE文件都被赋予了目标操作系统的标志。对于WindowsNT的PE文件格式而言,这一标志在PE文件头部结构之前。在Windows和OS/2的某些版本中,这一标志是文件头的第一个字。同样,对于PE文件格式,WindowsNT使用了一个DWORD值。以上的宏返回了文件标志的偏移量,而不管它是哪种类型的可执行文件。所以,文件头部是在DWORD标志之后,还是在WORD标志处,是由这个标志是否WindowsNT文件标志所决定的。要解决这个问题,我编写了ImageFileType函数(如下),它返回了映像文件的类型:PEFILE.CDWORDWINAPIImageFileType(LPVOIDlpFile){/*首先出现的是DOS文件标志*/if(*(USHORT*)lpFile==IMAGE_DOS_SIGNATURE){/*由DOS头部决定PE文件头部的位置*/if(LOWORD(*(DWORD*)NTSIGNATURE(lpFile))==IMAGE_OS2_SIGNATURE||LOWORD(*(DWORD*)NTSIGNATURE(lpFile))==IMAGE_OS2_SIGNATURE_LE)return(DWORD)LOWORD(*(DWORD*)NTSIGNATURE(lpFile));elseif(*(DWORD*)NTSIGNATURE(lpFile)==IMAGE_NT_SIGNATURE)returnIMAGE_NT_SIGNATURE;elsereturnIMAGE_DOS_SIGNATURE;}else/*不明文件种类*/return0;}以上列出的代码立即告诉了你NTSIGNATURE宏有多么有用。对于比较不同文件类型并且返回一个适当的文件种类来说,这个宏就会使这两件事变得非常简单。WINNT.H之中定义的四种不同文件类型有:WINNT.H#defineIMAGE_DOS_SIGNATURE0x5A4D//MZ#defineIMAGE_OS2_SIGNATURE0x454E//NE#defineIMAGE_OS2_SIGNATURE_LE0x454C//LE#defineIMAGE_NT_SIGNATURE0x00004550//PE00首先,Windows的可执行文件类型没有出现在这一列表中,这一点看起来很奇怪。但是,在稍微研究一下之后,就能得到原因了:除了操作系统版本规范的不同之外,Windows的可执行文件和OS/2的可执行文件实在没有什么区别。这两个操作系统拥有相同的可执行文件结构。现在把我们的注意力转向WindowsNTPE文件格式,我们会发现只要我们得到了文件标志的位置,PE文件之后就会有4个字节相跟随。下一个宏标识了PE文件的头部:PEFILE.C#definePEFHDROFFSET(a)((LPVOID)((BYTE*)a+\((PIMAGE_DOS_HEADER)a)-e_lfanew+\SIZE_OF_NT_SIGNATURE))这个宏与上一个宏的唯一不同是这个宏加入了一个常量SIZE_OF_NT_SIGNATURE。不幸的是,这个常量并未定义在WINNT.H之中,于是我将它定义在了PEFILE.H中,它是一个DWORD的大小。既然我们知道了PE文件头的位置,那么就可以检查头部的数据了。我们只需要把
本文标题:PE_文件格式详解
链接地址:https://www.777doc.com/doc-3393706 .html