PE文件结构.docx

上传人:b****0 文档编号:9038633 上传时间:2023-05-16 格式:DOCX 页数:47 大小:62.13KB
下载 相关 举报
PE文件结构.docx_第1页
第1页 / 共47页
PE文件结构.docx_第2页
第2页 / 共47页
PE文件结构.docx_第3页
第3页 / 共47页
PE文件结构.docx_第4页
第4页 / 共47页
PE文件结构.docx_第5页
第5页 / 共47页
PE文件结构.docx_第6页
第6页 / 共47页
PE文件结构.docx_第7页
第7页 / 共47页
PE文件结构.docx_第8页
第8页 / 共47页
PE文件结构.docx_第9页
第9页 / 共47页
PE文件结构.docx_第10页
第10页 / 共47页
PE文件结构.docx_第11页
第11页 / 共47页
PE文件结构.docx_第12页
第12页 / 共47页
PE文件结构.docx_第13页
第13页 / 共47页
PE文件结构.docx_第14页
第14页 / 共47页
PE文件结构.docx_第15页
第15页 / 共47页
PE文件结构.docx_第16页
第16页 / 共47页
PE文件结构.docx_第17页
第17页 / 共47页
PE文件结构.docx_第18页
第18页 / 共47页
PE文件结构.docx_第19页
第19页 / 共47页
PE文件结构.docx_第20页
第20页 / 共47页
亲,该文档总共47页,到这儿已超出免费预览范围,如果喜欢就下载吧!
下载资源
资源描述

PE文件结构.docx

《PE文件结构.docx》由会员分享,可在线阅读,更多相关《PE文件结构.docx(47页珍藏版)》请在冰点文库上搜索。

PE文件结构.docx

PE文件结构

PE文件结构

(一)

一、前言(Preface)

------------------

PE(“portableexecutable”,可移植的可执行文件)文件格式,是微软WindwosNT,Windows95和Win32子集①中的可执行的二进制文件的格式;在WindowsNT中,驱动程序也是这种格式。

它还能被应用于各种目标文件②和库文件中。

这种文件格式是由微软设计的,并于1993年被TIS(toolinterfacestandard,工具接口标准)委员会(由Microsoft,Intel,Borland,Watcom,IBM,等等组成)所批准,它明显的基于COFF文件格式的许多知识。

COFF(“commonobjectfilefromat”,通用目标文件格式)是应用于好几种UNIX系统③和VMS④系统中的目标文件和可执行文件的格式。

Win32SDK⑤中包含一个名叫的头文件,其中含有很多用于PE格式的#define和typedef定义。

我将逐步地提到其中的很多结构成员名字和#define定义。

你也可能发现DLL文件“imagehelp.dll”很有用途,它是WindowNT的一部分,但其书面文件却很缺乏。

它的一些功用在“DeveloperNetwork”(开发者网络)中有所描述。

二、总览(GeneralLayout)

-------------------------

在一个PE文件的开始处,我们会看到一个MS-DOS可执行体(英语叫“stub”,意为“根,存根”);它使任何PE文件都是一个有效的MS-DOS可执行文件。

在DOS-根之后是一个32位的签名以及魔数0x00004550(IMAGE_NT_SIGNATURE)(意为“NT签名”,也就是PE签名;十六进制数45和50分别代表ASCII码字母E和P----译者注)。

之后是文件头(按COFF格式),用来说明该二进制文件将运行在何种机器之上、分几个区段、链接的时间、是可执行文件还是DLL、等等。

