PE结构分析

一;PE结构_PE头

前言

    该文章前半部分讲解整个PE的结构。然后我们来解析一段程序字节码来加深PE结构的理解。

天象独行

    注意:该文章说明的是32bit的PE结构。本质上64bit 结构(PE32+)与32bit结构上没有区别。区别在于修改了几个域,将原来的域扩展到64bit而已。

1;PE结构框架

    PE结构简单来说就是由一系列有组织的数据的集合组成的。下面我们直接来看下图来了解PE文件的基本结构框架。

911429_CN6RTH7WTTVSXTK

    我们先看图当中右边的部分,我们可以看到整个PE结构由DOS头,PE头,节表(因为翻译的不同,部分文章当中用“块”表示,同一个含义),节内容组成。接下来,我们分别来看看这些结构有什么作用。

DOS MZ头和DOS Stub

    第一部分Dos 头主要是为了兼容DOS系统所遗留下来的产物。它里面包含了“DOS MZ头”和“DOS Stub”两个部分。其中DOS MZ头它的定义数据结构名称为IMAGE_DOS_HEADER。

    下图为该数据结构的描述。DOS Stub 当中包含了一些DOS指令。

911429_BBE9DESZM954WC2

IMAGE_NT_HEADERS

    第二部分PE头的数据结构定义为IMAGE_NT_HEADERS。(如下图查看) 

911429_KZY9TSCXJWRDGXE

    数据结构IMAGE_NT_HEADERS当中包含了Signature,IMAGE_FILE_HEADER,IMAGE_OPTIONAL_HEADER32,IMAGE_DATA_DIREC++TORY。当中后三个包含的内容本身也是一个数据结构(数据结构当中嵌套数据结构形式)

PE头标志Signature

    PE头标志Signature是标志着PE头的开始位置。占位四个字节,这个位置的地址保存在DOS MZ头当中最后一个元素(IMAGE_DOS_HEADER.e_lfanew)。

标准PE头IMAGE_FILE_HEADER

    Signature标识之后紧接着就是标准PE头内容。整个IMAGE_FILE_HEADER数据结构(标准通用对象文件格式COFF)占位20字节。该结构当中记录了PE文件的全局属性详细如下图:

911429_5BBMVD33E6ZG43D

扩展PE头IMAGE_OPTIONAL_HEADER32

    该结构同样是定义了一些属性。详细如下图:

911429_G6GPV4JG8VWSDM5
911429_SRVMME6UTDUBNCZ

数据目录项IMAGE_DATA_DIRECTORY

    在扩展PE头数据结构当中最后一个元素“DataDirectory”定义了数据目录项。数据目录项本身也是一个数据结构。其中记录了不同类型的数据的目录信息。比如:导出表,导入表,资源,重定位表等。下面我们来看看具体结构:

911429_SEPVNMZ6J8G2UMK

节表项IMAGE_SECTION_HEADER

    PE头下面紧接着就是节表,节表当中记录着特定的节有关的信息。(节的属性,节的大小,在文件和内存中的起始位置)

    节表当中节的数量IMAGE_FILE_HEADER.NumberOfSections。下面我们来看看节表的数据结构的定义:

911429_2RPGFQJUBECDVNH

2;案例

    下面我们来打开一个应用程序来看看他们的PE结构

    注释:下面标注出来常用的内容

    首先我们来查看一下DOS头内容。着重查看“MZ标识”和“PE头位置”。我们可以在DOS MZ 数据结构的定义当中可以知道”MZ”标志位是第一个元素,并且类型属于WORD。占用两个字节。“PE头位置”定义在最后一个元素,类型是DWORD。占用四个字节。那么我们查看下图:

911429_XRDQUNYPM59FC3W

    DOS Strub 是存放一些DOS指令,这里我们不去查看它。既然我们通过DOS MZ数据结构的最后一个元素知道了PE头的地址(起始地址00 00 00 b0)那么现在我们跳转进入PE头来查看。

    我们都知道PE头的数据结构IMAGE_NT_HEADERS。当中定义了三个元素。DWORD类型元素Signature,数据结构类型IMAGE_FILE_HEADER(标准PE头),数据结构类型IMAGE_OPTIONAL_HEADER32(扩展PE头)。其中标准PE头占用20个字节,扩展PE头占用216个字节。下面我们来看看PE头的大体位置:

