pe文件(2023最新pe文件百科介绍)

由网友(风停意未止)分享简介:pe文献,齐称Portable Executable,意为可移植的履行体,是微硬Windows操做体系上的步伐文献(多是直接被履行,如DLL)。它包孕多见的EXE、DLL、OCX、SYS、COM等,使用规模宽泛。中文名pe文献意 为可移植的履行体是齐 称Portable Executable操做体系Windows简介...

pe文件,全称Portable Executable,意为可移植的执行体,是微软Windows操作系统上的程序文件(可能是间接被执行,如DLL)。

StudyPE文件查看工具 PE文件信息查看器 V1.0.8 正式官方版软件下载

它包括常见的EXE、DLL、OCX、SYS、COM等,应用范围广泛。

中文名

pe文件

意 为

可移植的执行体是

全 称

Portable Executable

操作系统

Windows

简介

一个 操作系统的 可执行文件格式在很多方面是这个系统的一面镜子。虽然学习一个 可执行文件格式通常不是一个 程序员的首要任务,但是你可以从这其中学到大量的知识。在这篇文章中,我会给出 Microsoft 的所有基于win32系统(如winnt,win9x)的可移植可执行(PE) 文件格式的详细介绍。在可预知的未来,包括 Windows2000, PE 文件格式在 MicroSoft 的 操作系统中扮演一个重要的角色。如果你在使用 Win32 或 Winnt ,那么你已经在使用 PE 文件了。甚至你只是在 Windows3.1 下使用 Visual C++ 编程,你使用的仍然是 PE 文件(Visual C++ 的 32 位 MS-DOS扩展组件用这个格式)。简而言之,PE 格式已经普遍应用,并且在不短的将来仍是不可避免的。现在是时候找出这种新的 可执行文件格式为 操作系统带来的东西了。

线程局部变量

我最后不会让你盯住无穷无尽的 十六进制Dump,也不会详细讨论页面的每一个单独的位的重要性。代替的,我会向你介绍包含在 PE 文件中的概念,并且将他们和你每天都遇到的东西联系起来。比如,线程 局部变量的概念,如下所述:

declspec(thread) int i;

我快要发疯了,直到我发现它在 可执行文件中实现起来是如此的简单并且优雅。既然你们中的许多人都有使用 16 Windows 的背景,我将把 Win32 PE 文件的构造追溯到和它等价的16 位 NE 文件。

除了一个不同的可执行文件格式, MicroSoft 还引入了一个用它的 编译器和 汇编器生成的新的目标模块格式。这个新的 OBJ 文件格式有许多和PE 文件共同的东东。我做了许多 无用功去查找这个新的OBJ 文件格式的文档。所以我以自己的理解对它进行解析,并且,在这里,除了 PE 文件,我会描述它的一部分。

微软系统工具

大家都知道, Windows NT继承了 VAX? VMS? 和 UNIX? 的传统。许多 Windows NT 的创始人在进入 微软前都在这些平台上进行设计和编码。当他们开始设计 Windows NT 时,很自然的,为了 最小化项目启动时间,他们会使用以前写好的并且已经测试过的工具。用这些工具生成的并且工作的可执行和 OBJ 文件格式叫做 COFF (Common Object File Format 的首字母缩写)。COFF 的相对年龄可以用八进制的域来指定。COFF 本身是一个好的起点,但是需要扩展到一个 现代操作系统如 Windows 95 和 Windows NT 的需要。这个更新的结果就是(PE格式)可移植 可执行文件格式。它被称为"可移植的"是因为在所有平台(如x86,Alpha,MIPS等等)上实现的WindowsNT 都使用相同的 可执行文件格式。当然了,也有许多不同的东西如 二进制代码的CPU指令。重要的是 操作系统的装入器和程序设计工具不需要为任何一种CPU完全重写就能达到目的。

MicroSoft 抛弃现存的32位工具和 可执行文件格式的事实证实了他们想让 WindowsNT 升级并且运行的更快的决心。为16位Windows编写的虚拟设备 驱动程序用一种不同的32位文件布局--LE 文件格式--WindowsNT出现很早以前就存在了。比这更重要的是对 OBJ 文件的替换!在 WindowsNT 的 C 编译器以前,所有的 微软编译器都用 Intel 的 OMF ( Object Module Format ) 规范。就像前面提到的,MicroSoft 的 Win32 编译器生成 COFF 格式的 OBJ 文件。一些 微软的竞争者,如 Borland 和 Symentec ,选择放弃了 COFF 格式并坚持 Intel 的 OMF 文件格式。这样的结果是制作 OBJ 和 LIB 的公司为了使用多个不同的 编译器,不得不为每个不同的编译器分发这些库的不同版本(如果他们不这么做)。

