购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.1 Windows PE文件

2.1.1 Windows PE文件简介

Windows可移植可执行(PE)文件是Windows系统引入并使用的标准的可执行文件格式,具备借鉴性和兼容性。关于借鉴性,Windows PE文件格式主要借鉴了UNIX操作系统所采用的通用对象文件格式(Common Object File Format,COFF)规范。这从如下事实可以看出:Microsoft Visual C++编译器产生的中间态目标文件仍使用COFF格式,经过链接后才能最终生成可执行的PE文件格式。在兼容性方面,Windows PE文件格式保留了MS-DOS操作系统中熟悉的MZ头部,以确保兼容之前的MS-DOS操作系统。此外,Windows PE文件格式有2种类型:32位的可执行文件PE32和64位的可执行文件PE+或者PE32+。

在Windows系统中,采用Windows PE文件格式的文件很多,主要包括:扩展名为EXE、SCR的可执行系列,扩展名为SYS、VXD的驱动程序系列,扩展名为DLL、OCX、CPL、DRV的链接库系列,以及扩展名为OBJ的对象文件系列。其中,OBJ文件是不可执行的,其他都是可执行的,DLL、SYS文件虽然不能直接在Shell上执行,但在调试器或服务上是可执行的。Windows PE文件的组织方式由PE文件头和PE文件体组成(见图2-1)。

图2-1 Windows PE文件组织方式

2.1.2 Windows PE文件基本概念

为详细解剖Windows PE文件结构,我们使用MASM汇编语言编写一个简单的“Hello World”程序,并借此程序来学习、掌握Windows PE文件格式的相关基础知识。

“Hello World”汇编程序源代码如下:

使用MASMplus编译器编译、连接后运行结果如图2-2所示。

图2-2 Hello World程序运行结果

我们利用WinHex、PEview等工具来查看PE文件结构,并对比PE文件在磁盘和内存中的差异。在学习具体的PE文件格式之前,将常见的几个“地址”概念解释一下。

文件偏移地址(File Offset Address,FOA):是指文件在磁盘上存放时某一地址相对于文件开头的偏移。

装载基址(Image Base):PE文件装入内存时的基地址。默认情况下EXE文件的装载基址是0x00400000,而DLL文件的装载基址是0x10000000。这些默认装载基址是可以更改的。

虚拟内存地址(Virtual Address,VA):PE文件中某个地址被装入内存后的地址。

相对虚拟地址(Relative Virtual AAddress,RVA):某个地址没有计算装载基址时相对于0的偏移地址。

1.PE文件指纹

在识别一个文件是否为PE文件时,不应只看文件后缀名,而应借助PE文件指纹:“MZ”和“PE”。使用010 Editor打开一个EXE文件,可以看到文件的头两字节都是“MZ”;而在0x3C位置处保存着一个地址,查看该地址可以看到保存着“PE”。通过这两个PE文件指纹,可基本判定为PE文件(见图2-3)。

图2-3 PE文件指纹

2.PE文件结构

Windows PE文件结构被组织为一个线性的数据流,主要包括4大部分:DOS部分、PE文件头、节表、节数据(见图2-4)。

图2-4 Windows PE文件结构

利用PEview打开一个EXE文件,其左窗口中可以看到列出的PE文件完整结构(见图2-5)。

图2-5 PEview列出的PE文件完整结构

3.PE文件的两种状态

一般而言,Windows PE文件存在两种不同状态:PE磁盘文件和PE内存映像。从文件结构方面来看,存储于磁盘上的PE磁盘文件与加载至内存中运行的PE内存映像会有所不同,这主要是Windows系统存储数据时基于不同的数据对齐基准导致的。对于PE磁盘文件,文件数据一般以1个磁盘扇区(512=0x200字节)对齐的方式存储;对于加载至内存中运行的PE内存映像,文件数据一般以1个内存分页(4096=0x1000字节)对齐的方式存储。因此,PE磁盘文件中块的大小为0x200的整数倍,PE内存映像中块的大小为0x1000的整数倍,映射后实际数据的大小不变,块中剩余部分用0填充(见图2-6)。

