码迷,mamicode.com
首页 > Windows程序 > 详细

win PE

时间:2015-04-27 09:31:19      阅读:318      评论:0      收藏:0      [点我收藏+]

标签:

 

学习Win  PE结构笔记

 

 

 

 

 

windows PE 文件是指windows系统中的一种文件格式,它包括 .exe, .dll, .sys, .ocx, .cpl, .scr, .drv, .efi, .fon ....                

在了解PE文件结构以前,大家应该先明白下面几个名词的概念:

 

 

1 虚拟地址(VA

windows中每一个进程加载到内存中,都会有一个的4GB大小的虚拟内存地址空间,在这片虚拟的空间中,任何一个位置的地址,都称之为虚拟地址(VA)。

 

2 相对虚拟地址(RVA

每一个PE文件加载到内存中,都会有一个加载基址,相对虚拟地址(RVA)就是相对于加载基址的偏移量。

公式:虚拟地址(VA) = 加载基址(ImageBase)+ 相对虚拟地址(RVA);

PE文件会有一个默认的加载基址,是扩展头的第十个数据成员ImageBase(映像基址)。当这个位置被其他PE文件占用,就会加载到别地方。

 

3 文件偏移(Offset

文件偏移是用于指定文件中的某个位置(未加载到内存中,在磁盘文件中的位置),是指在磁盘中相对于文件起始的偏移量。

 

4 对齐的概

PE中的SectionAligment 字段与FileAligment字段描述了此映像文件的内存对齐大小与文件对齐大小。

根据上面两个字段,系统再给某一个区段上的数据块分配空间时,都会根据其对齐大小的整数倍来分配空间。

文件对齐和内存对齐的概念,举例来说一个体积为0x1150的区段,如果它的内存对齐大小为0x1000(4KB),文件对齐大小为0x200(512B),

那么这个区段在内存中实际占的大小就是0x2000,其中0x1150是有意义的,剩余的用零填充。在文件中实际占用的大小是0x1200,其中0x1150,是有意义的,剩下的用零填充。

 

 

 

 

下面是比较常见的节表的名称和作用,在分析的过程中如果遇到不在下表中的一些节表名称,这种现象说明这个文件是十分可疑的。

 

 

节名

描述

.text

代码段,里面的数据全都是代码

.data

可读写的数据段,存放全局变量或静态变量

.rdata

只读数据区

 .idata

导入数据区,存放导入表信息

.edata

导出数据区,导出表信息

.rsrc

资源区段,存放程序用到的所有资源,如图表,菜单等

.bss

未初始化数据区

.crt

用于支持C++运行时库所添加的数据

.tls

存储线程局部变量

.reloc

包含重定位信息

.sdata

包含相对于可被全局指针定位的可读写数据

.srdata

包含相对于可被全局指针定位的只读数据

.pdata

包含异常表

.debug$S

包含OBJ文件中的Codeview格式符号

.debug$T

包含OBJ文件中的Codeview格式类型的符号

.debug$P

包含使用预编译头时的一些信息

.drectve

包含编译时的一些链接命令

.didat

包含延迟装入的数据

 

 

 

 

 

 

 

 

 

 

 

 

PE文件由以下几个结构顺序构成:

 

DOS头部                                                        //大小为64(0x40)

DOS Stub                                                       //为兼容DOS程序而设立.

NT头部                                                           //存储PE文件的全部属性,初始化信息

区段头表(也叫节表,因为区段也可以叫做节)               //对于PE主体文件属性的分段描述,个数不定.

各个区段(节)(代码,数据,重定位等等)                   //PE文件的主体,分段存储着可执行的代码,各种数据,资源等( .rdata .reloc .text .rsrc ...)

接下来,分别介绍各个部分,每一个部分,都有一些常用到的重要成员,把这些常用的成员位置记住,基本就算熟悉了PE文件结构。

 

DOS:

typedef struct _IMAGE_DOS_HEADER {     

    WORD   e_magic;                                   // [0x00] Magic number(重要,是否为PE文件的第一个标志)

    WORD   e_cblp;                                     // [0x02]Bytes on last page of file

    WORD   e_cp;                                        // [0x04]Pages in file

    WORD   e_crlc;                                      // [0x06]Relocations

    WORD   e_cparhdr;                                // [0x08]Size of header in paragraphs

    WORD   e_minalloc;                               //[0x0A] Minimum extra paragraphs needed

    WORD   e_maxalloc;                              //[0x0C] Maximum extra paragraphs needed

    WORD   e_ss;                                       // [0x0E]Initial (relative) SS value

    WORD   e_sp;                                       // [0x10]Initial SP value

    WORD   e_csum;                                   // [0x12]Checksum

    WORD   e_ip;                                        //[0x14] Initial IP value

    WORD   e_cs;                                        //[0x16] Initial (relative) CS value

    WORD   e_lfarlc;                                   // [0x18]File address of relocation table

    WORD   e_ovno;                                    // [0x1A]Overlay number

    WORD   e_res[4];                                  // [0x1C]Reserved words

    WORD   e_oemid;                                  // [0x24]OEM identifier (for e_oeminfo)

    WORD   e_oeminfo;                                //[0x26] OEM information; e_oemid specific

    WORD   e_res2[10];                               // [0x28]Reserved words

    LONG   e_lfanew;                                   // [0x3C]File address of new exe header(有用,PE解析时用它找到PE头的位置

  } IMAGE_DOS_HEADER, * PIMAGE_DOS_HEADER;

补充:

1 PE 文件的第一个结构便是这个结构体.

2 重要的是第一个和最后一个,也就是e_magic与e_lfanew这两个数据成员。

3 e_magic,翻译为魔数,其实它就是一个标记,DOS头标志位,其值恒为4D5A,在系统中用宏定义为:IMAGE_DOS_SIGNATURE

4 e_lfanew,它表示NT头部在文件中的偏移.

5 其余大多已经没有什么用了,其内容通常为0居多.

 

 

 

DOS Stub:

在DOS头部与NT头部之间有一部分区域,存储着一些被dos头使用的数据.包括一些提示字符串等等,这部分的大小不太确定,所以,NT头的具体位置要由DOS头的最后一个成员e_lfanwe确定.

 

NT头部:

typedef struct _IMAGE_NT_HEADERS {

    DWORD Signature;                                                  //标记(重要,判断是否为PE文件的第二个标志

    IMAGE_FILE_HEADER FileHeader;                              //文件头(重要,存储着PE文件的基本信息

    IMAGE_OPTIONAL_HEADER32 OptionalHeader;           //扩展头(重要,存储着关于PE文件时加载的信息

} IMAGE_NT_HEADERS32, * PIMAGE_NT_HEADERS32;

补充:

1 NT头由一个简单的标记,一个不太复杂的文件头和一个比较复杂的扩展头组成.

2 如果是PE文件,标记的值恒为0x00004550, ASCII为PE00,,在系统中用宏定义为:IMAGE_NT_SIGNATURE

3 另外两个成员是两个结构体,里面存储的信息非常有用,可以说解析这两个结构是PE解析真正的开始。

 

 

文件头:

NT头的第二个成员,是一个结构体,可以称为文件头,存储着一些关于这个PE文件的信息.

typedef struct _IMAGE_FILE_HEADER {

    WORD      Machine;                         1  (文件的运行平台)

    WORD      NumberOfSections;      2  (区段的数量

    DWORD   TimeDateStamp;               3  (文件创建时间)

    DWORD   PointerToSymbolTable;      4  (符号表偏移,用于调试)

    DWORD   NumberOfSymbols;            5   (符号个数,用于调试)

    WORD    SizeOfOptionalHeader;     6   (扩展头的大小

    WORD    Characteristics;                 7   PE文件的一些属性

} IMAGE_FILE_HEADER, * PIMAGE_FILE_HEADER;

补充:

1 关于Machine: 这个文件可以运行在哪一个CPU平台.常见的  0x014c 代表 CPU 型号为Intel 386, 0x0200  代表 CPU 型号为Intel 64.

2 NumberOfSections:区段的个数,也就是PE文件的主体被分成了多少个部分,一般有代码,只读数据,数据,重定位等区段(节)。

3 TimeDateStamp:表明文件是何时被创建的,但是这个数据是一个非常大的32位的数值,具体解析这个数据可以使用函数

struct tm *gmtime( 

   const time_t *timer 

);

将TimeDateStamp的地址强制转换后作为参数,之后使用一个tm结构体接收就可以得到具体时间了.

4 PointerToSymbolTable:指向符号表,用于调试.

5 SizeOfOptionalHeader:扩展头的大小,32位系统中一般是224(0x00E0),在64位系统中一般是240(0x00F0),扩展头大小其实是不太确定的,因为不排除人为的修改.

6 Characteristics:PE文件属性值,可以用来判断文件类型。有几个需要知道:Dll一般是0x0210,EXE一般是0x010F.win32 SDK 中的winNT.h定义一组宏,来表示不同的文件属性.

 

 

 

 

 

扩展头

typedef struct _IMAGE_OPTIONAL_HEADER {

    //

    // Standard fields.

    //

    WORD    Magic;                                 1    文件类型标识,32位一般是0x010B,64位的PE文件一般是0x020B,还有0x0170,代表ROM镜像。

    BYTE    MajorLinkerVersion;                    2    连接器主版本

    BYTE    MinorLinkerVersion;                    3    连接器次版本

    DWORD   SizeOfCode;                            4   (重要)指所有代码区段(节)的总大

    DWORD   SizeOfInitializedData;                 5    已初始化数据的总大小

    DWORD   SizeOfUninitializedData;               6    未初始化数据的总大小,在磁盘中不占用空间,在加载进内存之后,会预留这么大的空间。一般存储在.bss区段中。

    DWORD   AddressOfEntryPoint;                   7   (重要)程序开始执行的相对虚拟地址(RVA),也叫OEPOrginal Entry Point ,源入口点

    DWORD   BaseOfCode;                            8   (重要)起始代码的相对虚拟地址(RVA),一般这个值为0x00001000.

    DWORD   BaseOfData;                            9    起始数据的相对虚拟地址(RVA)

    //

    // NT additional fields.

    //

    DWORD   ImageBase;                             10  (重要)默认加载基址(如果没有加载到这个地址,会发生重定位.)

    DWORD   SectionAlignment;                      11  (重要)块对齐数,就是在映射到内存中的区段(节)对齐,这个数必须大于文件对齐数,一般是0x1000

    DWORD   FileAlignment;                         12  (重要)文件对齐数,就是在硬盘中的文件的区段(节)对齐,一般是0x200

    WORD    MajorOperatingSystemVersion;           13   主操作系统版本号

    WORD    MinorOperatingSystemVersion;           14   次操作系统版本号

    WORD    MajorImageVersion;                     15   主映像版本

    WORD    MinorImageVersion;                     16   次映像版本

    WORD    MajorSubsystemVersion;                 17   主子系统版本

    WORD    MinorSubsystemVersion;                 18   次子系统版本

    DWORD   Win32VersionValue;                     19   保留值,一般是0

    DWORD   SizeOfImage;                           20  (重要)要把文件加载进内存,所需要的内存大小,注意是进行了块对齐之

    DWORD   SizeOfHeaders;                         21   所有头部大小,Dos头、PE头、区段表的尺寸之和

    DWORD   CheckSum;                              22   校验和(一般无用)对于驱动和一些系统dll来说需要校验(使用IMAGEHLP.DLL中的CheckSumMappedFile API)

    WORD    Subsystem;                             23  (重要)子系统

    WORD    DllCharacteristics ;                   24  (重要)指示Dll特征的标志,DllMain()函数何时被调用,默认为0.

    DWORD   SizeOfStackReserve;                    25   初始化时栈的大小

    DWORD   SizeOfStackCommit;                     26   初始化时实际提交的栈的大小

    DWORD   SizeOfHeapReserve;                     27   初始化时保留的堆的大小

    DWORD   SizeOfHeapCommit;                      28   初始化时实际提交的堆的大小

    DWORD   LoaderFlags;                           29   与调试相关  

    DWORD   NumberOfRvaAndSizes;                   30   数据目录的个数,也就是下面那个数组中元素的个数。

    IMAGE_DATA_DIRECTORY DataDirectory[ IMAGE_NUMBEROF_DIRECTORY_ENTRIES];   31  (非常重要)数据目录

} IMAGE_OPTIONAL_HEADER32, * PIMAGE_OPTIONAL_HEADER32;

说明:

1 一共有31个成员,重要的大概有10个。另外有一个极度重要的数据目录。

2 扩展头是属于NT头部的第三部分,紧随文件头结构之后,存储着加载文件时的一些初始化信息,32位系统中扩展头大小一般是224(0x00E0),在64位系统中一般是240(0x00F0)。

3  IMAGE_NUMBEROF_DIRECTORY_ENTRIES  这是个宏定义,值是0x10,表示一般情况下有16个数据目录.

这个是数据目录的定义

typedef struct _IMAGE_DATA_DIRECTORY {

    DWORD   VirtualAddress;     // 数据的相对虚拟地址(RVA

    DWORD   Size;               // 数据的大

} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

 

它包含了这段数据存储的相对虚拟地址(RVA)以及这段数据的大小,所以数据目录表只是一个引导,只是帮助我们找到这段数据,这里列出数据目录的宏与名称。

 

IMAGE_DIRECTORY_ENTRY_EXPORT           0

 

导出表(IMAGE_EXPORT_DIRECTORY结构)。

 

IMAGE_DIRECTORY_ENTRY_IMPORT           1

 

导入表(IMAGE_IMPORT_DESCRIPTOR结构数组)。

 

IMAGE_DIRECTORY_ENTRY_RESOURCE       2

 

资源表(IMAGE_RESOURCE_DIRECTORY结构)。

 

IMAGE_DIRECTORY_ENTRY_EXCEPTION      3

 

异常处理程序表(IMAGE_RUNTIME_FUNCTION_ENTRY结构数组)

 

IMAGE_DIRECTORY_ENTRY_SECURITY        4

 

安全目录表,一般情况用于保存数字签名或安全证书。

 

IMAGE_DIRECTORY_ENTRY_BASERELOC      5 

 

基址重定位信息。

 

IMAGE_DIRECTORY_ENTRY_DEBUG              6

 

索引调试信息

 

IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7

 

版权 

 

IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8

 

全局指针目录。用在64位平台

 

IMAGE_DIRECTORY_ENTRY_TLS                  9

 

指向线程局部存储(Thread Local Storage)初始化节。

 

IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG  10

 

载入配置

 

IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT  11

 

绑定输入目录

 

IMAGE_DIRECTORY_ENTRY_IAT                  12

 

导入地址表

 

IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13

 

延迟载入描述

 

IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR  14 

 

COM信息

 

 

最后还有一个全零的保留目录,也就是数组长度是16.这是一般的情况之下,文件是可以拥有更多数据目录的。这个时候,扩展头的大小也不是E0了。

至此,NT头的内容就结束了,之后是区段头表.

 

 

 

区段头表:

 

再回忆PE文件结构:1 DOS头   2 DOS头用的数据   3 NT头(包括文件头与扩展头) 4  区段(节)头表   5 各个区段(节).

区段是不需要我们直接解析的地方,也就说,区段头表是我们直接探索的最后位置了。

区段头表存储着PE文件主体的一些属性,区段头表是由若干个结构体依次排列组成,每一个结构体代表着PE文件主体中一段数据的属性,也就是每一个区段头都对应着PE文件主体的一段数据,这段数据叫做区段或者节,区段头规定了区段(节)的属性。

区段头结构

typedef struct _IMAGE_SECTION_HEADER {

    BYTE    Name[ IMAGE_SIZEOF_SHORT_NAME];                   1         //区段的名字,如.text  .reloc  .rdata

    union {

            DWORD   PhysicalAddress;

            DWORD   VirtualSize;                              2 (重要)//这个区段在虚拟内存中会使用的总大小,没有经过对齐

    } Misc;                                                   

    DWORD   VirtualAddress;                                   3 (重要)//这个区段起始的相对虚拟地址(RVA

    DWORD   SizeOfRawData;                                    4 (重要)//区段在文件中的大小,这个值进行了文件对齐

    DWORD   PointerToRawData;                                 5 (重要)//区段的文件偏移 RVA Offset 时用

    DWORD   PointerToRelocations;                             6         //区段的重定位信息的文件偏移。在OBJ文件中才有用

    DWORD   PointerToLinenumbers;                             7         //COFF行号信息的文件偏移.

    WORD    NumberOfRelocations;                              8         //PointerToRelocations域指向的重定位信息的数目。在OBJ文件中才有用

    WORD    NumberOers;                                       9         //PointerToLinenumbers域指向的行号信息的数目。只有当生成COFF行号信息时才使用。

    DWORD   Characteristics;                                  10 (重要)//区段的属性,这个在文件被载入的时候意义比较重大.

} IMAGE_SECTION_HEADER, * PIMAGE_SECTION_HEADER;

补充:

1 区段头表是由多个这个结构体构成,以一个全是0的结构体结尾

 

2 关于区段名字的规矩是这样的:

.text                     一般是代码段,这个是非常重要的

.data段                   一般是数据段

.bss段                    表示未初始化的数据,比如static变量,可能是在进入一个函数的时候才被初始化的

.rdata段                  表示只读的数据,比如字符串,

.textbss段                和代码有关,不是很清楚做什么用的

.idata和edata             存储导入表和导出表的信息。

.rsrc段                   存储资源的区段(节)

.relcoc段                 存储重定位信息的区段(节)

 

3 Characteristics:       区段(节)的属性。属性的具体值,参考MSDN.

 

4 这个结构的大小是40(0x28)。

 

5 虽然区段头表就在NT头的后面,系统提供了一个宏来方便的找到它的位置:IMAGE_FIRST_SECTION( pNTHeader ),参数是NT头的指针。

 

6 相对虚拟地址(RVA)与文件偏移(Offectset)的转换

这是解析数据目录表的基础

这里提供转换的方法:我们把需要转换的RVA,在下面的公式中定义成 RVA(转)

RVA(转),一定是在某个区段中,通过循环遍历上面的结构体中成员变量,如果RVA(转)符合大于 VirtualAddress(区段起始的相对虚拟地址RVA

小于 (VirtualAddress + Misc.VirtualSize).就是在这个区段中,这样得到了这个区段的起始RVA和区段起始Offset.通过下面的公式就可以得到转换后的文件偏移了

公式:Offect  =  RVA(转) -  RVA(区段起始)+Offect(区段起始

 

 

 

 

导出表

概述:

1)导出是指这个PE文件所导出的供其他PE文件使用的函数,变量,或者类的行为。

2)每一个导出的函数(变量,类),都有一个唯一的序号与之对应,有的情况下,会没有函数名(变量,类),但是会有函数(变量,类)地址和序号。可以通过序号调用这样的函数。

3)由上一条可知,导出表包括   1 函数(变量,类)地址   2 序号   3 函数(变量,类)名

4)导出表,是根据数据目录表的第一个数组元素中的相对虚拟地址,再通过刚刚说过的相对虚拟地址转换文件偏移的方式,可以方便的找到它。

下面是导出表的数据结构

typedef struct _IMAGE_EXPORT_DIRECTORY {

    DWORD    Characteristics;                  1        保留值,恒为0

    DWORD   TimeDateStamp;                     2        时间,和文件头中的时间一样的

    WORD      MajorVersion;                    3        主版本号

    WORD      MinorVersion;                    4        次版本号

    DWORD   Name;                              5(重要)本PE文件的名字,也就是谁导出的这些函数(变量,类

    DWORD   Base;                              6(重要)序号基

    DWORD   NumberOfFunctions;                 7(重要)函数数

    DWORD   NumberOfNames;                     8(重要)函数名称数

    DWORD   AddressOfFunctions;                9(重要)函数地址表的相对虚拟地址RVA  

    DWORD   AddressOfNames;                   10(重要)函数名称表的相对虚拟地址RVA

    DWORD   AddressOfNameOrdinals;            11(重要)序号表的相对虚拟地址RVA   

} IMAGE_EXPORT_DIRECTORY, * PIMAGE_EXPORT_DIRECTORY;

补充:

1) 导出表应该被安排在.edata中,不过这个段一般都会合并到.rdata中。

2) 有一个序号基数,通过序号表得到的序号再加上这个序号基数才是真正的函数序号。

3) 最后三个成员,通过这三个相对虚拟地址,转换为文件偏移后,可以方便的找到函数地址表,函数名称表,序号表。

4) 函数名称表,存储的是函数名称的相对虚拟地址,再一次的转换为文件偏移后才能使用,这一点在解析的时候要注意。

