图1.PE文件映像结构
从MS-DOS文件头结构开始,我将按照PE文件格式各成分的出现顺序依次对其进行讨论,并且讨论的大部分是以示例代码为基础来示范如何获得文件的信息的。所有的源码均摘自PEFILE.DLL模块的PEFILE.C文件。这些示例都利用了Windows NT最酷的特色之一——内存映射文件,这一特色允许用户使用一个简单的指针来存取文件中所包含的数据,因此所有的示例都使用了内存映射文件来存取PE文件中的数据。
注意:请查阅本文末尾关于如何使用PEFILE.DLL的那一段。
返回目录
3.1 MS-DOS头部/实模式头部
如上所述,PE文件格式的第一个组成部分是MS-DOS头部。在PE文件格式中,它并非一个新概念,因为它与MS-DOS 2.0以来就已有的MS-DOS头部是完全一样的。保留这个相同结构的最主要原因是,当你尝试在Windows 3.1以下或MS-DOS 2.0以上的系统下装载一个文件的时候, *** 作系氏猜颂统能够读取这个文件并明白它是和当前系统不相兼容的。换句话说,当你在MS-DOS 6.0下运行一个Windows NT可执行文件时,你会得到这样一条消息:“This program cannot be run in DOS mode.”如果MS-DOS头部不是作为PE文件格式的第一部分的话, *** 作系统装载文件的时候就会失败,并提供一些完全没用的信息,例如:“The name specified is not recognized as an internal or external command, operable program or batch file.”
MS-DOS头部占据了PE文件的头64个字节,描述它内容的结构如下:
//WINNT.H
typedef struct _IMAGE_DOS_HEADER { // DOS的.EXE头部
USHORT e_magic// 魔术数字
USHORT e_cblp// 文件最后页的字节数
USHORT e_cp// 文件页数
USHORT e_crlc// 重定义元素个数
USHORT e_cparhdr// 头部尺寸,以段落为单位
USHORT e_minalloc// 所需的最小附加段
USHORT e_maxalloc// 所需的最大附加段
USHORT e_ss// 初始的SS值(相对偏移量)
USHORT e_sp// 初始的SP值
USHORT e_csum// 校验和
USHORT e_ip// 初始的IP值
USHORT e_cs// 初始的CS值(相对偏移量)
USHORT e_lfarlc// 重分配表文件地址
USHORT e_ovno// 覆盖号
USHORT e_res[4]// 保留字
USHORT e_oemid// OEM标识符(相对e_oeminfo)
USHORT e_oeminfo// OEM信息
USHORT e_res2[10]// 保留字
LONG e_lfanew// 新exe头部的文件地址
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER
第一个域e_magic,被称为魔术数字,它被用于表示一个MS-DOS兼容的文件类型。所有MS-DOS兼容的可执兆念行文件都将这个值设为0x5A4D,表示ASCII字符MZ。MS-DOS头部之所以有的时候被称为MZ头部,就是这个缘故。还有许多其它的域对于MS-DOS *** 作系统来说都有用,但是对于Windows NT来说,这个结构中只有一个有用歼郑的域——最后一个域e_lfnew,一个4字节的文件偏移量,PE文件头部就是由它定位的。对于Windows NT的PE文件来说,PE文件头部是紧跟在MS-DOS头部和实模式程序残余之后的。
返回目录
3.2 实模式残余程序
实模式残余程序是一个在装载时能够被MS-DOS运行的实际程序。对于一个MS-DOS的可执行映像文件,应用程序就是从这里执行的。对于Windows、OS/2、Windows NT这些 *** 作系统来说,MS-DOS残余程序就代替了主程序的位置被放在这里。这种残余程序通常什么也不做,而只是输出一行文本,例如:“This program requires Microsoft Windows v3.1 or greater.”当然,用户可以在此放入任何的残余程序,这就意味着你可能经常看到像这样的东西:“You can''t run a Windows NT application on OS/2, it''s simply not possible.”
当为Windows 3.1构建一个应用程序的时候,链接器将向你的可执行文件中链接一个名为WINSTUB.EXE的默认残余程序。你可以用一个基于MS-DOS的有效程序取代WINSTUB,并且用STUB模块定义语句指示链接器,这样就能够取代链接器的默认行为。为Windows NT开发的应用程序可以通过使用-STUB:链接器选项来实现。
返回目录
3.3 PE文件头部与标志
PE文件头部是由MS-DOS头部的e_lfanew域定位的,这个域只是给出了文件的偏移量,所以要确定PE头部的实际内存映射地址,就需要添加文件的内存映射基地址。例如,以下的宏是包含在PEFILE.H源文件之中的:
//PEFILE.H
#define NTSIGNATURE(a) ((LPVOID)((BYTE *)a + \
((PIMAGE_DOS_HEADER)a)->e_lfanew))在处理PE文件信息的时候,我发现文件之中有些位置需要经常查阅。既然这些位置仅仅是对文件的偏移量,那么用宏来实现这些定位就比较容易,因为它们较之函数有更好的表现。
请注意这个宏所获得的是PE文件标志,而并非PE文件头部的偏移量。那是由于自Windows与OS/2的可执行文件开始,.EXE文件都被赋予了目标 *** 作系统的标志。对于Windows NT的PE文件格式而言,这一标志在PE文件头部结构之前。在Windows和OS/2的某些版本中,这一标志是文件头的第一个字。同样,对于PE文件格式,Windows NT使用了一个DWORD值。
以上的宏返回了文件标志的偏移量,而不管它是哪种类型的可执行文件。所以,文件头部是在DWORD标志之后,还是在WORD标志处,是由这个标志是否Windows NT文件标志所决定的。要解决这个问题,我编写了ImageFileType函数(如下),它返回了映像文件的类型:
//PEFILE.C
DWORD WINAPI ImageFileType (LPVOID lpFile)
{
/* 首先出现的是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))
else if (*(DWORD *)NTSIGNATURE (lpFile) ==
IMAGE_NT_SIGNATURE)
return IMAGE_NT_SIGNATURE
else
return IMAGE_DOS_SIGNATURE
}
else
/* 不明文件种类 */
return 0
}
以上列出的代码立即告诉了你NTSIGNATURE宏有多么有用。对于比较不同文件类型并且返回一个适当的文件种类来说,这个宏就会使这两件事变得非常简单。WINNT.H之中定义的四种不同文件类型有:
//WINNT.H
#define IMAGE_DOS_SIGNATURE 0x5A4D // MZ
#define IMAGE_OS2_SIGNATURE 0x454E // NE
#define IMAGE_OS2_SIGNATURE_LE 0x454C // LE
#define IMAGE_NT_SIGNATURE 0x00004550 // PE00
首先,Windows的可执行文件类型没有出现在这一列表中,这一点看起来很奇怪。但是,在稍微研究一下之后,就能得到原因了:除了 *** 作系统版本规范的不同之外,Windows的可执行文件和OS/2的可执行文件实在没有什么区别。这两个 *** 作系统拥有相同的可执行文件结构。
现在把我们的注意力转向Windows NT PE文件格式,我们会发现只要我们得到了文件标志的位置,PE文件之后就会有4个字节相跟随。下一个宏标识了PE文件的头部:
//PEFILE.C
#define PEFHDROFFSET(a) ((LPVOID)((BYTE *)a + \
((PIMAGE_DOS_HEADER)a)->e_lfanew + \
SIZE_OF_NT_SIGNATURE))
这个宏与上一个宏的唯一不同是这个宏加入了一个常量SIZE_OF_NT_SIGNATURE。不幸的是,这个常量并未定义在WINNT.H之中,于是我将它定义在了PEFILE.H中,它是一个DWORD的大小。
既然我们知道了PE文件头的位置,那么就可以检查头部的数据了。我们只需要把这个位置赋值给一个结构,如下:
PIMAGE_FILE_HEADER pfh
pfh = (PIMAGE_FILE_HEADER)PEFHDROFFSET(lpFile)
在这个例子中,lpFile表示一个指向可执行文件内存映像基地址的指针,这就显出了内存映射文件的好处:不需要执行文件的I/O,只需使用指针pfh就能存取文件中的信息。PE文件头结构被定义为:
//WINNT.H
typedef struct _IMAGE_FILE_HEADER {
USHORT Machine
USHORT NumberOfSections
ULONG TimeDateStamp
ULONG PointerToSymbolTable
ULONG NumberOfSymbols
USHORT SizeOfOptionalHeader
USHORT Characteristics
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER
#define IMAGE_SIZEOF_FILE_HEADER 20
请注意这个文件头部的大小已经定义在这个包含文件之中了,这样一来,想要得到这个结构的大小就很方便了。但是我觉得对结构本身使用sizeof运算符(译注:原文为“function”)更简单一些,因为这样的话我就不必记住这个常量的名字IMAGE_SIZEOF_FILE_HEADER,而只需要记住结构IMAGE_FILE_HEADER的名字就可以了。另一方面,记住所有结构的名字已经够有挑战性的了,尤其在是这些结构只有WINNT.H中才有的情况下。
PE文件中的信息基本上是一些高级信息,这些信息是被 *** 作系统或者应用程序用来决定如何处理这个文件的。第一个域是用来表示这个可执行文件被构建的目标机器种类,例如DEC(R) Alpha、MIPS R4000、Intel(R) x86或一些其它处理器。系统使用这一信息来在读取这个文件的其它数据之前决定如何处理它。
Characteristics域表示了文件的一些特征。比如对于一个可执行文件而言,分离调试文件是如何 *** 作的。调试器通常使用的方法是将调试信息从PE文件中分离,并保存到一个调试文件(.DBG)中。要这么做的话,调试器需要了解是否要在一个单独的文件中寻找调试信息,以及这个文件是否已经将调试信息分离了。我们可以通过深入可执行文件并寻找调试信息的方法来完成这一工作。要使调试器不在文件中查找的话,就需要用到IMAGE_FILE_DEBUG_STRIPPED这个特征,它表示文件的调试信息是否已经被分离了。这样一来,调试器可以通过快速查看PE文件的头部的方法来决定文件中是否存在着调试信息。
WINNT.H定义了若干其它表示文件头信息的标记,就和以上的例子差不多。我把研究这些标记的事情留给读者作为练习,由你们来看看它们是不是很有趣,这些标记位于WINNT.H中的IMAGE_FILE_HEADER结构之后。
PE文件头结构中另一个有用的入口是NumberOfSections域,它表示如果你要方便地提取文件信息的话,就需要了解多少个段——更明确一点来说,有多少个段头部和多少个段实体。每一个段头部和段实体都在文件中连续地排列着,所以要决定段头部和段实体在哪里结束的话,段的数目是必需的。以下的函数从PE文件头中提取了段的数目:
PEFILE.C
int WINAPI NumOfSections(LPVOID lpFile)
{
/* 文件头部中所表示出的段数目 */
return (int)((PIMAGE_FILE_HEADER)
PEFHDROFFSET (lpFile))->NumberOfSections)
}
如你所见,PEFHDROFFSET以及其它宏用起来非常方便。
返回目录
3.4 PE可选头部
PE可执行文件中接下来的224个字节组成了PE可选头部。虽然它的名字是“可选头部”,但是请确信:这个头部并非“可选”,而是“必需”的。OPTHDROFFSET宏可以获得指向可选头部的指针:
//PEFILE.H
#define OPTHDROFFSET(a) ((LPVOID)((BYTE *)a + \
((PIMAGE_DOS_HEADER)a)->e_lfanew + \
SIZE_OF_NT_SIGNATURE + \
sizeof(IMAGE_FILE_HEADER)))
可选头部包含了很多关于可执行映像的重要信息,例如初始的堆栈大小、程序入口点的位置、首选基地址、 *** 作系统版本、段对齐的信息等等。IMAGE_OPTIONAL_HEADER结构如下:
//WINNT.H
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// 标准域
//
USHORT Magic
UCHAR MajorLinkerVersion
UCHAR MinorLinkerVersion
ULONG SizeOfCode
ULONG SizeOfInitializedData
ULONG SizeOfUninitializedData
ULONG AddressOfEntryPoint
ULONG BaseOfCode
ULONG BaseOfData
//
// NT附加域
//
ULONG ImageBase
ULONG SectionAlignment
ULONG FileAlignment
USHORT MajorOperatingSystemVersion
USHORT MinorOperatingSystemVersion
USHORT MajorImageVersion
USHORT MinorImageVersion
USHORT MajorSubsystemVersion
USHORT MinorSubsystemVersion
ULONG Reserved1
ULONG SizeOfImage
ULONG SizeOfHeaders
ULONG CheckSum
USHORT Subsystem
USHORT DllCharacteristics
ULONG SizeOfStackReserve
ULONG SizeOfStackCommit
ULONG SizeOfHeapReserve
ULONG SizeOfHeapCommit
ULONG LoaderFlags
ULONG NumberOfRvaAndSizes
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER
如你所见,这个结构中所列出的域实在是冗长得过分。为了不让你对所有这些域感到厌烦,我会仅仅讨论有用的——就是说,对于探究PE文件格式而言有用的。
标准域
首先,请注意这个结构被划分为“标准域”和“NT附加域”。所谓标准域,就是和UNIX可执行文件的COFF格式所公共的部分。虽然标准域保留了COFF中定义的名字,但是Windows NT仍然将它们用作了不同的目的——尽管换个名字更好一些。
Magic。我不知道这个域是干什么的,对于示例程序EXEVIEW.EXE示例程序而言,这个值是0x010B或267(译注:0x010B为.EXE,0x0107为ROM映像,这个信息我是从eXeScope上得来的)。
MajorLinkerVersion、MinorLinkerVersion。表示链接此映像的链接器版本。随Window NT build 438配套的Windows NT SDK包含的链接器版本是2.39(十六进制为2.27)。
SizeOfCode。可执行代码尺寸
SizeOfInitializedData。已初始化的数据尺寸.
SizeOfUninitializedData。未初始化的数据尺寸。
AddressOfEntryPoint。在标准域中,AddressOfEntryPoint域是对PE文件格式来说最为有趣的了。这个域表示应用程序入口点的位置。并且,对于系统黑客来说,这个位置就是导入地址表(IAT)的末尾。以下的函数示范了如何从可选头部获得Windows NT可执行映像的入口点。
//PEFILE.C
LPVOID WINAPI GetModuleEntryPoint(LPVOID lpFile)
{
PIMAGE_OPTIONAL_HEADER poh
poh = (PIMAGE_OPTIONAL_HEADER)OPTHDROFFSET(lpFile)
if (poh != NULL)
return (LPVOID)poh->AddressOfEntryPoint
else
return NULL
}
BaseOfCode。已载入映像的代码(“.text”段)的相对偏移量。
BaseOfData。已载入映像的未初始化数据(“.bss”段)的相对偏移量
Windows NT附加域
添加到Windows NT PE文件格式中的附加域为Windows NT特定的进程行为提供了装载器的支持,以下为这些域的概述。
ImageBase。进程映像地址空间中的首选基地址。Windows NT的Microsoft Win32 SDK链接器将这个值默认设为0x00400000,但是你可以使用-BASE:linker开关改变这个值。
SectionAlignment。从ImageBase开始,每个段都被相继的装入进程的地址空间中。SectionAlignment则规定了装载时段能够占据的最小空间数量——就是说,段是关于SectionAlignment对齐的。
Windows NT虚拟内存管理器规定,段对齐不能少于页尺寸(当前的x86平台是4096字节),并且必须是成倍的页尺寸。4096字节是x86链接器的默认值,但是它可以通过-ALIGN: linker开关来设置。
FileAlignment。映像文件首先装载的最小的信息块间隔。例如,链接器将一个段实体(段的原始数据)加零扩展为文件中最接近的FileAlignment边界。早先提及的2.39版链接器将映像文件以0x200字节的边界对齐,这个值可以被强制改为512到65535这么多。
MajorOperatingSystemVersion。表示Windows NT *** 作系统的主版本号;通常对Windows NT 1.0而言,这个值被设为1。
MinorOperatingSystemVersion。表示Windows NT *** 作系统的次版本号;通常对Windows NT 1.0而言,这个值被设为0。
MajorImageVersion。用来表示应用程序的主版本号;对于Microsoft Excel 4.0而言,这个值是4。
MinorImageVersion。用来表示应用程序的次版本号;对于Microsoft Excel 4.0而言,这个值是0。
MajorSubsystemVersion。表示Windows NT Win32子系统的主版本号;通常对于Windows NT 3.10而言,这个值被设为3。
MinorSubsystemVersion。表示Windows NT Win32子系统的次版本号;通常对于Windows NT 3.10而言,这个值被设为10。
Reserved1。未知目的,通常不被系统使用,并被链接器设为0。
SizeOfImage。表示载入的可执行映像的地址空间中要保留的地址空间大小,这个数字很大程度上受SectionAlignment的影响。例如,考虑一个拥有固定页尺寸4096字节的系统,如果你有一个11个段的可执行文件,它的每个段都少于4096字节,并且关于65536字节边界对齐,那么SizeOfImage域将会被设为11 * 65536 = 720896(176页)。而如果一个相同的文件关于4096字节对齐的话,那么SizeOfImage域的结果将是11 * 4096 = 45056(11页)。这只是个简单的例子,它说明每个段需要少于一个页面的内存。在现实中,链接器通过个别地计算每个段的方法来决定SizeOfImage确切的值。它首先决定每个段需要多少字节,并且最后将页面总数向上取整至最接近的SectionAlignment边界,然后总数就是每个段个别需求之和了。
SizeOfHeaders。这个域表示文件中有多少空间用来保存所有的文件头部,包括MS-DOS头部、PE文件头部、PE可选头部以及PE段头部。文件中所有的段实体就开始于这个位置。
CheckSum。校验和是用来在装载时验证可执行文件的,它是由链接器设置并检验的。由于创建这些校验和的算法是私有信息,所以在此不进行讨论。
Subsystem。用于标识该可执行文件目标子系统的域。每个可能的子系统取值列于WINNT.H的IMAGE_OPTIONAL_HEADER结构之后。
DllCharacteristics。用来表示一个DLL映像是否为进程和线程的初始化及终止包含入口点的标记。
SizeOfStackReserve、SizeOfStackCommit、SizeOfHeapReserve、SizeOfHeapCommit。这些域控制要保留的地址空间数量,并且负责栈和默认堆的申请。在默认情况下,栈和堆都拥有1个页面的申请值以及16个页面的保留值。这些值可以使用链接器开关-STACKSIZE:与-HEAPSIZE:来设置。
LoaderFlags。告知装载器是否在装载时中止和调试,或者默认地正常运行。
NumberOfRvaAndSizes。这个域标识了接下来的DataDirectory数组。请注意它被用来标识这个数组,而不是数组中的各个入口数字,这一点非常重要。
DataDirectory。数据目录表示文件中其它可执行信息重要组成部分的位置。它事实上就是一个IMAGE_DATA_DIRECTORY结构的数组,位于可选头部结构的末尾。当前的PE文件格式定义了16种可能的数据目录,这之中的11种现在在使用中。
数据目录
WINNT.H之中所定义的数据目录为://WINNT.H
// 目录入口
// 导出目录
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0
// 导入目录
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1
// 资源目录
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2
// 异常目录
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3
// 安全目录
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4
// 重定位基本表
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5
// 调试目录
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6
// 描述字串
#define IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7
// 机器值(MIPS GP)
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8
// TLS目录
#define IMAGE_DIRECTORY_ENTRY_TLS 9
// 载入配置目录
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10
基本上,每个数据目录都是一个被定义为IMAGE_DATA_DIRECTORY的结构。虽然数据目录入口本身是相同的,但是每个特定的目录种类却是完全唯一的。每个数据目录的定义在本文的以后部分被描述为“预定义段”。
//WINNT.H
typedef struct _IMAGE_DATA_DIRECTORY {
ULONG VirtualAddress
ULONG Size
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY
每个数据目录入口指定了该目录的尺寸和相对虚拟地址。如果你要定义一个特定的目录的话,就需要从可选头部中的数据目录数组中决定相对的地址,然后使用虚拟地址来决定该目录位于哪个段中。一旦你决定了哪个段包含了该目录,该段的段头部就会被用于查找数据目录的精确文件偏移量位置。
所以要获得一个数据目录的话,那么首先你需要了解段的概念。我在下面会对其进行描述,这个讨论之后还有一个有关如何定位数据目录的示例。
返回目录
4 PE文件段
PE文件规范由目前为止定义的那些头部以及一个名为“段”的一般对象组成。段包含了文件的内容,包括代码、数据、资源以及其它可执行信息,每个段都有一个头部和一个实体(原始数据)。我将在下面描述段头部的有关信息,但是段实体则缺少一个严格的文件结构。因此,它们几乎可以被链接器按任何的方法组织,只要它的头部填充了足够能够解释数据的信息。
返回目录
4.1 段头部
PE文件格式中,所有的段头部位于可选头部之后。每个段头部为40个字节长,并且没有任何的填充信息。段头部被定义为以下的结构:
//WINNT.H
#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER {
UCHAR Name[IMAGE_SIZEOF_SHORT_NAME]
union {
ULONG PhysicalAddress
ULONG VirtualSize
} Misc
ULONG VirtualAddress
ULONG SizeOfRawData
ULONG PointerToRawData
ULONG PointerToRelocations
ULONG PointerToLinenumbers
USHORT NumberOfRelocations
USHORT NumberOfLinenumbers
ULONG Characteristics
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER
你如何才能获得一个特定段的段头部信息?既然段头部是被连续的组织起来的,而且没有一个特定的顺序,那么段头部必须由名称来定位。以下的函数示范了如何从一个给定了段名称的PE映像文件中获得一个段头部:
//PEFILE.C
BOOL WINAPI GetSectionHdrByName(LPVOID lpFile, IMAGE_SECTION_HEADER *sh, char *szSection)
{
PIMAGE_SECTION_HEADER psh
int nSections = NumOfSections (lpFile)
int i
if ((psh = (PIMAGE_SECTION_HEADER)SECHDROFFSET(lpFile))
!= NULL)
{
/* 由名称查找段 */
for (i = 0i <nSectionsi++)
{
if (!strcmp(psh->Name, szSection))
{
/* 向头部复制数据 */
CopyMemory((LPVOID)sh, (LPVOID)psh,
sizeof(IMAGE_SECTION_HEADER))
return TRUE
}
else
psh++
}
}
return FALSE
}
这个函数通过SECHDROFFSET宏将第一个段头部定位,然后它开始在所有段中循环,并将要寻找的段名称和每个段的名称相比较,直到找到了正确的那一个为止。当找到了段的时候,函数将内存映像文件的数据复制到传入函数的结构中,然后IMAGE_SECTION_HEADER结构的各域就能够被直接存取了。
返回目录
4.2 段头部的域
Name。每个段都有一个8字符长的名称域,并且第一个字符必须是一个句点。
PhysicalAddress或VirtualSize。第二个域是一个union域,现在已不使用了。
VirtualAddress。这个域标识了进程地址空间中要装载这个段的虚拟地址。实际的地址由将这个域的值加上可选头部结构中的ImageBase虚拟地址得到。切记,如果这个映像文件是一个DLL,那么这个DLL就不一定会装载到ImageBase要求的位置。所以一旦这个文件被装载进入了一个进程,实际的ImageBase值应该通过使用GetModuleHandle来检验。
SizeOfRawData。这个域表示了相对FileAlignment的段实体尺寸。文件中实际的段实体尺寸将少于或等于FileAlignment的整倍数。一旦映像被装载进入了一个进程的地址空间,段实体的尺寸将会变得少于或等于FileAlignment的整倍数。
PointerToRawData。这是一个文件中段实体位置的偏移量。
PointerToRelocations、PointerToLinenumbers、NumberOfRelocations、NumberOfLinenumbers。这些域在PE格式中不使用。
Characteristics。定义了段的特征。这些值可以在WINNT.H及本光盘(译注:MSDN的光盘)的PE格式规范中找到。
值 定义
0x00000020 代码段
0x00000040 已初始化数据段
0x00000080 未初始化数据段
0x04000000 该段数据不能被缓存
0x08000000 该段不能被分页
0x10000000 共享段
0x20000000 可执行段
0x40000000 可读段
0x80000000 可写段
返回目录
4.3 定位数据目录
数据目录存在于它们相应的数据段中。典型地来说,数据目录是段实体中的第一个结构,但不是必需的。由于这个缘故,如果你需要定位一个指定的数据目录的话,就需要从段头部和可选头部中获得信息。
借助专用软件清理常用的注册表清理软件有超级兔子注册表优化软件、RegCleaner、Notron Utility、Windows优化大师等,这些软件能够查找Windows注册表中的垃圾并给予清除,使系统清洁高效。这些软件各有优缺点,完全可以根据个人爱好自由选择,它们的使用方法也较简单,这里就不再介绍了。
注册表的手动清理
虽然用专门的软件来清理注册表比较简单方便,但是它们并不是万能的,有些对于自己来说并不需要的内容就得靠手工删除了。
方法是点击“开始/运行”,在对话框中输入“regedit”,调出注册表进行如下逐项 *** 作:
1、清除多余的窗口配色方案。可打开“HKEY-CURRENT-USER\Control Panel\Appearance\Schemes”,通常只保留“Windows标准”即可。更直观的方法是打开“控制面板/显示/外观”,在“窗口配色方案”下拉列表中进行删除。
2、删除多余的时区。可打开“HKEY-LOCAL-MACHINE\Software\Microsoft\Windows\CurrentVersion\Time Zones”,我们只使用北京时间,可以删除其余的时区设置。
3、清除国家列表。国家列表中包括了全世界100多个国家和地区,习惯氏扰是只留下“中国”,我想全部删除也无妨。可依次打开“HKEY-LOCAL-MACHINE\Software\Microsoft\Windows\CurrentVersion\Telephony\Country List”,删除多余的国家代码。
4、删除多余的语言代码表,留下“英语(美国)”、“中文(中国)”即可。打开“HKEY-LOCAL-MACHINE\System\CurrentControlSet\Control\Nls\Locale”,删除多余的语言代码。
5、删除“运行”中的程序执行记录。方法为打开“HKEY-USERS\.DEFAULT\Software\Microsoft\Windows\CurrentVersion\Explorer\RunMRU”,删除下面的内容。
6、删除失效的文件关联。注册表文件有关文件关联的内容存储在HKEY-CLASSES-ROOT键下。 该主键可以大致看成两部分。第一部分(按字母顺序A~Z排列)用来定义文件类型。第二部分与第一部分一一对应,用于记录打开文件的应用程序。一般来说,在第二部分中展开可疑键值后,如果在子键Command下无内容,说明该键为空键,可以删除。更直观的方法是打开“文件管理器/查看/选项/文件类型”,这其实可以看作是 HKEY-CLASSES-ROOT的一个图形界面, 重点查看那些使用“通用文件图标”(白底上带歼磨旦一个Windows标志)的项。如果确信用来打开文件的程序已经不存在,可将该项删除。上面两种方法可以配合使用。
注册表的压缩整理
Windows自带的Regedit.exe就可以做很好的压缩,即重游差新写入注册表文件。
其基本的用法如下:
1、在DOS下实模式(可以用“重新启动计算机并切换到DOS方式”),首先使用Regedit配合参数e导出注册表文件,格式如下:Regedit /e newreg.reg(导出文件名)。
2、使用Regedit配合参数c重写注册表文件:Regedit /c newreg.reg,执行后会有一个指令写入进程的百分比。
一般情况下,隔上一段时间用上面的方法压缩一下注册表文件即可较好地控制其大小。但要获得更好的效果(压缩的比例更大),就需要在压缩前用注册表清理工具对注册表文件作一番大扫除。对注册表文件进行各种 *** 作前请务必先备份(文件system.dat和user.dat),方法是导出注册表。
在PE文件格式中,它并非一个新概念,因为它与MS-DOS 2.0以来就已有的MS-DOS头部是完全一样的。保留这个相同结构的最主要原因是,当你尝试在Windows 3.1以下或MS-DOS 2.0以上的系统下装载一个文件的时候, *** 作系统能够读取这个文件并明白它是和当前系统不相兼容的。换句话说,当你在MS-DOS 6.0下运行一个Windows NT可执行文件时,你会得到这样一条消息:“This program cannot be run in DOS mode.”如果MS-DOS头部不是作为PE文件格式的第一部分的话, *** 作系统装载文件的时候就会失败,并提供一些完全没用的信息,例如:“The name specified is not recognized as an internal or external command, operable program or batch file.” MS-DOS头部占据了PE文件的头64个字节,描述它内容的结构如下:WINNT.H typedef struct _IMAGE_DOS_HEADER { // DOS的.EXE头部USHORT e_magic// 魔术数字USHORT e_cblp// 文件最后页的字节数USHORT e_cp// 文件页数USHORT e_crlc// 重定义元素个数USHORT e_cparhdr// 头部尺寸,以段落为单位USHORT e_minalloc// 所需的最小附加段USHORT e_maxalloc// 所需的最大附加段USHORT e_ss// 初始的SS值(相对偏祥培祥移量)USHORT e_sp// 初始的SP值USHORT e_csum//谨搏 校验和USHORT e_ip// 初始的IP值USHORT e_cs// 初始的CS值(相对偏移量)USHORT e_lfarlc// 重分配表文件地址USHORT e_ovno// 覆盖号USHORT e_res[4]// 保留字USHORT e_oemid// OEM标识符(相对e_oeminfo)USHORT e_oeminfo// OEM信息USHORT e_res2[10]// 保留字LONG e_lfanew// 新exe头部的文件地址 } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER第一个域 e_magic ,被称为魔术数字,它被用于表示一个MS-DOS兼容的文件类型。所有MS-DOS兼容的可执行文件都将这个值设为0x5A4D,表示ASCII字符MZ。MS-DOS头部之所以有的时候被称为MZ头部,就是这个缘故。还有许多其它的域对于MS-DOS *** 作系统来说都有中衡用,但是对于Windows NT来说,这个结构中只有一个有用的域——最后一个域 e_lfnew ,一个4字节的文件偏移量,PE文件头部就是由它定位的。对于Windows NT的PE文件来说,PE文件头部是紧跟在MS-DOS头部和实模式程序残余之后的。~另附一个其他参看资料2: http://www.wmgdesign.com/itschool/Program/vc/200612/itschool_13513.html欢迎分享,转载请注明来源:内存溢出
评论列表(0条)