PE 文件格式在 winnt.h 头文件中文档化了(用最不精确的语言)!大约在 winnt.h 的中间部分标题为"Image Format"的一个块。在把 MS-DOS 的 MZ 文件头和 NE 文件头移入新的PE文件头之前,这个块就开始于一个小栏。WINNT.H提供PE文件用到的生鲜 数据结构的定义,但只有很少有助于理解这些数据结构和标志 变量的注释。不管谁为PE文件格式写出这样的头文件都肯定是一个信徒无疑(突然持续地冒出Michael J. O'Leary的名字来)。描述名字,连同深嵌的结构体和宏。当你配套winnt.h进行编码时,类似下面这样的表达式并不鲜见:

pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG]

.VirtualAddress;

为了有助于逻辑的理解这些winnt.h中的信息,阅读可移植可执行和公共对象 文件格式的规格说明,这些在MSDN既看 光盘中是可用的,一直包括到2001年8月。

现在让我们转换到COFF格式的OBJ文件的主体上来,WINNT.H包括COFF OBJ和LIB的结构化定义和类型定义。不幸的是,我还没有找到上面提到的 可执行文件格式的类似文档。既然PE文件和COFF OBJ文件是如此的相似,我决定是时间把这些文件带到重点上来,并且把它们也文档化。仅仅读过了关于PE文件的组成,你自己也想Dump一些PE文件来看这些概念。如果你用 微软基于32位 WINDOWS的开发工具,DUMPBIN 程序可以将PE文件和COFF OBJ/LIB文件转化为可读的形式。在所有的PEDump器中,DUMPBIN是最容易理解的。它恰好有一些很好的选项来 反汇编它正解析的文件的 代码块,Borland用户可以使用 tdump来浏览PE文件,但tdump不能解析 COFF OBJ/LIB 文件。这不是一个重要的东西因为Borland的 编译器首先就不生成 COFF 格式的OBJ文件。

我写了一个PE和COFF OBJ 文件的Dump程序--PEDUMP,我想提供一些比DUMPBIN更加可理解的输出。虽然它没有反 汇编器以及和LIB库文件一起工作,它在其他方面和DUMPBIN是一样的,并且加入了一些新的特性来使它值得被认同。它的 源代码在任何一个MSJ电子公报版上都可以找到,所有我不打算在这里把他全部列出。作为代替,我展示一些从PEDUMP得到的示例输出来阐明我为它们描述的概念。

译注:--说实话,我从这这份 代码中几乎唯一学到的东西就是"如何处理命令行",其它的都没学到。

程序代码

表 1 PEDUMP.C

file://--------------------/

// PROGRAM: PEDUMP

// FILE: PEDUMP.C

// AUTHOR: Matt Pietrek - 1993

file://--------------------/

#include

#include

#include "objdump.h"

#include "exedump.h"

#include "extrnvar.h"

// Global variables set here, and used in EXEDUMP.C and OBJDUMP.C

BOOL fShowRelocations = FALSE;

BOOL fShowRawSectionData = FALSE;

BOOL fShowSymbolTable = FALSE;

BOOL fShowLineNumbers = FALSE;

char HelpText[] =

"PEDUMP - Win32/COFF .EXE/.OBJ file dumper - 1993 Matt Pietrek"

"Syntax: PEDUMP [switches] filename"

" /A include everything in dump"

" /H include hex dump of sections"

" /L include line number information"

" /R show base relocations"

" /S show symbol table";

// Open up a file, memory map it, and call the appropriate dumping routine

void DumpFile(LPSTR filename)

HANDLE hFile;

HANDLE hFileMapping;

LPVOID lpFileBase;

PIMAGE_DOS_HEADER dosHeader;

hFile = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL,

OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

if ( hFile = = INVALID_HANDLE_VALUE )

{ printf("Couldn't open file with CreateFile()");

return; }