图2-6 PE磁盘文件与PE内存映像结构

在利用PEview打开一个EXE文件时,从IMAGE_OPTIONAL_HEADER中可以看到Section Alignment(内存节对齐)数值为0x1000,File Alignment(磁盘文件对齐)数值为0x200(见图2-7)。

图2-7 PE文件的内存节对齐数值与磁盘文件对齐数值

为验证上述有关磁盘文件对齐与内存节对齐的知识,我们利用WinHex打开之前已创建并编译连接的HelloWorld.exe程序,且在不关闭该程序的情况下打开RAM,可看出PE磁盘文件和PE内存映像存在如下不同(见图2-8、图2-9)。

(1)偏移起始地址:磁盘文件偏移从00000000开始,而内存映像偏移从00400000开始。

(2)对齐数值:从偏移220开始,两个文件的内容都是00;但磁盘文件到偏移400处就开始出现新数据,而内存映像则要到偏移1000处才有类似数据。这印证了上述Section Alignment(内存节对齐)数值为0x1000和File Alignment(磁盘文件对齐)数值为0x200且用0填充对齐的知识点。

图2-8 “HelloWorld”程序的磁盘文件和内存映像的偏移起始地址

图2-9 “HelloWorld”程序的磁盘文件和内存映像的对齐数值

2.1.3 Windows PE文件结构

Windows PE文件所有与加载及运行相关的信息都体现在PE文件结构中,下面将简要介绍PE文件结构。

1.DOS头

DOS头是Windows PE文件的起始部分,是DOS时代遗留的产物,也是PE文件的一个遗传基因。DOS头结构体IMAGE_DOS_HEADER的大小为64字节(0x00-0x3F),结构如下:

DOS头主要用于16位系统中,在32位系统中DOS头成为冗余数据(可填充任意数据),但有两个重要成员(PE文件指纹):e_magic字段(偏移0x0)和e_lfanew字段(偏移0x3C)。

(1)e_magic:保存DOS签名“MZ”(0x4D5A)。

(2)e_lfanew:保存PE文件头地址,可通过这个地址找到PE文件头及其标识“PE”。

我们利用PEview工具打开任意一个EXE文件,可以看到其DOS头信息(见图2-10)。

图2-10 PE文件的DOS头

2.DOS存根

为兼容DOS系统,PE文件结构增加了DOS存根(DOS Stub)部分(见图2-11)。DOS存根区域大小为112字节(0x40-0xAF),其数据由链接器自动填充(也可填充任意数据),是一段可以在DOS下运行的代码,用于向终端输出一行提示信息:This program cannot be run in DOS mode。

图2-11 PE文件的DOS存根

我们从00~A0这段十六进制数另存为一个单独的文件——DOS.bin(见图2-12)。

图2-12 PE文件00~A0的数值

利用IDA Pro工具打开这个DOS.bin文件,即可查看其反汇编后的代码,可以看到它先调用DOS的9号中断用于在屏幕上显示上述提示字符,再调用4C号中断退出程序(见图2-13)。

图2-13 DOS Stub程序的反汇编代码

其实,在DOS头和DOS Stub程序部分,最重要的只有两个字段:e_magic(偏移0x00)保存DOS签名“MZ”(0x4D5A),e_lfanew(偏移0x3C)保存PE文件头地址。如果只保留这两个字段,将偏移0x00~0xB0区间的剩余字段随意填充数据,PE文件仍可正常运行。

图2-14、图2-15分别为HelloWorld.exe的0x00~0xB0区间的原有十六进制数值和仅保留偏移0x00处的DWORD字段e_magic值和偏移0x3C处的DWORD字段e_lfanew值而删除剩余其他字段值的HelloWorld.exe。

