PE文件格式.docx

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

PE文件格式.docx

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

PE文件格式.docx

PE文件格式

PE文件是Win32的原生文件格式.每一个Win32可执行文件都遵循PE文件格式.对PE文件格式的了解可以加深你对Win32系统的深入理解.

一、基本结构。

 

上图便是PE文件的基本结构。

(注意:

DOSMZHeader和部分PEheader的大小是不变的;DOSstub部分的大小是可变的。

一个PE文件至少需要两个Section,一个是存放代码,一个存放数据。

NT上的PE文件基本上有9个预定义的Section。

分别是:

.text,.bss,.rdata,.data,.rsrc,.edata,.idata,.pdata,和.debug。

一些PE文件中只需要其中的一部分Section.以下是通常的分类:

l执行代码Section,通常命名为:

.text(MS)orCODE(Borland)

l数据Section,通常命名为:

.data,.rdata,或.bss(MS)或DATA(Borland).

l资源Section,通常命名为:

.edata

l输入数据Section,通常命名为:

.idata

l调试信息Section,通常命名为:

.debug

这些只是命名方式,便于识别。

通常与系统并无直接关系。

通常,一个PE文件在磁盘上的映像跟内存中的基本一致。

但并不是完全的拷贝。

Windows加载器会决定加载哪些部分,哪些部分不需要加载。

而且由于磁盘对齐与内存对齐的不一致,加载到内存的PE文件与磁盘上的PE文件各个部分的分布都会有差异。

当一个PE文件被加载到内存后,便是我们常说的模块(Module),其起始地址就是所谓的HModule.

二、DOS头结构。

所有的PE文件都是以一个64字节的DOS头开始。

这个DOS头只是为了兼容早期的DOS操作系统。

这里不做详细讲解。

只需要了解一下其中几个有用的数据。

1.e_magic:

DOS头的标识,为4Dh和5Ah。

分别为字母MZ。

2.e_lfanew:

一个双字数据,为PE头的离文件头部的偏移量。

Windows加载器通过它可以跳过DOSStub部分直接找到PE头。

3.DOS头后跟一个DOSStub数据,是链接器链接执行文件的时候加入的部分数据,一般是“ThisprogrammustberununderMicrosoftWindows”。

这个可以通过修改链接器的设置来修改成自己定义的数据。

三、PE头结构。

PE头的数据结构被定义为IMAGE_NT_HEADERS。

包含三部分:

1.Signature:

PE头的标识。

双字结构。

为50h,45h,00h,00h.即“PE”。

2.FileHeader:

20字节的数据。

包含了文件的物理层信息及文件属性。

这里主要注意三项。

lNumberOfSections:

定义PE文件Section的个数。

如果对PE文件新增或删除Section的话,一定要记的修改此域。

lSizeOfOptionalHeader:

定义OptionHeader结构的大小。

lCharacteristics:

主要用来标识当前的PE文件是执行文件还是DLL。

其各位都有具体的含义。

数据位

Windows.inc的预定义

为1时的含义

0

IMAGE_FILE_RELOCS_STRIPPED

文件中不存在重定位信息

1

IMAGE_FILE_EXECUTABLE_IMAGE

文件是可执行的

2

IMAGE_FILE_LINE_NUMS_STRIPPED

不存在行信息

3

IMAGE_FILE_LOCAL_SYMS_STRIPPED

不存在符号信息

7

IMAGE_FILE_BYTES_REVERSED_LO

小尾方式

8

IMAGE_FILE_32BIT_MACHINE

只在32位平台运行

9

IMAGE_FILE_DEBUG_STRIPPED

不包含调试信息

10

IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP

不能从可移动盘运行

11

IMAGE_FILE_NET_RUN_FROM_SWAP

不能从网络运行

12

IMAGE_FILE_SYSTEM

系统文件。

不能直接运行

13

IMAGE_FILE_DLL

DLL文件

14

IMAGE_FILE_UP_SYSTEM_ONLY

文件不能在多处理器上运行

15

IMAGE_FILE_BYTES_REVERSED_HI

大尾方式

3.OptionalHeader:

总共224个字节。

最后128个字节为数据目录(DataDirectory)。

以下是字段的说明:

lAddressOfEntryPoint:

程序入口点地址。

但加载器要运行加载的PE文件时要执行的第一个指令的地址。

它是一个RVA(相对虚拟地址)地址。

一些对PE文件插入代码的程序就是修改此处的地址为要运行的代码,然后再跳转回此处原来的地址。

lImageBase:

PE文件被加载到内存的期望的基地址。

对于EXE文件,通常加载后的地址就期望的地址。

但是DLL却可能是其他的。

因为如果这个地址被占,系统就会重新分配一块新的内存,同时会修改此处加载后的地址。

EXE文件通常是400000h.

lSectionAlignment:

每一个Section的内存对齐粒度。

比如:

此值为4096(1000h),那么每一个Section的起始地址都应该是4096(1000h)的整数倍。

如果第一个Section的地址是401000h,大小为100个字节。

那么下一个Section的起始地址为402000h.。

两个Section之间的空间大部分是空的,未用的。

lFileAlignment:

每一个Section的磁盘对齐粒度。

比如,此值为512(200h),那么每一个Section在文件内的偏移位置都是512(200h)的整数倍。

与SectionAlignment同理。

lSizeOfImage:

PE文件在内存空间整个映像的大小。

包含所有的头及按SectinAlignment对齐的所有的Section。

lSizeOfHeaders:

所有的头加上Section表的大小。

也就是文件大小减去文件中所有Section的大小。

可以用这个值获取PE文件中第一Section的位置。

lDataDiretory:

16个IMAGE_DATA_DIRECTORY结构的数组。

每一个成员都对应一个重要的数据结构,比如输入表,输出表等。

 

有两个地方需要注意:

l如果PEheader里的最后两个字段被赋予一个伪造的值的话,比如:

nLoaderFlags=ABDBFFFFh(其默认值为0)

nNumberOfRvaAndSizes=DFFDEEEEh(其默认值为10h)

一些调试工具或反编译工具会认为这个PE文件是损坏的。

有的会直接执行,如果是病毒的话,就会被直接感染;有的则会重启工具。

所以最好在查看调试一个PE文件前,先看一下这里的取值是否被人赋予一个伪造的很大的值。

如果是的话,先修改成默认的值。

l有人可能注意到在一些PE文件(MS的链接器链接的PE文件)的DOSStub部分跟PEheader部分之间存在一部分垃圾数据。

标识为其倒数第二非0的双字节是一个“Rich”。

这部分数据包含了一些加密数据,来标识编译这个PE文件的组件。

可用来检举某些病毒程序所编译的程序来自哪台机器。

四、数据目录结构(DataDirectory)。

DataDirectory是OptionalHeader的最后128个字节,也是IMAGE_NT_HEADERS的最后一部分数据。

它由16个IMAGE_DATA_DIRECTORY结构组成的数组构成。

IMAGE_DATA_DIRECTORY的结构如下:

每一个IMAGE_DATA_DIRECTORY都是对应一个PE文件重要的数据结构。

他们分别如下:

VirtualAddress指的是对应数据结构的RVA地址;iSize指的是对应数据结构的大小(字节单位)。

一个PE文件一般只包含其中的一部分,也就是其中一部分数据结构是有数据的;另一部分则都是0。

比如,EXE文件一般都存在IMAGE_DIRECTORY_ENTRY_IMPORT(输入表),而不存在IMAGE_DIRECTORY_ENTRY_EXPORT(输出表)。

而DLL则两者都包含。

下图就是某一个PE文件的数据目录:

五、Section表。

Section表紧跟在PEheader后面。

由IMAGE_SECTION_HEADER数据结构组成的数组。

每一个包含了对应Section在PE文件中的属性和偏移位置。

这里不是所有的成员都是有用的。

lName1:

块名,这是一个8位ASCII码名,用来定义块名。

多数块名以一个"."开始(如.text),尽管许多PE文档都认为这个"."实际上并不是必须的。

值得注意的是,如果块名超过8位,则最后的NULL不存在。

带有一个"$"的区块名字会从链接器那里得到特殊的对待,前面带"$"的相同名字的区块被合并,在合并后的区块中它们是按"$"后面的字符字母顺序进行合并的。

lMisc.VirtualSize:

指出实际的、被使用的区块大小。

如果VirtualSize大于SizeOfRawData,那么SizeOfRawData来自于可执行文件初始化数据的大小,与VirtualSize相差的字节用0填充。

这个字段在OBJ文件中设为0。

lVirtualAddress:

该块装载到内存中的RVA。

这个地址是按照内存页对齐的,它的数值总是SectionAlignment的整数倍。

在MS工具中,第一块的默认RVA为1000H.在OBJ中,该字段没意义。

如果该值为1000H,PE文件被加载到400000H,那么该Section的起始地址为401000H。

lSizeOfRawData:

该块在磁盘文件中所占的大小。

在可执行文件中,这个值必须是PE头部指定的文件对齐大小的倍数。

如果是0,则说明区块中的数据是未初始化的。

该块在磁盘文件中所占的大小,这个数值等于VirtualSize字段的值按照FileAlignment的值对齐以后的大小。

例如,FileAlignment的大小为1000H,如果VirtualSize中的块长度为2911,则SizeOfRawData为3000H}