hFileMapping = CreateFileMapping(hFile, NULL,

PAGE_READONLY, 0, 0, NULL);

if ( hFileMapping = = 0 )

{

CloseHandle(hFile);

printf("Couldn't open file mapping with CreateFileMapping()");

return;

lpFileBase = MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0);

if ( lpFileBase = = 0 )

CloseHandle(hFileMapping);

CloseHandle(hFile);

printf("Couldn't map view of file with MapViewOfFile()");

return;

printf("Dump of file %s", filename);

dosHeader = (PIMAGE_DOS_HEADER)lpFileBase;

if ( dosHeader->e_magic = = IMAGE_DOS_SIGNATURE )

{ DumpExeFile( dosHeader ); }

else if ( (dosHeader->e_magic = = 0x014C) // Does it look like a i386

&& (dosHeader->e_sp = = 0) ) // COFF OBJ file???

// The two tests above aren't what they look like. They're

// really checking for IMAGE_FILE_HEADER.Machine = = i386 (0x14C)

// and IMAGE_FILE_HEADER.SizeOfOptionalHeader = = 0;

DumpObjFile( (PIMAGE_FILE_HEADER)lpFileBase );

else

printf("unrecognized file format");

UnmapViewOfFile(lpFileBase);

CloseHandle(hFileMapping);

CloseHandle(hFile);

// process all the command line arguments and return a pointer to

// the filename argument.

PSTR ProcessCommandLine(int argc, char *argv[])

int i;

for ( i=1; i < argc; i++ )

strupr(argv

// Is it a switch character?

if ( (argv = = '-') || (argv = = '/') )

if ( argv = = 'A' )

{ fShowRelocations = TRUE;

fShowRawSectionData = TRUE;

fShowSymbolTable = TRUE;

fShowLineNumbers = TRUE; }

else if ( argv = = 'H' )

fShowRawSectionData = TRUE;

else if ( argv = = 'L' )

fShowLineNumbers = TRUE;

else if ( argv = = 'R' )

fShowRelocations = TRUE;

else if ( argv = = 'S' )

fShowSymbolTable = TRUE;

else // Not a switch character. Must be the filename

{ return argv; }

int main(int argc, char *argv[])

PSTR filename;

if ( argc = = 1 )

{ printf( HelpText );

return 1; }

filename = ProcessCommandLine(argc, argv);

if ( filename )

DumpFile( filename );

return 0;

}

基本概念

让我们复习一下几个透过PE文件的设计了解到的基本概念。我用术语"MODULE"来表示一个 可执行文件或一个DLL载入 内存的 代码(CODE)、数据(DATA)、资源(RESOURCES),除了代码和数据是你的程序直接使用的,一个模块还可以由WINDOWS用来确定数据和代码载入的位置的支撑 数据结构组成。在16位WINDOWS中,这些支撑 数据结构在模块数据库(用一个HMODULE来指示的段)中。在WIN32里面,这些 数据结构在PE 文件头中,这些我将会简要地解释一下。

PE文件的两因素

关于PE文件最重要的是, 磁盘上的 可执行文件和它被WINDOWS调入 内存之后是非常相像的。WINDOWS载入器不必为从 磁盘上载入一个文件而辛辛苦苦创建一个进程。载入器使用 内存映射文件机制来把文件中相似的块映射到虚拟空间中。用一个构造式的分析模型,一个PE文件类似一个预制的屋子。它本质上开始于这样一个空间,这个空间后面有几个把它连到其余空间的机件(就是说,把它联系到它的DLL上,等等)。这对PE格式的DLL是一样容易应用的。一旦这个模块被载入,Windows 就可以有效的把它和其它 内存映射文件同等对待。

和16位Windows不同的是。16位NE文件的载入器读取文件的一部分并且创建完全不同的 数据结构在内存中表示模块。当 数据段或者 代码段需要载入时,载入器必须从全局堆中新申请一个段,从 可执行文件中找出生鲜数据,转到这个位置,读入这些生鲜数据,并且要进行适当的修正。除此而外,每个16位模块都有责任记住当前它使用的所有段选择器,而不管这个段是否被丢弃了,如此等等。