(本文中可执行文件和DLL文件的区别在于:

DLL文件不能被启动,但能被别的二进制文件使用,而一个二进制文件则不能链接到另一个可执行文件。

那些之后,是可选头(尽管它一直都存在,却仍被称作“可选”----因为COFF文件格式仅为库文件使用一个“可选头”,却不为目标文件使用一个“可选头”,这就是为什么它被称为“可选”的原因)。

它会告诉我们该二进制文件怎样被载入的更多信息:

开始的地址呀、保留的堆栈数呀、数据段的大小呀、等等。

可选头的一个有趣的部分是尾部的“数据目录”数组;这些目录包含许多指向各“节”数据的指针。

例如:

如果一个二进制文件拥有一个输出目录,那么你就会在数组成员“IMAGE_DIRECTORY_ENTRY_EXPORT”(输出目录项)中找到一个指向那个目录的指针,而该指针指向文件中的某节。

跟在各种头后面我们就发现各个“节”了,它们都由“节头”引导。

本质上讲,各节中的内容才是你执行一个程序真正需要的东西,所有头和目录这些东西只是为了帮助你找到它们。

每节都含有和对齐、包含什么样的数据(如“已初始化数据”等等)、是否能共享等有关的一些标记,还有就是数据本身。

大多数(并非所有)节都含有一个或多个可通过可选头的“数据目录”数组中的项来参见的目录,如输出函数目录和基址重定位目录等。

无目录形式的内容有:

例如“可执行代码”或“已初始化数据”等。

+-------------------+

|DOS-stub|--DOS-头

+-------------------+

|file-header|--文件头

+-------------------+

|optionalheader|--可选头

|----------|

||

|datadirectories|--数据目录

||

+-------------------+

||

|sectionheaders|--节头

||

+-------------------+

||

|section1|--节1

||

+-------------------+

||

|section2|--节2

||

+-------------------+

||

|...|

||

+-------------------+

||

|sectionn|--节n

||

+-------------------+

三、DOS-根和签名(DOS-stubandSignature)

-----------------------------------------

DOS-根的概念很早从16位windows的可执行文件(当时是“NE”格式⑥)时就广为人知了。

根原来是用于OS/2⑦系统的可执行文件的,也用于自解压档案文件和其它的应用程序。

对于PE文件来说,它是一个总是由大约100个字节所组成的和MS-DOS2.0兼容的可执行体,用来输出象“thisprogramneedswindowsNT”之类的错误信息。

你可以通过确认DOS-头部分是否为一个IMAGE_DOS_HEADER(DOS头)结构来认出DOS-根,它的前两个字节必须为连续的两个字母“MZ”(有一个#defineIMAGE_DOS_SIGNATURE的定义是针对这个WORD单元的)。

你可以通过跟在后面的签名来将一个PE二进制文件和其它含有根的二进制文件区分开来,跟在后面的签名可由头成员'e_lfanew'(它是从字节偏移地址60处开始的,有32字节长)所设定的偏移地址找到。

对于OS/2系统和Windows系统的二进制文件来说,签名是一个16位的word单元;对于PE文件来说,它是一个按照8位字节边界对齐的32位的longword单元,并且IMAGE_NT_SIGNATURE(NT签名)的值已由#defined定义为0x00004550(即字母“PE/0/0”----译者)。

四、文件头(FileHeader)

-------------------------

要到达IMAGE_FILE_HEADER(文件头)结构,请先确认DOS-头“MZ”(起始的2个字节),然后找出DOS-根的头部的成员“e_lfanew”,并从文件开始处跳过那么多的字节。

在核实你在那里找到的签名后,IMAGE_FILE_HEADER(文件头)结构的文件头就紧跟其后开始了,下面我们将从头至尾的介绍其成员。

1)第一个成员是“Machine(机器)”,一个16位的值,用来指出该二进制文件预定运行于什么样的系统。

已知的合法的值有:

IMAGE_FILE_MACHINE_I386(0x14c)

Intel80386处理器或更高

0x014d

Intel80386处理器或更高

0x014e

Intel80386处理器或更高

0x0160

R3000(MIPS⑧)处理器,大尾⑨

IMAGE_FILE_MACHINE_R3000(0x162)

R3000(MIPS)处理器,小尾

IMAGE_FILE_MACHINE_R4000(0x166)

R4000(MIPS)处理器,小尾

IMAGE_FILE_MACHINE_R10000(0x168)

R10000(MIPS)处理器,小尾

IMAGE_FILE_MACHINE_ALPHA(0x184)

DECAlphaAXP⑩处理器

IMAGE_FILE_MACHINE_POWERPC(0x1F0)

IBMPowerPC,小尾

2)然后是“NumberOfSections(节数)”成员,16位的值。

它是紧跟在头后面的节的数目。

我们以后将讨论节的问题。

3)下一个成员是时间戳“TimeDateStamp”(32位),用来给出文件建立的时间。

即使它的“官方”版本号没有改变,你也可通过这个值来区分同一个文件的不同版本。

(除了同一个文件的不同版本之间必须唯一,时间戳的格式没有明文规定,但似乎是按照UTC?

时间“从1970年1月1日00:

00:

00算起的秒数值”----也就是大多数C语言编译器给time_t标志使用的格式。

这个时间戳是用来绑定各个输入目录的,我们稍后再讨论它。

警告:

有一些链接器往往将时间戳设为荒唐的值,而不是如前所述的time_t格式的链接时间。

4-5)成员“PointerToSymbolTable(符号表指针)”和成员“NumberOfSymbols(符号数)”(都是32位)都用于调试信息的。

我不知道该怎样去解读它,并且我发现该指针的值总为0。