lPointerToRawData:

该块在磁盘文件中的偏移。

对于可执行文件,这个值必须是PE头部指定的文件对齐大小的倍数。

lPointerToRelocations:

这部分在EXE文件中无意义。

在OBJ文件中,表示本块重定位信息的偏移量。

在OBJ文件中如果不是零,则会指向一个IMAGE_RELOCATION的数据结构。

lNumberOfRelocations:

由PointerToRelocations指向的重定位的数目。

lNumberOfLinenumbers:

由NumberOfRelocations指向的行号的数目,只在COFF样式的行号被指定时使用。

lCharacteristics:

块属性,该字段是一组指出块属性(如代码/数据/可读/可写等)的标志。

多个标志值通过OR操作形成Characteristics的值。

这些标志很多都可以通过链接器/SECTION选项设置。

数据位在Windows.inc中的预定义

为1时的含义

IMAGE_SCN_CNT_CODE(00000020H)

节中包含代码

IMAGE_SCN_CNT_INITIALIZED_DATA(00000040H)

节中包含已初始化数据

IMAGE_SCN_CNT_UNINITIALIZED_DATA(00000080H)

节中包含未初始化数据

25

IMAGE_SCN_MEM_DISCARDABLE(02000000H)

节中的数据在进程开始后将被丢弃