对Win32来讲,模块所使用的所有 代码,数据,资源,导入表,和其它需要的模块 数据结构都在一个连续的 内存块中。在这种形势下,你只需要知道载入器把 可执行文件 映射到了什么地方。通过作为映像的一部分的 指针,你可以很容易的找到这个模块所有不同的块。

相对虚拟地址

另一个你需要知道的概念是相对 虚拟地址(RVA)。PE文件中的许多域都用术语RVA来指定。一个RVA只是一些项目相对于文件 映射到 内存的偏移。比如说,载入器把一个文件 映射到 虚拟地址0x10000开始的 内存块。如果一个映像中的实际的表的首址是0x10464,那么它的RVA就是0x464。

( 虚拟地址 0x10464)-(基地址 0x10000)=RVA 0x00464

为了把一个RVA转化成一个有用的 指针,只需要把RVA值加到模块的基地址上即可。基地址是 内存 映射EXE和DLL文件的首址,在Win32中这是一个很重要的概念。为了方便起见,WindowsNT 和 Windows9x用模块的基地址作为这个模块的实例句柄(HINSTANCE)。在Win32中,把模块的基地址叫做HINSTANCE可能导致混淆,因为术语"实例句柄"来自16位Windows。一个程序在16位Windows中的每个拷贝得到它自己分开的 数据段(和一个联系起来的全局句柄)来把它和这个程序其它的拷贝分别开来,就形成了术语"实例句柄"。在Win32中,每个程序不必和其它程序区别开来,因为他们不共享相同的 地址空间。术语INSTANCE仍然保持16位 windows和32位Windows之间的连续性。在Win32中重要的是你可以对任何DLL调用GetModuleHandle()得到一个 指针去访问它的组件(译注)。

译注

如果 dllname 为 NULL,则得到执行体自己的模块句柄。这是非常有用的,如通常 编译器产生的启动 代码将取得这个句柄并将它作为一个参数hInstance传给WinMain !

你最终需要理解的PE文件的概念是"块(Section)"。PE文件中的一个块和NE文件中的一个段或者资源等价。块可以包含 代码或者数据。和段不同的是,块是 内存中连续的空间,而没有尺寸限制。当你的连接器和库为你建立,并且包含对 操作系统非常重要的信息的其它的 数据块时,这些块包含你的程序直接声明和使用的 代码或数据。在一些PE格式的描述中,块也叫做对象。术语对象有如此多的涵义,以至于只能把 代码和数据叫做"块"。

PE首部

和其它 可执行文件格式一样,PE文件在众所周知的地方有一些 定义文件其余部分面貌的域。首部就包含这样像 代码和数据的位置和尺寸的地方, 操作系统要对它进行干预,比如初始 堆栈大小,和其它重要的块的信息,我将要简短的介绍一下。和 微软其它可执行格式相比,主要的首部不是在文件的最开始。典型的PE文件最开始的数百个字节被DOS残留部分占用。这个残留部分是一个可以打印如"这个程序不能在DOS下运行!"这类信息的小程序。所以,你在一个不支持Win32的系统中运行这个程序,便可以得到这类 错误信息。当载入器把一个Win32程序 映射到 内存,这个映射文件的第一个字节对应于DOS残留部分的第一个字节。那是无疑的。和你启动的任一个基于Win32 的程序一起,都有一个基于DOS的程序连带被载入。

和 微软的其它可执行格式一样,你可以通过查找它的起始偏移来得到真实首部,这个偏移放在DOS残留首部中。WINNT.H头文件包含了DOS残留程序的 数据结构定义,使得很容易找到PE首部的起始位置。e_lfanew 域是PE真实首部的偏移。为了得到PE首部在 内存中的 指针,只需要把这个值加到映像的 基址上即可。

file://忽/略类型转化和指针转化 ...

pNTHeader = dosHeader + dosHeader->e_lfanew;

一旦你有了PE主首部的 指针,游戏就可以开始了!PE主首部是一个IMAGE_NT_HEADERS的结构,在WINNT.H中定义。这个结构由一个双字(DWORD)和两个子结构组成,布局如下:

DWORD Signature;

IMAGE_FILE_HEADER FileHeader;

IMAGE_OPTIONAL_HEADER OptionalHeader;