图2-14 “HelloWorld”程序原有代码

图2-15 仅保留两个字段e_magic、e_lfanew值的“HelloWorld”程序

将修改后的HelloWorld.exe保存并运行,可发现该程序仍能正常运行,且运行结果与原程序相同(见图2-16)。

图2-16 修改后的HelloWorld程序

3.PE文件头

PE文件头(NT头)是真正的Win32程序格式头部,其中包括PE格式的各种信息,用于指导系统如何装载和执行此程序代码。PE文件头的结构体IMAGE_NT_HEADER中还包含两个其他结构体,占用248字节,IMAGE_NT_HEADER结构体如下:

PE文件头只有3个字段。

1)Signature

Signature类似于DOS头中的e_magic,其高16位是0,低16位是0x00004550,用ASCII码字符表示为“PE00”,用于标识PE文件头的开始(见图2-17)。

图2-17 PE文件的PE文件头标识

2)FileHeader

FileHeader定义了PE文件的一些基本信息和属性,这些属性会在PE加载器加载时用到。当PE加载器检测到PE文件头中定义的一些属性不满足当前运行环境时,就会终止加载该PE文件。PE文件头结构体IMAGE_FILE_HEADER大小为20字节,在微软的官方文档中被称为标准通用对象文件格式(COFF),定义如下:

PE文件的FileHeader结构体中有以下几个重要的字段。

Machine: 表示运行于什么CPU上,0代表任意,0x014C代表Intel 386及后续CPU,0x8664代表x64系列CPU。

NumberOfSections: 表示该文件中节的数量。

SizeOfOptionalHeader: 表示其后的IMAGE_OPTIONAL_HEADER32结构的大小,32位为0xE0,64位为0xF0。

Characteristics: 表示文件属性(见表2-1)。

表2-1 IMAGE_FILE_HEADER.Characteristics属性位的含义

续表

PE文件的文件头信息如下(见图2-18)。

图2-18 PE文件的文件头信息

3)OptionalHeader

OptionalHeader是PE可选头。尽管称为可选头,但其实是不可或缺的字段。在不同系统平台下,其结构体表示不一样,如32位下是IMAGE_OPTIONAL_HEADER32,而在64位下是IMAGE_OPTIONAL_HEADER64。

PE可选头中重要的字段:IMAGE_OPTIONAL_HEADER结构体的大小由IMAGE_FILE_HEADER结构的SizeOfOptionalHeader字段记录。以32位IMAGE_OPTIONAL_HEADER32说明如下。

Magic :表示文件类型。32位PE文件数值为0x010B,64位PE文件数值为0x020B,ROM映像文件数值为0x0107。

AddressOfEntryPoint :表示程序入口的相对虚拟地址(Relative Virtual Address,RVA)。在大多数可执行文件中,该地址不直接指向Main、WinMain或DLLMain函数,而指向运行时的库代码并由它来调用上述函数。

ImageBase :表示内存镜像基址,通常为0x00400000,也可在链接时自己设置。ImageBase+AddressOfEntryPoint=程序实际运行入口地址(使用OllyDBG调试程序时就从这个地址开始运行)。

Section Alignment :表示内存对齐数值,一般为4KB,即内存页大小。

File Alignment :表示磁盘文件对齐数值,一般为512字节,即扇区大小,现在多为4KB。

NumberOfRvaAndSizes :表示数据目录项数目。

DataDirectory [16]:数据目录表,由数个相同的IMAGE_DATA_DIRECTORY结构组成,指向输出表、输入表、资源块、重定位表等。

IMAGE_DATA_DIRECTORY结构体定义如下:

PE文件的可选头大小为224字节(见图2-19)。

图2-19 PE文件的可选头信息

4.节表

PE文件中的代码数据按照不同属性存在不同的节中。节类似于容器,用于存储PE文件的真正程序部分(代码和数据),且每个节可拥有独立的内存权限、节的名字。如果PE文件头的NumberOfSections值中有 N 个节,则节表就由 N 个IMAGE_SECTION_HEADER结构组成。每个IMAGE_SECTION_HEADER结构大小为40字节,存储了它所关联的节的信息,如位置、长度、属性等。