26

IMAGE_SCN_MEM_NOT_CACHED(04000000H)

节中的数据不会经过缓存

27

IMAGE_SCN_MEM_NOT_PAGED(08000000H)

节中的数据不会被交换到磁盘

28

IMAGE_SCN_MEM_SHARED(10000000H)

节中的数据将被不同的进程所共享

29

IMAGE_SCN_MEM_EXECUTE(20000000H)

映射到内存后的页面包含可执行属性

30

IMAGE_SCN_MEM_READ(40000000H)

映射到内存后的页面包含可读属性

31

IMAGE_SCN_MEM_WRITE(80000000H)

映射到内存后的页面包含可写属性

六、PE文件各个Section。

PE文件的Sections部分包含了文件的内容。

包括代码,数据,资源和其他可执行信息。

每一个Section由一个头部和一个数据部分组成。

所有的头部都存放在紧跟PEheader后的Section表内。

1.执行代码。

在NTWindows系统内,所有的PE文件的代码段都存放在一个Section内,通常命名为.text(MS)或CODE(Borland)。

这一段包含了早先提起的AddressOfEntryPoint多指地址的指令及输入表中的jumpthunktable。

2.数据。

l.bss段存放未初始化的数据,包括函数内或源模块内声明的静态变量。

l.rdata段存放只读数据,比如常字符串,常量,调试指示信息。

l.data段存放其他所有的数据(除了自动化变量,其存放在栈中)。

比如程序的全局变量。

3.资源。

.rsrc段包含了一个模块的资源信息。

以资源树的结构存放数据。

需要用工具来查看。

4.输出数据。

.edata段包含了PE文件的输出目录(ExportDirectory)。

5.输入数据。

.idata包含了PE文件的输入目录和输入地址表。

6.调试信息。

调试信息存放在.debug段。

PE文件也支持单独的调试文件。

Debug段包含调试信息,但是调试目录却存放在.rdata内。

7.线程局部存储。

(TLS)

Windows支持每一个进程包含多个线程。

每一个线程有其私有的存储空间(TLS)去存放线程自身的数据。

链接器都会为进程创建一个.tls段来存放TLS模板。

当进程创建一个线程时,系统就会按照这个模板创建一个线程私有的局部存储空间。

8.基重定位。

当加载器加载PE文件到内存的时候,有时候不一定是其预期的基地址。