标志域用ASCII表示就是"PE"。如果在DOS首部中用了e_lfanew域,你得到一个NE标志而不是PE,那么这是16位NE文件。同样的,在标志域中的LE表示这是一个Windows3.x 的 虚拟设备 驱动程序(VxD)。LX表示这个文件是OS/2 2.0文件。

PE DWORD标志后的是结构 IMAGE_FILE_HEADER 。这个域只包含这个文件最基本的信息。这个结构表现为并未从它的原始COFF实现更改过。除了是PE首部的一部分,它还表现在 微软Win32 编译器生成的COFF OBJ 文件的最开始部分。IMAGE_FILE_HEADER的这个域显示在下面:

表2 IMAGE_FILE_HEADER Fields

WORD Machine

表示CPU的类型,下面定义了一些CPU的ID

0x14d Intel i860

0x14c Intel I386 (same ID used for 486 and 586)

0x162 MIPS R3000

0x166 MIPS R4000

0x183 DEC Alpha AXP

WORD NumberOfSections

这个文件中的块数目。

DWORD TimeDateStamp

连接器产生这个文件的日期(对OBJ文件是 编译器),这个域保存的数是从 1969年12月下午4:00开始到现在经过的秒数。

DWORD PointerToSymbolTable

COFF符号表的文件 偏移量。这个域只用于有COFF调试信息的OBJ文件和PE文件,PE文件支持多种调试信息格式,所以调试器应该指向数据目录的IMAGE_DIRECTORY_ENTRY_DEBUG条目。

DWORD NumberOfSymbols

COFF符号表的符号数目。见上面。

WORD SizeOfOptionalHeader

这个结构后面的可选首部的尺寸。在OBJ文件中,这个域是0。在 可执行文件中,这是跟在这个结构后的IMAGE_OPTIONAL_HEADER结构的尺寸。

WORD Characteristics

关于这个文件信息的标志。一些重要的域如下:

0x0001 这个文件中没有重定位信息

0x0002 可执行文件映像(不是OBJ或LIB文件)

0x2000 文件是 动态连接库,而非程序

其它域定义在WINNT.H中。

PE首部的第三个组成部分是一个IMAGE_OPTIONAL_HEADER型的结构。对PE文件,这一部分当然不是"可选的"。COFF格式允许单独实现来定义一个超出标准IMAGE_FILE_HEADER附加信息的结构。IMAGE_OPTIONAL_HEADER里面的域是PE的实现者感到超出IMAGE_FILE_HEADER基本信息以外非常关键的信息。

并非 IMAGE_OPTIONAL_HEADER 的所有域都是重要的(见图4)。比较重要,需要知道的是ImageBase 和 SubSystem 域。你可以忽略其它域的描述。

表3 IMAGE_FILE_HEADER 的域:

WORD Magic

表现为一些类别的标志字,通常是0X010B 。

BYTE MajorLinkerVersion

BYTE MinorLinkerVersion

生成这个文件的连接器的版本。这个数字以十进制显示比用 十六进制好。一个典型的连接器版本是2.23。

DWORD SizeOfCode

所有 代码块的进位尺寸。通常大多数文件只有一个 代码块,所以这个域和 .TEXT 块匹配。

DWORD SizeOfInitializedData

已初始化的数据组成的块的大小(不包括 代码段)。然而,和它在文件中的表现形式并不一致。

DWORD SizeOfUninitializedData

载入器在 虚拟内存中申请空间,但在 磁盘上的文件中并不 占用空间的块的尺寸。这些块在程序启动时不需要指定初值,因此术语名就是"未初始化的数据"。未初始化的数据通常在一个名叫 .bss 的块中。

DWORD AddressOfEntryPoint

载入器开始执行这个程序的地址,即这个PE文件的入口地址。这是一个RVA,通常在 .text 块中。

DWORD BaseOfCode

代码块起始地址的RVA 。在 内存中, 代码块通常在PE首部之后, 数据块之前。在 微软的连接器产生的EXE文件中,这个值通常是0x1000 。Borland 的连接器 TLINK32 也一样,把映像第一个 代码块的RVA和映像 基址相加,填入这个域。

译注:这个域好像一直没有什么用

DWORD BaseOfData

数据块起始地址的RVA 。在 内存中, 数据块经常在最后,在PE首部和 代码块之后。

译注:这个域好像也一直没有什么用

DWORD ImageBase