5) 导出的函数地址表,序号表,函数名表的关系:

     5.1序号不是按顺序排列的 

     5.2序号与函数名是一一对应的,一一对应的意思是,两个表中相同位置的元素相对应。这也说明了结构体中为什么没有序号的数量,因为序号的数量和函数名数量是一样的。

     5.3序号可能不是连续的,比如可能会没有1 ,有2,没有5,有6,但这并不代表缺失的这个序号没有所对应的函数地址

     5.4函数个数会比函数名个数多,多出来的这些函数,可能是用序号导出的函数,也可能是一个无效函数,地址填充0

     5.5序号表元素的值,对应着函数地表的位置,那一个位置中的函数地址,是这个序号所对应的函数名的函数地址。由此,三个表联系起来。这条很重要。

     5.6函数地址表中元素,有地址值,但是序号表中没有序号与之对应,说明这是一个序号导出函数,没有函数名,他的序号就是它自己在函数地址表中的位置。(当然这个位置加上序号基数才是它真正的序号)

     5.7函数地址表中元素,填充为0,说明这是一个无效的函数,也不会有序号和函数名与之对应。

下面这个图指出了一个比较混乱的导出表结构,通过这个混乱的结构能更好的理解导出表。

                       技术分享

可以看出,序号的基数为X,导出表中的函数地址表中的第一个函数,函数名表和序号表中没有相应的值和它对应的,它是由序号导出的。

 