6)成员“SizeOfOptionalHeader(可选头大小)”(16位)只是“IMAGE_OPTIONAL_HEADER(可选头)”项的大小,你能用它去验证PE文件结构的正确性。

7)成员“Characteristics(特性)”是一个16位的,由许多标志位形成的集合组成,但大多数标志位只对目标文件和库文件有效。

具体如下:

位0IMAGE_FILE_RELOCS_STRIPPED(重定位被剥离文件)表示如果文件中没有重定位信息,该位置1,这就表明各节的重定位信息都在它们各自的节中;可执行文件不使用该位,它们的重定位信息放在下面将要描述的“baserelocation”(基址重定位)目录中。

位1IMAGE_FILE_EXECUTABLE_IMAGE(可执行映象文件)表示如果文件是一个可执行文件,也即不是目标文件或者库文件时,置1。

如果链接器尝试创建一个可执行文件,却因为一些原因失败了,并保存映像以便下次例如增量链接时使用,此时此标志位也可能置1。

位2IMAGE_FILE_LINE_NUMS_STRIPPED(行数被剥离文件)表示如果行数信息被剥除,此位置1;此位也不用于可执行文件。

位3IMAGE_FILE_LOCAL_SYMS_STRIPPED(本地符号被剥离文件)表示如果文件中没有关于本地符号的信息时,此位置1(此位也不用于可执行文件)。

位4IMAGE_FILE_AGGRESIVE_WS_TRIM(强行工作集修剪文件)表示如果操作系统被假定为:

通过将正在运行的进程(它所使用的内存数量)强行的页清除来修剪它的工作集时,此位置1。

如果一进程是大部分时间处于等待,且一天中仅被唤醒一次的演示性的应用程序之类时,此位也应该被置1。

位7IMAGE_FILE_BYTES_REVERSED_LO(低字节变换文件)和位15IMAGE_FILE_BYTES_REVERSED_HI(高字节变换文件)表示如果一文件的字节序不是机器所预期的形式,因此它在读入前必须调换字节时,此位置1。

这样做对可执行文件是不可靠的(操作系统期望可执行文件都已经被正确地按字节排整齐了)。

位8IMAGE_FILE_32BIT_MACHINE(32位机器文件)表示如果使用的机器被期望为32位的机器时,此位置1。

现在的应用程序总将此位置1;NT5系统可能工作不同。

位9IMAGE_FILE_DEBUG_STRIPPED(调试信息被剥离文件)表示如果文件中没有调试信息,此位置1。

此位可执行文件不用。

按照其它信息([6])(这里指的是参考书目中的第[6]种----译者注),此位被称作“恒定”,并且当一个映象文件只有在被装入优先的装入地址才能运行(亦即:

此文件不可重定位)时,此位置1。

位10IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP(移动介质文件从交换文件运行)表示如果一个应用程序不可以从可移动的介质,如软盘或CD-ROM上运行时,此位置1。

在这种情况下,建议操作系统将文件复制到交换文件并从那里执行。

位11IMAGE_FILE_NET_RUN_FROM_SWAP(网络文件从交换文件运行)表示如果一个应用程序不可以从网络上运行时,此位置1。

在这种情况下,建议操作系统将文件复制到交换文件并从那里执行。

位12IMAGE_FILE_SYSTEM(系统文件)表示如果文件是一个象驱动程序那样的系统文件,此位置1。

此位可执行文件不用;我所见过的所有NT系统的驱动程序也不用。

位13IMAGE_FILE_DLL(DLL文件)表示如果文件是一个DLL文件时,此位置1。

位14IMAGE_FILE_UP_SYSTEM_ONLY(仅但处理器系统的文件)表示如果文件不设计运行在多处理器系统上(也就是说,因为此文件严格地依赖单一处理器的一些方式工作,所以它会发生冲突)时,此位置1。

五、相对虚拟地址(RelativeVirtualAddresses)

---------------------------------------------

PE格式大量地使用所谓的RVA(相对虚拟地址)。

一个RVA,亦即一个“RelativeVirtualAddresses(相对虚拟地址)”,是在你不知道基地址时,被用来描述一个内存地址的。

它是需要加上基地址才能获得线性地址的数值。

基地址就是PE映象文件被装入内存的地址,并且可能会随着一次又一次的调用而变化。

例如:

假若一个可执行文件被装入的地址是0x400000,并且从RVA0x1560处开始执行,那么有效的执行开始处将位于0x401560地址处。

假若它被装入的地址为0x100000,那么执行开始处就位于0x101560地址处。

因为PE-文件中的各部分(各节)不需要像已载入的映象文件那样对齐,事情变得复杂起来。