连接器创建一个 可执行文件时,它假定这个文件被 映射到 内存中的一个指定的地方,这个地址就存在这个域中,假定一个载入地址可以使连接器优化以便节省空间。如果载入器真的把这个文件 映射到了这个地方,在运行之前 代码不需要任何改变。在为WindowsNT 创建的 可执行文件中, 默认的ImageBase 是0x10000。对DLL, 默认是0x40000。在Window95中,地址0x10000不能用来载入32位EXE文件,因为这个区域在一个被所有进程共享的线性地址空间中。因此, 微软把Win32 可执行文件的 默认 基址改为0x40000,假定基址为0x10000 的老程序坐在Windows95 中需要更长的载入时间,这是因为载入器需要 重定位基址。

译注:这个域即"Prefered Load Address",如果没有什么意外,这就是该PE文件载入 内存后的地址。

DWORD SectionAlignment

映射到 内存中时,每个块都必须保证开始于这个值的整数倍。为了 分页的目的, 默认的SectionAlignment 是 0x1000。

DWORD FileAlignment

在PE文件中,组成每个块的生鲜数据必须保证开始于这个值的整数倍。 默认值是0x200 字节,也许是为了保证块都开始于一个 磁盘 扇区(一个扇区通常是 512 字节)。这个域和NE文件中的段/资源对齐(segment/resource alignment)尺寸是等价的。和NE文件不同的是,PE文件通常没有数百个的块,所以,为了对齐而浪费的通常空间很少。

WORD MajorOperatingSystemVersion

WORD MinorOperatingSystemVersion

这个程序运行需要的 操作系统的最小版本号。这个域有点含糊,因为Subsystem 域(后面将会说到)可以提供类似的功能。这个域在到目前为止的Win32中 默认是1.0。

WORD MajorImageVersion

WORD MinorImageVersion

一个可由用户定义的域。这允许你有不同的EXE和DLL版本。你可以通过 链接器的 /version 选项设置这个域的值。例如:"link /version:2.0 myobj.obj"。

WORD MajorSubsystemVersion

WORD MinorSubsystemVersion

这个程序运行需要的最小子系统版本号。这个域的一个典型值是3.10 (表示WindowsNT 3.1)。

DWORD Reserved1

通常是 0 。

DWORD SizeOfImage

载入器必须关心的这个映像所有部分的大小总和。是从映像的开始到最后一个块结尾这段区域的大小。最后一个块结尾按SectionAlignment进位。

译注:这个很重要,可以大,但不可以小!

DWORD SizeOfHeaders

PE首部和块表的大小。块的实际数据紧跟在所有首部组件之后。

DWORD CheckSum

这个文件的CRC 校验和。在微软可执行格式中,这个域被忽略并且置为0 。这个规则的一个例外情况是信任服务,这类EXE文件必须有一个合法的 校验和。

WORD Subsystem

可执行文件的用户界面使用的子系统类型。WINNT.H 定义了下面这些值:

NATIVE 1 不需要子系统(比如 设备驱动)

WINDOWS_GUI 2 在Windows 图形用户界面子系统下运行

WINDOWS_CUI 3 在Windows字符子系统下运行( 控制台程序)

OS2_CUI 5 在OS/2字符子系统下运行(仅对OS/2 1.x)

POSIX_CUI 7 在 Posix 字符子系统下运行

WORD DllCharacteristics

指定在何种环境下一个DLL的初始化函数(比如DllMain)将被调用的标志 变量。这个值经常被置为0 。但是 操作系统在下面四种情况下仍然调用DLL的初始化函数。

下面的值定义为:

1 DLL第一次载入到进程中的地址空间中时调用

2 一个线程结束时调用

4 一个线程开始时调用

8 退出 DLL时调用

DWORD SizeOfStackReserve

为初始线程保留的 虚拟内存总数。然而并不是所有这些 内存都被提交(见下一个域)。这个域的 默认值是0x100000(1Mbytes)。如果你在CreateThread 中把堆栈尺寸指定为 0 ,结果将是用这个相同的值(0x10000)。

DWORD SizeOfStackCommit

开始提交的初始线程 堆栈总数。对 微软的连接器,这个域 默认是0x1000字节(一页),TLINK32 是两页。

DWORD SizeOfHeapReserve