导入表

概述

1)导入表是根据数据目录表的第二个元素找到的。找出的方法与找导出表相同。

2)导入是这个PE文件在运行时,需要别的PE文件给予的支持。导入表存储的是从其他PE文件导入过来的函数名,序号。在加载到内存之后,会存储这些函数的地址。

3)由于一个PE文件可能会需要多个PE文件的支持,所以导入表结构一般有多个,就是说导入表其实是一个结构体数组,以一个全零元素为结尾,每一个数组的元素,代表一个PE文件的导入信息。

下面是导入表的数据结构:

 

typedef struct _IMAGE_IMPORT_DESCRIPTOR {

    union {

        DWORD   Characteristics;                 

        DWORD   OriginalFirstThunk;          1 (重要)指向一个结构体数组的相对虚拟地址(RVA,结构体数组叫输入名称表(INTImport Name Table

    } DUMMYUNIONNAME; 

    DWORD   TimeDateStamp;                   2  时间标志

    DWORD   ForwarderChain;                  3  转发机制用到

    DWORD   Name;                            4(重要)导入的PE文件的名字的相对虚拟地址RVA

    DWORD   FirstThunk;                      5 (重要) 指向一个结构体数组的相对虚拟地址(RVA),结构体数组叫做输入地址表(IATImport Address Table

} IMAGE_IMPORT_DESCRIPTOR, * PIMAGE_IMPORT_DESCRIPTOR;

 

typedef struct _IMAGE_THUNK_DATA32 {

    union {

        DWORD ForwarderString;  //转发用到

        DWORD Function;             //导入函数的地址,在加载到内存后,这里才起作用 

        DWORD Ordinal;              //假如是序号导入的,用到这里

        DWORD AddressOfData;   //假如是函数名导入的,用到这里,它指向一个PIMAGE_IMPORT_BY_NAME结构体

    } u1;

} IMAGE_THUNK_DATA32;

1 在磁盘文件中,起作用的只有后两个成员

2 这个结构占据4个字节,假如最高位为1,那么序号导入起作用,只需输出一个序号,假如最高位为0,那么是最后一个成员其作用,指向一个PIMAGE_IMPORT_BY_NAME,判断最高位是否为1可以使用系统提供的宏IMAGE_SNAP_BY_ORDINAL32(),参数就是这个结构体。

typedef struct _IMAGE_IMPORT_BY_NAME {

    WORD    Hint;

    CHAR   Name[1];

} IMAGE_IMPORT_BY_NAME, * PIMAGE_IMPORT_BY_NAME;

这个结构包含了序号和函数名。

下面这张图表示导入表的双桥结构:
  技术分享

1 OriginalFirstThunk 与  FirstThunk 指向的是相同类型的结构体IMAGE_THUNK_DATA32

2 在磁盘文件中OriginalFirstThunk与FirstThunk中的数据是相同的,可以将输入名称表(INT)看成是输入地址表(IAT)的一个备份。在加载到内存中之后,输入地址表会由PE加载器把相应PE文件的函数地址覆盖到这里来。这时,输入地址表才是真正的输入地址表。

 

  

 

重定位:

1)什么是重定位?

重定位就是你本来这个程序理论上要占据这个地址,但是由于某种原因,这个地址现在不能让你霸占,你必须转移到别的地址,这就需要基址重定位。由于每个进程都有自己独立的虚拟地址空间,既然都是自己的,怎么会被占据呢?其实对于EXE应用程序来说,是这样的。但是动态链接库就不一样了,动态链接库都是寄居在别的应用程序的空间的,所以出现要载入的基地址被应用程序占据了也是很正常的,这时它就不得不进行重定位了。

2)重定位表是根据数据目录表的第6个元素。

 

3)重定位表也是一个结构体数组,以全零元素结尾,每一个数组元素描述了4KB大小的区域的重定位信息。

