《PE总结 》– 资源表

一、资源简介

资源是PE文件中非常重要的部分,几乎所有的PE文件中都包含资源,与导入表和导出表相比,资源的组织方式要复杂的多。

我们知道,Windows 将程序的各种界面定义为资源,包括加速键(Accelerator)、位图(Bitmap)、光标(Cursor)、对话框(Dialog Box)、图标(Icon)、菜单(Menu)、串表(String Table)、工具栏(Toolbar)和版本信息(Version Information)等。除此之外,还可以使用自定义的类型。每种类型的资源中可能存在多个资源项,这些资源项用不同的ID或者名称来分辨,在某个资源ID下,还可以同时存在不同代码页的版本。但是要将这么多种类型的不同ID 的资源有序地组织起来是一件非常痛苦的事情,因此,我们采取类似于磁盘目录结构的方式保存。

我们来看看PE中资源的组织方式:

image

建立如上的结构后,如果我们要查找某个资源的话,那么按照根目录->资源目录->资源ID->资源代码页这样的步骤一层层地进入相应的子目录并找到正确的资源。不过第三层目录防止的代码页“文件”不是资源本身而是一个用来描述资源的结构罢了,通过这个结构中的指针才能最后找到资源数据。

二、资源的组织方式

1.获取资源的位置

资源数据块的位置和大小可以从PE文件头中的IMAGE_OPTIONAL_HEADER32结构的数据目录字段中获取。在获取资源块的时候,注意不要使用查找“.rsrc”节起始地址的方法,虽然在一般情况下总是在“.rsrc”节中,但这并不是必然的。

2.资源目录

不管是根目录,还是第2层或第3层中的每个目录都是由一个IMAGE_RESOUCE_DIRECTORY结构和紧跟其后的数个IMAGE_RESOUCE_DIRECTORY_ENTRY结构组成的,这两种结构一起组成一个目录块。

IMAGE_RESOUCE_DIRECTORY的结构如下:

IMAGE_RESOURCE_DIRECTORY STRUCT

Characteristics DWORD ? ;理论上为资源的属性,不过事实上总是0
TimeDateStamp DWORD ? ;资源的产生时刻
MajorVersion WORD ? ;理论上为资源的版本,不过事实上总是0
MinorVersion WORD ?
NumberOfNamedEntries WORD ? ;以名称(字符串)命名的入口数量
NumberOfIdEntries WORD ? ;以ID(整型数字)命名的入口数量

IMAGE_RESOURCE_DIRECTORY ENDS

在这个结构中最重要的是最后两个字段,它们说明了本目录中目录项的数量,为什么有两个字段?

因为不管是资源种类,还是资源名称都可以用名称或ID两种方式来定义。

NameberOfNamedEntries字段的值是以字符串命名的资源数量,而NumberOfIdEntries 字段的值是以ID命名的资源数量。两个加起来才是本目录中的目录项总和,也就是当前IMAGE_RESOURCE_DIRECTORY结构后面紧跟着的IMAGE_RESOUCE_DIRECTORY_ENTRY结构的数量。

IMAGE_RESOURCE_DIRECTORY_ENTRY STRUCT

Name1 DWORD ? ;目录项的名称字符串指针或ID
OffsetToData DWORD ? ;目录项指针

IMAGE_RESOURCE_DIRECTORY_ENTRY ENDS

①Name1 字段完全是个百变精灵,该字段定义的是目录项的名称或ID。

当结构用于第一层目录时,定义的是资源类型;

当结构定义于第二层目录时,定义的是资源的名称;

当结构用于第三层目录时,定义的是代码页编号。

当最高位为 0 的时候,表示字段的值作为 ID 使用;而最高位为 1 的时候,字段的低位作为指针使用(资源名称字符串是使用 UNICODE编码),但是这个指针不是直接指向字符串哦,而是指向一个 IMAGE_RESOURCE_DIR_STRING_U 结构的。

IMAGE_RESOURCE_DIR_STRING_U STRUCT

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

IMAGE_RESOURCE_DIR_STRING_U ENDS

如果长度为100的时候,数据定义为:NameString  dw  100  dup(?)

如果想要得到ANSI类型的以0结尾的字符串,需要将NameString字段包含的UNICODE字符串用WideCharToMultiByte函数作个转换。

②OffsetOfData 字段是一个指针。

当最高位为 1 时,低位数据指向下一层目录块的起始地址;也就是一个IMAGE_RESOURCE_DIRECTORY,这种情况一般出现在第1层和第2层中。

当最高位为 0 时,指针指向 IMAGE_RESOURCE_DATA_ENTRY 结构,这种情况出现在第3层目录中。

当将Name1字段和OffsetToData用作指针时需要注意两点,首先是不要忘记将最高位清楚(使用0x7fffffff来and一下),其次就是这两个指针时从资源块开始的地方算起的偏移量,也就是根目录的起始位置算起的偏移量。不是RVA!!!

当IMAGE_RESOUCE_DIRECTORY_ENTRY用在第1层目录中的时候,它的Name1字段是作为资源类型来使用的。当资源类型以ID定义(最高位应等于0),并且ID数值在1-16之间时,表示这是系统预定义的类型,否则,表示这是一个自定义的类型。

image

3.资源数据入口

沿着资源目录树按照根目录->资源类型->资源ID的顺序到达第3层后,这一层目录的IMAGE_RESOUCE_DIRECTORY_ENTRY结构的OffsetData字段指向的是一个IMAGE_RESOURCE_DATA_ENTRY 结构。该结构描述了资源数据的位置和大小,定义如下:
IMAGE_RESOURCE_DATA_ENTRY STRUCT

OffsetToData DWORD ? ; 资源数据的RVA
Size1 DWORD ? ; 资源数据的长度
CodePage DWORD ? ; 代码页, 一般为0
Reserved DWORD ? ; 保留字段

IMAGE_RESOURCE_DATA_ENTRY ENDS

千山万水,此处的 IMAGE_RESOURCE_DATA_ENTRY 结构就是真正的资源数据了。结构中的OffsetOfData 指向资源数据的指针,其为 RVA 值。

本文链接:http://www.alonemonkey.com/pe-resource.html