为初始进程的堆保留的 虚拟内存总数。这个堆的句柄可以用GetPocessHeap 得到。并不是所有这些 内存都被提交(见下一个域)。

DWORD SizeOfHeapCommit

开始为进程堆提交的 内存总数。 默认是一页。

文件格式

PE文件的意思是Portable Executable(可移植,可执行),它是win32 可执行文件的标准格式.它的一些特性继承unix的COFF文件格式,同时保留了与旧版MS-DOS和WINDOWS的兼容.其可移植可执行意味着是跨win32平台的.

文件的层次结构

PE文件最前面紧随DOS MZ 文件头的是一个DOS 可执行文件(Stub).这使得PE文件成为一个合法的MS-DOS可执行文件.DOS MZ文件头后面是一个32位的PE文件标志0x50450000(IMAGE_NT_SIGNATURE),即PE00.接下来的是PE的 映像文件头,包含的信息有该程序的运行平台,有多少个节,文件链接的时间,文件的命名格式.后面还紧跟一个可选映像头,包含PE文件的逻辑分布信息,程序加载信息,开始地址,保留的 堆栈数量, 数据段大小等.可选头还有一个重要的域,称为:数据目录表"的数组,表的每一项都是指向某一节的 指针.可选映像头后面紧跟的是节表和节.节通过节表来实现索引.实际上,节的内容才是真正执行的数据和程序.每一个节都有相关的标志.每一个节会被一个或多个目录表指向,目录表可通过可选头的"数据目录表"的入口找到.就像输出函数表或 基址 重定位表.也存在没有目录表指向的节.

文件层次解释

A.DOS STUB和DOS头

DOS插桩程序在大多数情况下由 汇编器/ 编译器自动产生.通常它调用INT 21H服务9来显示上述字符串.可以通过IMAGE_DOS_HEADER结构来识别一个合法的DOS头.这个结构的头两个字节肯定是"MZ".可通过该结构的e_lfanew成员来找到PE文件的开始标志.MS-DOS头部占据了PE文件的头64个字节.在 微软的WINNT.H中可以找到其内容结构的描述.

文件头

在DOS STUB后是PE文件头(PE header).PE文件头是PE相关结构IMGAE_NT_HEADERS的简称,即NT映像头,存放PE整个文件信息发布的重要字段,包含了PE装载器用到的重要域.执行体在 操作系统中执行时,PE装载器将从DOS MZ头中找到PE头文件的起始偏移量e_lfanew,从而跳过DOS STUB直接定位真正的PE文件.它由3部分组成:

(1)PE文件标志(4H字节)

PE文件标志0x50450000即PE00,标志着NT映像头的开始,也是PE文件中与 windows有关内容的开始.

(2) 映像文件(14H字节)

是NT 映像文件的主要部分,包含PE文件的基本信息

(3)可选映像头

包含PE文件的逻辑分布信息.

C.节表

节表其实是紧跟NT 映像文件的一个结构 数组.其成员数目由映像文件头结构NumberOFSectios域的值来决定.

D.节

PE文件的真正内容划分为块,称之为节.节的划分基于各组数据的共同属性.惟有节的属性设置决定了节的特性和功能.典型的windows NT 应用程序可以具有9个节:.texr,.bss.rdata,.data,.rsrc,edata,idata,pdata,.debug

判断一个文件是否为PE文件

var //检测指定文件是否有效PE文件

PEDosHead: TImageDosHeader;

PENTHead: TImageNtHeaders;

m_file: integer;

begin

Result := False;

m_file := FileOpen(filename, fmOpenRead or fmShareDenyNone); //只读和其它任意

if m_File > 0 then

try

FileSeek(m_file, 0, soFromBeginning); //将 指针挪至 文件头

FileRead(m_file, PEDosHead, SizeOf(PEDosHead)); //读PEDosHead结构

FileSeek(m_file, PEDosHead._lfanew, soFromBeginning); //将 指针挪至_lfanew

FileRead(m_file, PENTHead, SizeOf(PENTHead)); //读PENTHead结构

finally

FileClose(m_file);

end;

if (PENTHead.Signature = IMAGE_NT_SIGNATURE) then //检验文件头部第一个字的值是否等于 IMAGE_DOS_SIGNATURE

Result := True;

end;

pe文件结构图

阅读全文

相关推荐

最新文章