以下是重定位表的数据结构

typedef struct _IMAGE_BASE_RELOCATION {

    DWORD   VirtualAddress;               1(重要)需要重定位内存页的起始位置(RVA

    DWORD   SizeOfBlock;                   2 (重要)这个结构体(算上TypeOffset)的大

//  WORD     TypeOffset[1];               3 (重要)一个特殊的数据,存放的是相对于第一个元素描述的位置的偏

} IMAGE_BASE_RELOCATION;

说明:

1 这是一个特殊的结构,第三个成员并不真正是这个结构体的成员,他紧随在结构体之后,是一个不定多长的数组,第一个成员描述的是某个区段中第一个需要重定位开始。

2 那我们如何知道这个区域中有多少个需要重定位的位置呢?就要根据第二个成员,它的大小是这个结构体大小与后面的TypeOffset数组的总大小。可以推出,重定位个数等于总大小减去结构体大小,再除以2。

公式描述:需重定位个数   n = (SizeOfBlock-8)/ 2;

3 第三个成员的高4位,描述的是一个属性。低12位描述的才是一个偏移。如下图所示:

 技术分享

4 当基址重定位发生的时候,用第一个成员是虚拟基址(VA),依次加上偏移(第三个成员的后12位),就能得到存储全局变量地址的相对虚拟地址,也就找到了这个地方,再根据第三个成员高四位描述的属性,对其进行重定位操作。

 

资源表:

概述:

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

 

2 资源表有三层结构。每一层都以一个IMAGE_RESOURCE_DIRECTORY开头,之后跟数个IMAGE_RESOURCE_DIRECTORY_ENTRY结构,可以说每一层由一个IMAGE_RESOURCE_DIRECTORY结构与一个IMAGE_RESOURCE_DIRECTORY_ENTRY结构体数组组成,这个结构体数组元素的个数由之前的结构给出,所以要注意:这个结构体数组不是以一个全零元素为结尾了,在解析的时候要注意。

3 资源表是根据数据目录表的第3个元素找到的,它找到的是资源结构的第一层。

4 理解三层目录结构,第一层告诉你有几种资源。每种资源叫什么,第二层告诉你这一种资源有多少个,每个资源叫什么,第三层告诉你一个具体资源在文件的什么位置,注意区分多少种资源和多少个资源。

如图所示: 

 技术分享

 

下面分别查看两个结构体

typedef struct _IMAGE_RESOURCE_DIRECTORY {

    DWORD    Characteristics;                  1属性,一般填0

    DWORD    TimeDateStamp;                2时间,一般填0

    WORD      MajorVersion;                    3 主版本号

    WORD      MinorVersion;                    4 次版本号

    WORD      NumberOfNamedEntries;    5 (重要)用字符串作为资源标识的条目个数

    WORD      NumberOfIdEntries;           6 (重要) 用数字ID作为资源标识的条目个数

//  IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[];

} IMAGE_RESOURCE_DIRECTORY, * PIMAGE_RESOURCE_DIRECTORY;

说明

其实在这里边我们唯一要注意的就是 NameberOfNamedEntries 和 NumberOfIdEntries,它们说明了本目录中目录项的数量。两者加起来就是本目录中的目录项总和。

也就是后边跟着的IMAGE_RESOURCE_DIRECTORY_ENTRY 数目。

下面是IMAGE_RESOURCE_DIRECTORY_ENTRY结构。

typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY STRUCT

{

    DWORD      Name;             1//目录项的名称字符串指针或ID

    DWORD      OffsetOfData;  2//目录项指针

 

}IMAGE_RESOURCE_DIRECTORY_ENTRY

 

1 .Name 字段是联合体,当最高位为 0  的时候,表示字段的值作为 ID 使用;而最高位为 1 的时候,字段的低位作为指针使用(资源名称字符串是使用 UNICODE编码),但是这个指针不是直接指向字符串哦,而是指向一个 IMAGE_RESOURCE_DIR_STRING_U 结构的。当结构用于第一层目录时,定义的是资源类型;当结构定义于第二层目录时,定义的是资源的名称;当结构用于第三层目录时,定义的是代码页编号。

该结构定义如下:

typedef struct _IMAGE_RESOURCE_DIR_STRING_U STRUCT

{

   DWORD   Length ; 字符串的长度

   DWORD   NameString    ; UNICODE字符串,由于字符串是不定长的。由Length 制定长度

}IMAGE_RESOURCE_DIR_STRING_U

 

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

 

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

 

3下面讲一下常规情况下的三层结构

3.1第一层:

 通过数据目录表的第3个元素找到这里,首先遇到的是那个IMAGE_RESOURCE_DIRECTORY结构,最后两个成员的和会告诉你后面有多少种需要解析的资源

 如果这种资源是已知的,那么这种资源属于按序号作为资源标识,Name元素最高位为0,这个时候整个四个字节代表着已知资源的类型, 这个表能说明不同的数字,代表的资源类型。

 技术分享

如果这种资源是未知的,那么这种资源属于字符串作为资源标识,这时联合体的最高位为1, OffseToData指明了一个结构体IMAGE_RESOURCE_DIR_STRING_U的位置。结构  体中保存着标识字符串。

typedef struct _IMAGE_RESOURCE_DIR_STRING_U

 {

    WORD    Length;                字符串的长度

    WCHAR   NameString[ 1 ] ; UNICODE字符串,由于字符串是不定长的。由Length 制定长度

} IMAGE_RESOURCE_DIR_STRING_U, * PIMAGE_RESOURCE_DIR_STRING_U;

第二个成员是这种资源类型的名字。第一个成员是这个名字的长度。需要注意的是这个名字不是以0结尾的。有个长度给你,解析的时候不要越界。

当OffseToData最高位为 1 时,指针指向 IMAGE_RESOURCE_DATA_ENTRY 结构。这个地方就是第二层。

 

 

3.2第二层 

通过第一层找到这里,首先遇到的是那个IMAGE_RESOURCE_DIRECTORY结构,最后两个成员的和会告诉你后面有多少个需要解析的资源

第一个成员,通过上一层的理解,其实这一层也很好理解了。当Name成员最高位是0的时候,说明这个资源的标识是一个数字,其实一些对话框,控件的ID值就是这个数字。如果Name成员最高位是1,说明这个资源的标识是字符串,

 

第二个成员的OffsetToData成员最高位为1,说明这个联合体表示的地方是一个目录,会带你去寻找这个资源的具体在地方,这个地方就是第三层。

3.3第三层

第一个联合体已经不是标识的意思了。整个四个字节代表的是这个资源是什么语言的。

第二个联合体的OffsetToData成员会为0,说明这个联合体表示的地方是一个数据,它指出了资源具体的位置。由OffsetToData会得到一个结构体IMAGE_RESOURCE_DATA_ENTRY 

typedef struct _IMAGE_RESOURCE_DATA_ENTRY {

    DWORD   OffsetToData;        //(重要)资源的偏移,注意这是一个相对虚拟地址(RVA

    DWORD   Size;                     //  ( 重要)资源的大

    DWORD   CodePage;             //  ( 重要)资源页属

    DWORD   Reserved;              //  保留,一般是0

} IMAGE_RESOURCE_DATA_ENTRY, * PIMAGE_RESOURCE_DATA_ENTRY;

比较重要第一个成员和第二个成员,一个是资源的偏移,另一个是资源的大小。

 

3.4 以上为常规情况下,不常规的情况下,可能第二层的时候,OffsetToData成员就为0了,直接指出了资源的位置。可能是因为不需要知道这个资源是什么语言类型。

3.5 但是最后得到的结构体中数据的偏移OffsetToData是一个相对虚拟地址(RVA),找到它需要转换文件偏移。

 

 

声明:以上资料图片参考(黑客免杀攻防 任晓珲  Windows PE 权威指南 戚利   鱼C工作室 小甲鱼)

 

win PE

标签:

原文地址:http://www.cnblogs.com/hekkoav/p/4424091.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!