911429_9K5H5TQPE78DSWD

    注释:因为Intel兼容机采用的是小端法。所以存储在硬盘文件当中有效位放在最低位。举例:PE头标签在硬盘/(内存)存储位00004550h

    现在我们从图中大概可以了解了PE头文件大体的位置,那么现在我们逐步分析一下其中内容:

    PE头标签(IMAGE_NT_HEADER.Signature)的RVA地址为0000h,且为DWORD类型。所以占用四个字节。通过查看发现。该位置存储的内容为十六进制值为“00004550h” 

    标准PE头(IMAGE_NT_HEADER.FileHeader)的RVA地址为00004h,且它是一个数据结构,占位是20个字节。那么我们来看看该数据结构IMAGE_FILE_HEADER。

    IMAGE_FILE_HEADER.Machine 是标准PE头当中的第一个元素。它是一个WORD数据类型。表示PE文件的运行平台。从图中我们可以知道它的值为014Ch。它的具体含义如下表:

911429_W82RDBXEMCBD7FD

    IMAGE_FILE_HEADER.NumberOfSections 元素是一个WORD类型数据,且RVA的值为0006h。它表示的含义是PE中节的数量。我们来在图中查看一下:

911429_XDU6AZ7J8NX9DJW

    IMAGE_FILE_HEADER.TimeDateStamp 元素是一个DWORD数据类型,且RVA的值为0008h。它表示编译器创建此文件时的时间戳。值为“5F8D9A37h”如下查看

911429_YZSJYXPKP5FJH3F

下面我们在来看看IMAGE_FILE_HEADER.Characteristics 元素,它是一个单字数据类型。偏移量为0016h。表示为文件属性标志字段。它是用二进制的位为1表示具备对应位置的权限。详细如下表:

911429_VSR6KRPHV63UUYA
911429_VWAC89ZXBJCUY5S

    标准PE头之后紧接着就是扩展PE(IMAGE_OPTIONAL_HEADER32)头。同样对照着数据结构定义来寻找具体元素位置。这里我们着重来看看扩展PE头当中最后一个元素”DataDirectory”它同样也是一个数据结构。表示数据目录(IMAGE_DATA_DIRECTORY)

数据目录当中包含了不同类型的数据的目录信息。它是由16个IMAGE_DATA_DIRECTORY结构组成的。

    下图描述了数据目录的起始位置:

911429_QEZWA76KBHEHY7N

数据目录项紧接这就是节表的内容,之后就是节内容。下面就不继续查看。

二;PE结构_导入表

前言

    该文章第一部分表述导入表的概念,第二部分讲解导入表的结构,第三部分举例在PE文件当中找到导入表的内容。

    注意:该文章不涉及延迟加载导入表等内容

1;第一部分

    在讲解导入表之前,我们先想一想,在我们使用高级语言编写程序的时候是不是有个导入的操作。E.g Python 当中可以使用import 来导入其他库来使用库当中的函数。那么导入表是不是也是这样一个作用呢?答案是肯定的。导入表当中的数据就是指定了PE文件调用外来函数(这里外来函数是指不在本程序当中定义的函数)的数目,这些外来函数在哪些动态链接库当中等等。Windows 加载器在运行PE时会通过导入库将动态链接库一并加载到进程的地址空间当中。

2;第二部分

2.1;IMAGE_IMPORT_DESCRIPTOR

导入表是数据目录中注册的数据类型。描述信息位于数据目录的第2个目录项

导入表的当中每20个字节为一组数据结构,该数据结构名称为IMAGE_IMPORT_DESCRIPTOR(导入表描述符)详细内容如下图:

911429_FF68PSR5FBV58HG

关键字“union” 表示Characteristics 和 OriginalFirstThunk 任意一个元素。这个结构体一共5个元素。每个元素的数据类型“dd” 占用4个字节。(注释:元素Name1保存着导入动态链接库的名称)其中我们需要关注的是第一个元素OriginalFirstThunk 和 第5个元素FirstThunk 。它们指向了另外一个数据结构。其中元素OriginalFirstThunk 指向的数组称之为INT。元素FirstThunk 指向的数据结构称之为IAT。

2.2;INT

       数组INT当中的每一项均是一个结构,该结构名称为IMAGE_THUNK_DATA结构。如下图:

911429_WD68NBAEXWEG73A

       IMAGE_THUNK_DATA是一个双字,之后用双字“0”作为结束标志。

       其中IMAGE_THUNK_DATA当中的双字指向了另外一个结构体IMAGE_IMPORT_BY_NAME(即IMAGE_THUNK_DATA保存着通往IMAGE_IMPORT_BY_NAME的RVA)

       IMAGE_IMPORT_BY_NAME结构体定义如下:

911429_VHNQNRM9FEZVZM8

2.3;IAT