例如,文件中的各节常按照512(十六进制的0x200----译者注)字节边界对齐,而已载入的映象文件则可能按照4096(十六进制的0x1000----译者注)字节边界对齐。

参见下面的“SectionAlignment(节对齐)”和“FileAlignment(文件对齐)”。

因此,为了在PE文件中找到一个特定RVA地址的信息,你得按照文件已被载入时的那样来计算偏移量,但要按照文件的偏移量来跳过。

试举一例,假若你已知道执行开始处位于RVA0x1560地址处,并且想从那里开始的代码处反汇编。

为了从文件中找到这个地址,你得先查明在RAM(内存)中各节是按照4096字节对齐的,并且“.code”节是从RVA0x1000地址处开始,有16384字节长;然后你才知道RVA0x1560地址位于此节的偏移量0x560处。

你还要查明在文件中那节是按512字节边界对齐,且“.code”节在文件中从偏移量0x800处开始,然后你就知道在文件中代码的执行开始处就在0x800+0x560=0xd60字节处。

然后你反汇编它并发现访问一个变量的线性地址位于0x1051d0处。

二进制文件的线性地址在装入时将被重定位,并常被假定使用的是优先载入地址。

因为你已查明优先载入地址为0x100000,因此我们可开始处理RVA0x51d0了。

因数据节开始于RVA0x5000处,且有2048字节长,所以它处于数据节中。

又因数据节在文件中开始于偏移量0x4800处,所以该变量就可以在文件中的0x4800+0x51d0-0x5000=0x49d0处找到。

六、可选头(OptionalHeader)

----------------------------

紧跟在文件头后面的就是IMAGE_OPTIONAL_HEADER(尽管它名叫“可选头”,它却一直都在那里)。

它包含有怎样去准确处理PE文件的信息。

我们也将从头至尾的介绍其成员。

1)第一个16位的word单元叫“Magic(魔数)”,就我目前所观察过的PE文件而言,它的值总是0x010b。

2-3)下面2个字节是创建此文件的链接器的版本(‘MajorLinkerVersion’,“链接器主版本号”和‘MinorLinkerVersion’,“链接器小版本号”)。

这两个值又是不可靠的,并不能总是正确地反映链接器的版本号。

(有好几个链接器根本就不设置这个值。

)况且,你可想象一下,你连使用的是“什么”链接器都不知道,知道它的版本号又有什么作用呢?

4-6)下面3个longword(每个32位)分别用来设定可执行代码的大小(“SizeOfCode”)、已初始化数据的大小(“SizeOfInitializedData”,所谓的“数据段”)、以及未初始化数据的大小(“SizeOfUninitializedData”,所谓的“bss?

段”)。

这些值也是不可靠的(例如:

数据段实际上可能会被编译器或者链接器分成好几段),并且你可以通过查看可选头后面的各个“节”来获得更准确的大小。

7)下一个32位值是RVA。

这个RVA是代码入口点的偏移量(‘AddressOfEntryPoint’,“入口点地址”)。

执行将从这里开始,它可以是:

例如DLL文件的LibMain()的地址,或者一个程序的开始代码(这里相应的叫main())的地址,或者驱动程序的DriverEntry()的地址。

如果你敢于“手工”装载映象文件,那么在你完成所有的修正和重定位后,你可以从这个地址开始执行你的进程。

8-9)下两个32位值分别是可执行代码的偏移值(‘BaseOfCode’,“代码基址”)和已初始化数据的偏移值(‘BaseOfData’,“数据基址”),两个都是RVA,并且两个对我们来说都没有多少意义,因为你可以通过查看可选头后面的各个“节”来获得更可靠的信息。

未初始化的数据没有偏移量,正因为它没有初始化,所以在映象文件中提供这些数据是没有用处的。

10)下一项是个32位值,提供整个二进制文件包括所有头的优先(线性)载入地址(‘ImageBase’,“映象文件基址”)。

这是一个文件已被链接器重定位后的地址(总是64KB的倍数)。

如果二进制文件事实上能被载入这个地址,那么加载器就不用再重定位文件了,也就节省了一些载入时间。

优先载入地址在另一个映象文件已被先载入那个地址(“地址冲突”,在当你载入好几个全部按照链接器的缺省值重定位的DLL文件时经常发生)时,或者该内存已被用于其它目的(堆栈、malloc()、未初始化数据、或不管是什么)时,就不能用了。

在这些情况下,映象文件必须被载人其它的地址,并且需要重定位(参见下面的“重定位目录”)。