那么就需要调整内部指令的相对地址。

所有需要调整的地址都存放在.reloc段内。

七、输出Section.

 

这个Section跟DLL关系比较密切。

DLL一般定义两种函数,内部使用的,和输出到外部给其他调用程序使用的。

输出到外部的函数就存储在这个Section内。

DLL输出函数分两种方式,通过名称和通过序号输出。

当其他程序需要调用DLL的时候,调用GetProcAddress,通过设置需要调用的函数名称或函数序号可以调用DLL内部输出的函数。

那么GetProcAddress是怎么获取DLL中真正的输出函数地址呢?

以下是详细的解说。

PE头的数据目录(DATADIRECTORY)数组的第一个成员对应的(通过其中的RVA地址可获得)数据结构是IMAGE_EXPORT_DIRECTORY(这里称为输出目录)。

成员

大小

描述

Characteristics

DWORD

未定义,总是0

TimeDateStamp

DWORD

输出表的创建时间。

与IMAGE_NT_HEADER.FileHeader.TimeDateStamp有相同的定义

MajorVersion

WORD

输出表的主版本号。

未使用,为0

MinorVersion

DWORD

输出表的次版本号。

未使用,为0

nName

DWORD

指向一个ASCII字符串的RVA,这个字符串是与这些输出函数关联的DLL的名称(比如,Kernel32.dll)。

这个值必须定义,因为如果DLL文件的名称如果被修改,加载器将使用这里的名称。

nBase

DWORD

这个字段包含用于这个可执行文件输出表的起始序数值(基数)。

正常情况下为1,但不是一定是。

当通过序数来查询一个输出函数时,这个值会被从序数里减去。

(比如,如果nBase=1,被查询的函数的序数是3,那么这个函数在序号表的索引是3-1=2)。

NumberOfFunctions

DWORD

输出地址表(EAT)的条目数。

其中一些条目可能是0,意味着这个序数值没有代码和数据输出。

NumberOfNames

DWORD

输出名称表(ENT)的条目数。

这个值总是大于或等于NumberOfFunctions。

小于的情况发生在符号只通过序数来输出时。

另外,当被赋值的序数里有数字间隔时也会有小于的情况。

这个值也是输出序数表的长度。

AddressOfFunctions

DWORD

输出地址表(EAT)的RVA。

输出地址表本身是一个RVA数组,数组中的每一个非零的RVA都对应一个被输出的符号。

AddressOfNames

DWORD

输出名称表(ENT)的RVA。

输出名称表本身是一个RVA数组。

数组中的每一个非零的RVA都向一个ASCII字符串。

每一个字符串都对应一个通过名称输出的符号。

这个表是排序。

这允许加栽器在查询一个被输出的符号时可用二进制查找方式。

名称的排序是二进制的,而不是按字母。

AddressOfNameOrdinals

DWORD

输出序数表(EOT)的RVA。

这个表将ENT中的数组索引映射到相应的输出地址条目。

实际上,IMAGE_EXPORT_DIRECTORY结构指向三个数组和一个ASCII字符串表。

其中重要的是输出地址表(EAT,即AddressOfFunctions指向的表),输出函数地址指针(RVA)构成了这个表。

而ENT和EOT则是可以一起合作来获取EAT里对应的地址数据。

下图演示了这个过程。

这个被加载的DLL的名称是F00.DLL。

总共输出了四个函数,其RVA地址分别为0x400042、0x400156、0x401256和0x400520。

一个外部调用程序需要调用其中一个名为”Bar”的函数,那么它先在输出名称表(ENT)里查找名称为Bar的函数,找到后,根据其在输出序号表(EOT)中对应的索引号,获取其中的数值为EAT中的索引值,这里是4,然后从EAT中根据索引4获取其真正的RVA地址0x400520。

以下是几个注意点:

l输出序号表(EOT)的存在就是为了是EAT跟ENT之间产生关联。

每一个ENT内的成员(函数名)有且只有一个EAT内的成员(函数地址)对应。

但是一个EAT内的成员并不是只有一个ENT内的成员对应。

比如,有的函数存在别名的话,就会出现多个ENT内的成员都对应一个EAT内的成员。

l如果已经获得一个函数的序号值,那么就可以直接到EAT内获得其RVA地址,而不需要经过ENT和EOT进行查找。