IAT (函数地址表)同样也是数据目录当中注册的数据类型,描述信息保存在数据目录项中第13个目录项。

       IAT的结构体为IMAGE_THUNK_DATA的数组

2.4;导入表,INT,IAT之间的关系

911429_YB8Y3HYZ9UM2A8G

       导入表当中第一个元素和第五个元素保存着通往INT和IAT 的偏移地址。在INT 以及IAT 当中保存在导入的动态链接库的信息。

3;第三部分

       接下来,我们用一个PE程序来查找一个PE文件的导入表的具体位置。

3.1;通过DOS头确定PE头的位置

       通过DOS MZ头的最后一个元素来定位PE头的位置:

注释:在硬盘文件当中,PE文件的地址都是从0开始计算的。

911429_YNPWVUGTMVGR5GK

3.2;定位PE扩展头的数据目录项位置

       导入表信息注册在PE扩展头当中的数据目录项的第二个位置当中。数据目录项的文件偏移地址为78h。所以在文件中数据目录项的地址为b0h + 78h = 128h。

911429_ZF54Z2NHVHTTZ6M

3.3;RVA转换为FOA

3.3.1;查看文件偏移

911429_M728B3TBSFU3PM8

3.3.2;计算导入表在文件当中的位置

FOA = 0000002010h – 0000002000h + 0000000600h = 610h

注释:计算方式参考文章:http://www.nvnv.xyz/newsinfo/804196.html

3.4;在硬盘文件当中查看导入表内容

       现在我们知道了导入表的地址,那么根据导入表描述符的定义,我们知道一个导入表当中的结构体占用了20个字节。导入表详细如下图:

911429_3PRBND43Z722D2R

3.5;重复3.2~3.4的内容,找到IAT内容

911429_8JWEYZEH27DQJN6

3.6;查看IMAGE_IMPORT_DESCRIPTOR第一个元素

IMAGE_IMPORT_DESCRIPTOR.OriginalFirstThunk,该元素有两种不同的解释:

1;双字最高位为0,表示导入符号是一个数值,该数值是一个RVA。

2;双字最高位为1,表示导入符号是名称。

       如图,我们查看到我们案例当中的最高位为0,那么该数值是一个RVA。

911429_86M68PSDZVWQ9NP

3.7;定位INT文件位置

       我们回顾一下INT是一个数组,其中元素大小为双字,以双字0为结束符。通过以知的RVA计算出FOA。FOA=654h详细如下图:

911429_XKK9YBY933PDQXS

3.8;定位IMAGE_IMPORT_BY_NAME

       INT当中每个双字元素,且保存着IMAGE_IMPORT_BY_NAME RVA地址。同样计算出FOA=65Ch。

IMAGE_IMPORT_BY_NAME.Hint

911429_6J86384XYRNJZEP

IMAGE_IMPORT_BY_NAME.Name1 大小不确定,表示函数名字符串名称 。以“”作为字符串结束标志。

911429_R5K4QXE79WUZCJM

       局部总结:截至目前,我们已经知道了。系统通过PE文件当中的PE扩展头最后一个元素数据目录项当中的第二个元素保存着导入表的RVA和数据库大小的信息。通过该导入表RVA可以定位到导入表的内容。导入表由多个导入表描述符IMAGE_IMPORT_DESCRIPTOR组成。每个导入表描述符由20个字节组成,最后由20个0字节作为结束符号。导入表描述符的第一个元素保存着一个RVA地址。该地址是指向了结构体IMAGE_THUNK_DATA。IMAGE_THUNK_DATA结构体本质上是一个双字的结构。该结构保存着结构体IMAGE_IMPORT_BY_NAME。这个结构体当中保存着需要导入函数的编号以及函数名称。

3.9;查看IMAGE_IMPORT_DESCRIPTOR.FirstThunk

       IMAGE_IMPORT_DESCRIPTOR.FirstThunk 是结构体当中最后一个元素,偏移地址为10h。详细位置如下图:

911429_JQGZM4H7GV2D6X3

       该元素是指定了IAT这个链表,它的值00002008h 表示RVA地址,计算出FOA地址608h。那么现在我们在IAT内容当中寻找该地址保存的内容是什么。如下图:很明显在IAT链表对应位置处保存的RVA地址为205Ch。计算FOA=65Ch。这个文件偏移地址当中对应的结构体为IMAGE_IMPORT_BY_NAME。 参考:3.8;定位IMAGE_IMPORT_BY_NAME。

911429_MQQC23BRBAQ93E6

     局部总结:INT与IAT两个链表,在硬盘文件的保存状态中,指定的最终都是结构体IMAGE_IMPORT_BY_NAME。这样的状态就是俗称的双桥结构