IMAGE_SECTION_HEADER结构体定义如下:

该结构体中重要的字段有以下几个。

Name[8] :表示节的名字,如.text、.data等。

VirtualSize :表示加载到内存中实际节的大小(对齐前)。

VirtualAddress :表示该节装载到内存中的RVA(内存对齐后,数值总是Section Alignment的整数倍)。

SizeOfRawData :表示该节在文件中所占的空间(文件对齐后),VirtualSize的值可能会比SizeOfRawData大,如bss节(SizeOfRawData为0)。

PointerToRawData :表示该节在文件中的偏移(FOA)。

Characteristics :表示节的属性,如代码/数据、可读/可写等(见表2-2)。

表2-2 IMAGE_SECTION_HEADER.Characteristics数据位含义

在PE文件结构中,经常会用到3个地址:VA表示虚拟内存地址;RVA表示相对虚拟地址,即相对于基地址的偏移地址;FOA表示文件偏移地址。

三者的转换步骤为:

(1)计算RVA=VA-ImageBase。

(2)若RVA位于PE头部(DOS头、DOS Stub、PE头、节表),则FOA==RVA。

(3)判断RVA位于哪个节。如RVA>=节.VirtualAddress(节在内存对齐后RVA)且RVA<=节.VirtualAddress+当前节内存对齐后的大小,则偏移量=RVA-节.VirtualAddress。

(4)FOA=节.PointerToRawData+偏移量。对于计算机病毒来说,由于已设定初始值的全局变量,其初始值会存储在PE文件中,如欲修改PE文件中全局变量的数据值,只需要找到文件中存储全局变量值的地方进行修改即可。

在上述关于PE文件可选PE头(IMAGE_OPTIONAL_HEADER)的介绍中,已提到其最后一个字段DataDirectory[16]代表数据目录表,由16个相同的IMAGE_DATA_DIRECTORY结构组成。

IMAGE_DATA_DIRECTORY结构体定义如下:

DataDirectory[16]数据目录表中的成员分别指向输出表、输入表、重定位表、资源表等信息,这些信息与PE文件的加载及执行息息相关。

1)输出表(导出表)

DataDirectory[16]数据目录表中的第1个成员指向输出表(导出表)。在创建一个动态链接库(Dynamic Link Library,DLL)时,实际上创建了一组能让EXE文件或其他DLL调用的函数。DLL文件通过输出表(Export Table)向系统提供输出函数名、序号和入口地址等信息。

输出表是一个40字节的结构体IMAGE_EXPORT_DIRECTORY,定义如下:

输出表中的输出函数名、序号和入口地址的查询如下(见图2-20)。

图2-20 输出表查询

2)输入表(导入表)

DataDirectory[16]数据目录表中的第2个成员指向输入表(导入表)。当PE文件加载至内存后,Windows系统将相应的DLL文件装入,EXE文件通过“输入表”找到相应的DLL中的导入函数,从而完成程序的正常运行。当前文件依赖几个DLL模块,就会有几个输入表且连续排列直到连续出现20个0结束。

输入表是一个20字节的结构体IMAGE_IMPORT_DESCRIPTOR,定义如下:

输入表有以下几个重要字段。

Name :DLL(依赖模块)名字的指针,一个以“00”结尾的ASCII字符的RVA地址。

OriginalFirstThunk :指向输入函数名称表(Imported Name Table,INT)的RVA。INT是一个IMAGE_THUNK_DATA结构的数组,数组中的每个IMAGE_THUNK_DATA结构都指向IMAGE_IMPORT_BY_NAME结构,数组以一个内容为0的IMAGE_THUNK_DATA结构结束。