但是这样的按序号输出的DLL不易于维护。

l通常情况下,EAT的个数(NumberOfFunctions)必须小于或等于ENT的个数(NumberOfNames)。

只有在一个函数按序号输出时(其在ENT和EOT表里没有对应的数据),ENT的数量才有可能少于EAT的数量。

比如,总共有70个函数输出,但是在ENT表里只有40个,这就意味着剩余的30个函数是靠序号输出的。

那么我们如何知道哪些是直接靠序号输出的呢?

只有通过排除法来获得。

把存在在EOT表里的序号从EAT里排除出去,剩下的就是靠序号输出的函数。

l当通过一个序号值来获取EAT内的函数RVA时,需要把这个序号值减去nBase的值来获取在EAT表里真正的索引位置。

而通过名称查找则不需要这么做。

l输出转向。

某些时候,你从一个DLL中调用的一个函数可能位于另一个DLL中。

这就叫输出转向。

比如,Kernel32.dll中的HeapAlloc就是转到调用NTDLL.dll中的RtlAllocHeap。

这种转向是在链接的时候,在.DEF文件中定义一个特殊的指令来实现的。

那么当一个函数被转向后,在其所在EAT表里对应的数据便不是其地址,而是一个指向表明被转向的DLL和函数的ASCII字符串的地址指针。

上图就是Kernel32.dll的输出函数表,其中HeapAlloc的RVA值0x00009048就是一个指向“NTDLL.RtlAllocHeap”的指针。

八、输入Section.

输入Section通常位于.idata段内。

它包含了所有程序需要用到的来自其他DLL的函数的信息。

Windows加载器负责加载所有程序用到的DLL到进程空间。

然后为进程找到所有其需要用到的函数的地址。

下面描述这个过程:

PE头的数据目录(DATADIRECTORY)数组的第二个成员对应的(通过其中的RVA地址可获得)数据结构是输入表。

输入表是一个IMAGE_IMPORT_DESCRIPTOR数据结构的数组。

没有字段表明这个数组的个数,只是它的最后一个成员的数据都为0。

每一个数组成员都对应一个DLL。

成员

大小

描述

OriginalFirstThunk

DWORD

指向输入名称表(INT)的RVA。

INT是由IMAGE_THUNK_DATA数据结构构成的数组。

数组中的每一个成员定义了一个输入函数的信息,数组最后以一个内容为0的IMAGE_THUNK_DATA结束。

TimeDateStamp

DWORD

当执行文件不与被输入的DLL进行绑定时,这个字段为0。

当以旧的方式绑定时,这个字段包括时间/日期。

当以新的样式绑定时,这个字段为-1。

ForwarderChain

DWORD

这是第一个被转向的API的索引。

老样式绑定的定义。

Name

DWORD

指向被输入DLL的ASCII字符串的RVA。

FirstThunk

DWORD

指向输入地址表(IAT)的RVA。

IAT也是一个IMAGE_THUNK_DATA数据结构的数组。

由上表可知,输入表主要是通过IMAGE_THUNK_DATA这个数据结构导入函数。

下面是IMAGE_THUNK_DATA的描述:

这是一个DWORD联合体数据结构。

其实这里对输入表有意义的字段只有两个,Ordinal和AddressOfData。

当这个DWORD数据的最高位为1的时候,代表函数以序号的方式导入,Ordinal的低31位就是输入函数在其DLL内的导出序号。

当这个DWORD的数据最高位为0的时候,代表函数以字符串方式导入。

AddressOfData就是一个指向用来导入函数名称的IMAGE_IMPORT_BY_NAME的数据结构的RVA。

(这里用来判断最高位的值0x8000000,预定义值为IMAGE_ORDINAL_FLAG32。

lHint字段也表示函数的序号,主要是用来便与加载器快速查找在导入的DLL的函数导出表,当通过这个序号查找到的函数跟所要导入的函数不匹配时,就改为通过名称查找。

不过这个字段是可选的,有些编译器把它设置为0。

lName1字段定义了导入函数的名称字符串,这是一个以0为结尾的字符串。

整个过程有点复杂,下图给出一个相对清晰的描述。

1.加载器首先读入IMAGE_IMPORT_DESCRIPTOR,获得需要加载的动态库User32.DLL。

2.加载器根据OriginalFirstThunk或Fir

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

当前位置:首页 > 农林牧渔 > 林学

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

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