3.10;加载到内存中的IAT表

     由上面的研究可以知道,INT,以及IAT表最终指向的内容都是相同的,那么为什么这么做呢?事实上当PE文件加载进入内存当中运行的时候,IAT被PE加载器修改为导入函数的地址。那么,INT 当中保存着调用函数的名称或函数的索引编号。IAT保存函数指令代码的内存空间的地址。

三;PE结构_导出表

1;导出表作用

    导出表描述了导出表所在PE文件向其他程序提供的可供调用的函数的情况。说明这个问题首先我们先了解一下代码重用机制。该机制提供了重用代码的动态链接库。而导出表就向调用者说明库当中有哪些函数可以被调用。

2;导出目录IMAGE_EXPORT_DIRECTORY

       导出数据的第一个结构为IMAGE_EXPORT_DIRECTORY。定义结构如下:

911429_JKENWWM7BKAXEDS

       注释:在导入表当中的IMAGE_IMPORT_DESCRIPTOR个数与调用的动态链接库个数相等,然而导出表的IMAGE_EXPORT_DIRECTORY只有一个。

3;定位导出表

接下来,我们来看看在PE文件当中如何定位导出表。导出表定义在PE拓展头当中数据目录项当中第一个元素。

2.1;从DOS MZ头定位PE头

911429_XVYX2589GGWNRWZ.jpg

2.2;定位到数据目录项位置(数据目录项)FOA=78h。下图红框位置则是导出表的RVA地址(00 00 21 40h)以及数据长度(8Fh)

911429_FDK7G6RZ4EKVSNW

2.3;通过导出表的RVA地址计算出FOA地址。FOA地址为940h。在硬盘文件中定位。红框当中内容则是IMAGE_EXPORT_DIRECTORY内容。

911429_77HVZN73JPNAHAR

2.4;IMAGE_EXPORT_DIRECTORY.nNAME

记录导出表所在的文件的最初文件名。

911429_QJRBCT42CTF47ZU

2.5;IMAGE_EXPORT_DIRECTORY.NumberOfFunctions

定义了文件中导出函数的总个数

911429_YFDM6QQDYDMFER3

2.6;IMAGE_EXPORT_DIRECTORY.NumberOfNames

表示导出表中定义名字函数的个数。

911429_HNQXYUMK5WKDCAM

2.7;IMAGE_EXPORT_DIRECTORY.AddressOfFunctions

该指针指向了全部导出函数的入口地址的起始。(注释因为由IMAGE_EXPORT_DIRECTORY.NumberOfFunctions的值为4,则导出函数有四个。那么对应的导出函数地址有四个)

911429_PNBV5YH6HW4QWCM

2.8;IMAGE_EXPORT_DIRECTORY.nBase

导出函数编号的起始值。(注意:导出的函数的编号是由AddressOfFunctions开始的顺序号加上nBase的值)

911429_P7GNSX5YCP9WNE2

2.9;IMAGE_EXPORT_DIRECTORY.AddressOfNames

该值是一个指针,指向的位置是一连串的双字值。这些双字的值指向了定义函数名的函数的字符串地址。

911429_9CZCWYWENAD8UZM

2.10;IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals

该值也是一个指针,与AddressOfNames是一一对应关系(注释:AddressOfNames指向的是字符串的指针数组,AddressOfNameOrdinals指向了函数在AddressOfFunctions中的索引值。)索引是单字

911429_E2SAJFGHKAS6NQ9

四;PE结构_资源表

1;什么使资源?

       大多数Windows程序都包含图标。也有菜单这些组成部分。我们称这些组成部分为“资源”。程序常用的六类资源包括:位图资源,光标资源,图标资源,菜单资源,对话框资源,自定义资源。

2;资源表作用?

       资源表就是用来定位这些资源的具体位置的。

3;资源表结构?

       资源表采用类似操作系统当中文件管理方式来定位具体资源地址。通过不同的子目录来达到分类资源的目的。详细如下图

911429_M7C6W8UTX63WJAS

       由左往右来看,第一层目录是由结构体IMAGE_RESOURCE_DIRECTORY与数个结构体

IMAGE_RESOURCE_DIRECTORY_ENTRY。来组成一个“目录块”。第二层当中同样也是由结构体IMAGE_RESOURCE_DIRECTORY与数个结构体IMAGE_RESOURCE_DIRECTORY_ENTRY来组成这样的“目录”。这样的“目录”可能有多个。第三层是由结构体IMAGE_RESOURCE_DATA_NETRY组成。该结构体描述了资源文件的位置和大小。