FirstThunk :指向输入地址表(Imported Address Table,AT)的RVA。IAT是一个IMAGE_THUNK_DATA结构的数组。

IMAGE_THUNK_DATA结构实际只占4字节,定义如下:

如果IMAGE_THUNK_DATA32的最高位为1,则第31位代表函数的导出序号,否则4字节是一个RVA,指向IMAGE_IMPORT_BY_NAME结构。

IMAGE_IMPORT_BY_NAME结构体仅有4字节,存储了一个输入函数的相关信息,定义如下:

INT和IAT内容一致表示PE文件处于未加载状态,当PE加载器将文件载入内存后会向IAT填入真正的函数地址(GetProcAddress)。输入表中的函数名称及其入口地址的查询转换关系如图2-21所示。

3)重定位表

尽管在PE文件的可选头中设置有ImageBase值,但如果PE文件不在首选地址(ImageBase)载入,文件中的每一个绝对地址都需要重新修正。由于需要修正的地址很多,就需要在文件中使用重定位表记录这些绝对地址的位置。在载入内存后,若载入基地址与ImageBase不同,就需要修正这些地址,若相同则无须修正。

DataDirectory[16]数据目录表中的第6个成员指向重定位表(Relocation Table)。重定位表由一个个的重定位块组成,每个块记录了4KB(1页)的内存中需要重定位的地址。每个重定位块的大小必须以DWORD(4字节)对齐。它们以一个IMAGE_BASE_RELOCATION结构开始,格式定义如下:

图2-21 输入表查询

尽管可能有多种重定位类型,但对x86可执行文件来说,所有的基址重定位类型都是IMAGE_REL_BASED_HIGHLOW,即第3种类型,并会以IMAGE_REL_BASED_ABSOLUTE类型来结束重定位。IMAGE_REL_BASED_ABSOLUTE重定位什么都不做,只用于填充,以便下一个IMAGE_BASE_RELOCATION按4字节分界线对齐。对于IA-64可执行文件,重定位类型总是IMAGE_REL_BASED_DIR64。尽管IA-64的EXE页大小是8KB,但基址重定位仍是4KB的块。重定位类型定义如下:

在执行PE文件前,加载程序在进行重定位时,会用PE文件在内存中的实际映像地址减去PE文件所要求的映像地址,根据重定位类型的不同,将差值添加到相应的地址数据中。由此可见,重定位表的作用为:PE文件加载至内存后,通过重定位表记录的RVA找到需要重定位的数据。重定位表通过“页基址RVA+页内偏移地址”的方式得到一个完整RVA,以减小表的大小(见图2-22)。

图2-22 重定位过程

4)资源表

Windows程序的各种界面被称为资源,包括加速键(Accelerator)、位图(Bitmap)、光标(Cursor)、对话框(Dialog Box)、图标(Icon)、菜单(Menu)、串表(String Table)、工具栏(Toolbar)和版本信息(Version Information)等。

定义资源时,既可用字符串作为名称来标识一个资源,也可通过ID号来标识资源(见表2-3)。

表2-3 系统预定义的资源类型

DataDirectory[16]数据目录表中的第3个成员指向资源表(Resource Table),不是直接指向资源数据,而是以磁盘目录形式定位资源数据。资源表是一个四层的二叉排序树结构(见图2-23)。

图2-23 资源的树形结构

每个节点都由资源目录结构和紧随其后的数个资源目录项结构组成,两种结构组成一个资源目录结构单元(目录块)(见图2-24)。

图2-24 资源目录结构

资源目录结构(IMAGE_RESOURCE_DIRECTORY)占16字节,其定义如下:

资源目录项结构(IMAGE_RESOURCE_DIRECTORY_ENTRY),占8字节,包含2个字段,结构定义如下: CsLNQ51sBdVCpXYGjY/4OYPYUjXFtUooB2lO5Dgug/dWfOPK3Em7uA4o7hMGQerH

点击中间区域
呼出菜单
上一章
目录
下一章
×