如果是一个DLL文件,这么做还会产生其它问题,因为此时的“绑定输入”已不再有效,所以使用DLL的二进制文件必须被修正----参见下面的“输入目录”一节。

11-12)下两个32位值分别是RAM中的“SectionAlignment”(当映象文件已被载入后,意为“节对齐”)和文件中的“FileAlignment”(文件对齐),它们都是PE文件的各节的对齐值。

这两个值通常都是32,或者是:

FileAlignment为512,SectionAlignment为4096。

节会在以后讨论。

13-14)下2个16位word单元都是预期的操作系统版本信息(MajorOperatingSystemVersion,“操作系统主版本号”)和(MinorOperatingSystemVersion,“操作系统小版本号”)[它们都使用微软自己书面确定的名字]。

这个版本信息应该为操作系统的版本号(如NT或Win95),而不是子系统的版本信息(如Win32)。

版本信息常常被不提供或者错误提供。

很明显的,加载器并不使用它们。

15-16)下2个16位word单元都是本二进制文件的版本信息('MajorImageVersion'“映象文件主版本号”和

'MinorImageVersion'“映象文件小版本号”)。

很多链接器不正确地设定这个信息,许多程序员也懒得提供这些,因此即便存在这样的信息,你最好也不要信赖它。

17-18)下2个16位word单元都是预期的子系统版本信息('MajorSubsystemVersion'“子系统主版本号”和'MinorSubsystemVersion'“子系统小版本号”)。

此信息应该为Win32或POSIX的版本信息,因为很明显的,16位程序或OS/2程序都不是PE格式的。

子系统版本应该被正确的提供,因为它“会”被检验和使用:

如果一个应用程序是一个Win32-GUI应用程序并运行于NT4系统之上,而且子系统版本“不是”4.0的话,那么对话框就不会是以3D形式显示,并且一些其它的特征也只会按“老式”的方式工作,因为此应用程序预期是在NT3.51系统上运行的,而NT3.51系统上只有程序管理器而没有浏览器、等等,于是NT4.0系统就尽可能地仿照那个系统的行为来运行程序。

19)然后,我们便碰到32位的“Win32VersionValue”(Win32版本值)。

我不清楚它有什么作用。

在我所观察过的PE文件中,它全部都为0。

20)下一个是32位值,给出映象文件将要使用的内存数量,单位为字节(‘SizeOfImage’,“映象文件大小”)。

如果是按照“SectionAlignment”对齐的,它就是所有头和节的长度的总和。

它提示加载器,为了载入映象文件需要多少页。

21)下一个是32位值,给出所有头的总长度,包括数据目录和节头(‘SizeOfHeaders’,“头的大小”)。

同时,它也是从文件的开头到第一节的原始数据的偏移量。

22)然后,我们发现一个32位的校验和(“CheckSum”)。

这个校验和,对于当前的NT版本,只在映象文件是NT驱动程序时才校验(如果校验和不正确,驱动就将装载失败)。

对于其他的二进制文件形式,校验和不需提供并且可能为0。

计算校验和的算法是微软的私产,他们不会告诉你的。

但是,Win32SDK的好几个工具都会计算和/或补正一个有效的校验和,而且imagehelp.dll中的CheckSumMappedFile()函数也会做同样的工作。

使用校验和的目的是为了防止载入无论如何都会冲突的、已损坏的二进制文件----况且一个冲突的驱动程序会导致一个BSOD?

错误,因此最好根本就不载入这样的坏文件。

23)然后,就到了一个16位的word单元“Subsystem”(子系统),用来说明映象文件应运行于什么样的NT子系统之上:

IMAGE_SUBSYSTEM_NATIVE

(1)

二进制文件不需要子系统。

用于驱动程序。

IMAGE_SUBSYSTEM_WINDOWS_GUI

(2)

映象文件是一个Win32二进制图象文件。

(它还是能用AllocConsole()打开一个控制台界面,但在开始时却不能自动地打开。

IMAGE_SUBSYSTEM_WINDOWS_CUI(3)

二进制文件是一个Win32控制台界面二进制文件。

(它将在开始时按照缺省值打开一个控制台,或者继承其父程序的控制台。

IMAGE_SUBSYSTEM_OS2_CUI(5)

二进制文件是一个OS/2控制台界面二进制文件。

(OS/2控制台界面二进制文件是OS/2格式,因此此值在PE文件中很少使用。

IMAGE_SUBSYSTEM_POSIX_CUI(7)

二进制文件使用POSIX?

控制台子系统。

Windows95的二进制文件总是使用Win32子系统,因此它的二进制文件的合法值

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > IT计算机 > 电脑基础知识

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2