4;IMAGE_RESOURCE_DIRECTORY

911429_FZ9PC5G2AFZ2TZT

5;IMAGE_RESOURCE_DIRECTORY_ENTRY

911429_HZXSS2EBVVEJNMS

       OffsetToData字段是一个指针,当最高位为1时,低位数据指向下一层目录块的起始地址;当最高位为0时,指针指向IMAGE_RESOURCE_DATA_ENTRY结构。

       注释: 将 Name 和 OffsetToData 用做指针时需要注意,该指针是从资源区块开始的地方算起的偏移量(即根目录的起始位置的偏移量),不是我们习惯的 RVA 。

6;IMAGE_RESOURCE_DATA_NETRY

911429_4Y9W5R8SV7KE96Z

7;IMAGE_RESOURCE_DIR_STRing_U

911429_6M6FH3FYRACMFMN

8;案例分析

8.1;定位数据目录项

911429_DRKX87YC33XE5TZ

8.2;定位资源表

文件偏移地址a00

911429_PNZQ8XZAH32Q9WG

8.3;资源表分析

8.3.1;第一层分析。

第一层结构下图:

911429_SJBFDX2HJ7CCBU5

       IMAGE_RESOURCE_DIRECTORY结构体共占用16个字节

911429_PCQB7G32NUP2JUT

       Characteristics

911429_PXT7B9UF2EP5WSX

       TimeDataStamp

911429_PXPYVRCEAZTABSU

       MajorVersion

911429_M43AGHYUJXSSJQ7

       MinorVersion

911429_FKX6RRVXZHJBAVM

       NumberOfNamedEntrise

911429_9FAYCYGAGNQKGY9

       NumberOfIdEntries

911429_AKGYUW4K5GWGAEN

       注释:NumberOfNamedEntrise和NumberOfIdEntries计算的和表示第一层当中有多少个

IMAGE_RESOURCE_DIRECTORY_ENTRY结构体。该案例有四个结构体。

       IMAGE_RESOURCE_DIRECTORY_ENTRY结构体

       Name

911429_ZJWN3EKAZN3YZRD

       OffsetToData

       该位置地址80000030h。8h=1000bit,最高位为1,则低位地址表示的是下一个目录项的地址。

911429_PDR6QBH9F42QKK8

       8.3.2;第二层分析

       由上面OffsetToData可知,最高位为1,则低位地址指向的是下一个目录项的地址。低位地址指向为30h (该地址并非RVA)

911429_6ZWSSP6MVZDGQQP

       接下来就是重复的分析

五;PE结构_重定位表

1;重定位表作用?

       我们知道PE结构当中PE扩展头当中的ImageBase当中定义了建议程序装载地址。那么如果该建议的地址已经被程序占用了呢?比如:动态链接库附加在程序当中使用。动态链接库导入进入程序的时候发现地址被占用了该怎么办?是不是需要换个地址装载。那么如何更换呢?这个行为就是重定位表所负责的。且重定位表属于数据目录项目表注册的数据类型。

       注释:大部分需要重定位的都是代码当中的绝对地址

2;重定位表项IMAGE_BASE_RELOCATION

911429_RNJXB6FJMTAFDKQ

       重定位表项类似于导入表,它不仅仅是一个。且重定位表项之间并不是相互紧紧连接的。它们之间存在着TypeOffset 这样一个WORD 数组。

3;TypeOffset

       TypeOffset本身是一个数组结构。它在PE文件当中的位置是存在重定位表项之间。该数组每项大小为两个字节(16bit)其中高4bit代表重定位的类型(详细如下图)。低12bit

是重定位地址。

911429_T75VEZG4P67MJBH

4;重定义表结构

911429_6DZMXGJYJ2XDHG6

5;重定位地址计算

       实际载入地址 = 基地址 + 代码起始页面 + 低十二位虚拟地址

       基地址 = 代码当中的起始地址

       代码起始页面 = IMAGE_BASE_RELOCATION.VirtualAddress

       低十二位虚拟地址 = TypeOffset 低十二位虚拟地址

6;案例

6.1;定位重定位表

911429_JAN6VTUJZQP256Y

6.2;重定位表内容

911429_TVBURU5QU8JFHYS

6.3;VirtualAddress

911429_ZUXB9D3GRQRXAZX

6.4;SizeOfBlock

911429_K2ZSWTB7KQDCB29

6.5;TypeOffset

911429_W5H6EEZYAVWQ2VQ

六;参考文献:

1;小甲鱼PE结构讲解系列

2; 逆向工程核心原理

3;加密与解密

4;WindowsPE权威指南

标签