PE(可执行文件格式)资源表是 Windows 操作系统中用于存储和管理可执行文件中各种资源的一种结构。它是一个层次化的资源目录,通过索引和标识符来查找和访问资源。
PE 资源表包含了多种类型的资源,例如位图、图标、光标、对话框、字符串、版本信息等。每个资源都有一个唯一的标识符和类型,用于在资源表中定位和识别。本节我们将讲述资源表的数据结构以及如何定位资源表。资源表的RVA地址和大小位于数据目录项第2项。
本节必须掌握的知识点:
资源表结构解析
资源表定位
7.2.1 资源表结构解析
■PE资源表的结构如下:
●资源目录表(Resource Directory Table):资源目录表是资源表的顶层结构,它包含了指向各个资源类型的目录项的指针。
●资源类型目录表(Resource Type Directory Table):资源类型目录表包含了每个资源类型的目录项,例如位图、图标、对话框等。
●资源名称/标识符目录表(Resource Name/Identifier Directory Table):资源名称/标识符目录表包含了每个资源类型下的资源名称或标识符的目录项。
●资源数据目录表(Resource Data Directory Table):资源数据目录表包含了每个资源的实际数据的位置和大小。
通过 PE 资源表,操作系统和应用程序可以轻松地访问和加载可执行文件中的资源。开发人员可以使用资源编辑器或编程工具来创建、编辑和管理 PE 资源表中的资源。
在程序中,可以使用 Win32 API 函数(如 FindResource、LoadResource)来加载和访问 PE 资源表中的资源,以满足程序的需求并提供更丰富的用户界面和功能。
●三级目录结构
PE的资源组织方式类似于操作系统的文件管理方式。从根目录开始,下设一级子目录、 二级子目录和三级子目录;三级子目录下才是文件。其三级目录结构如图7-1所示。
图7-1 三级目录结构
1.一级子目录按照资源类型分类,如”光标” 一级子目录、“位图” 一级子目录、“菜单” 一级子目录、“字符串” 一级子目录、“加速键” 一级子目录等多个资源类型。
2.二级子目录按照资源的ID分类。例如,同样是“菜单” 一级子目录的内容,其下可以有: IDM_OPEN的ID号为2001,IDM_EXIT的ID号为2002,IDM1的ID号为4000等多个菜单项。
3.三级子目录是按照资源的代码页分类,即不同的语言代码页对应不同的数据。其中,根据语言可以分为简体中文、英文、繁体中文等多个代码页。
4.三级目录后即为节点,也就是所说的“文件”。这里的“文件”其实就是包含了资源数据的指针和大小等信息的一个数据结构而已。对所有资源数据块的访问均可从这里开始。
●资源目录结构单元
由于一、二、三级目录的数据结构是相同的,均是由一个资源目录头加上多个线性跟随 着的资源目录项组成,我们可以将主干和枝干的节点称为资源目录结构单元,如图7-2所示。
图7-2 资源目录结构单元
从数据结构角度来看,资源表是一个四层的二叉排序树结构。其中,第一层为主干,第二、三层为枝干,叶子节点为第四层。主干和枝干的节点即为资源目录结构单元,完整示意见图7-3。
图7-3 资源表的二叉树结构
资源表的结构虽然比较复杂,但并不难理解。下一节我们将分析资源表在PE文件中的详细定义和定位。
■资源目录头
资源表数据从第一级资源目录开始。资源的每一级目录都会有一个资源目录头,它标识 了该类资源的属性、创建日期和版本等信息,其中也包含了随后的目录项的数量描述信息。 详细结构定义如下:
typedef struct _IMAGE_RESOURCE_DIRECTORY {
DWORD Characteristics; // 资源目录的特性标志,通常为 0
DWORD TimeDateStamp; // 资源目录的时间戳,表示目录的创建或修改时间
WORD MajorVersion; // 资源目录的主版本号
WORD MinorVersion; // 资源目录的次版本号
WORD NumberOfNamedEntries; // 命名目录项的数量
WORD NumberOfIdEntries; // 标识符目录项的数量
// Array of IMAGE_RESOURCE_DIRECTORY_ENTRY structures
// followed by resource directory entries (subdirectories or leaf nodes)
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
IMAGE_RESOURCE_DIRECTORY 结构中的字段的含义如下:
1.Characteristics:资源目录的特性标志,通常为 0。该字段目前保留,未使用。
2.TimeDateStamp:资源目录的时间戳,表示目录的创建或修改时间。该字段用于跟踪资源目录的变化。
3.MajorVersion 和 MinorVersion:资源目录的版本号,用于指示资源目录的版本信息。这些版本号可以用于识别资源目录的格式和兼容性。
4.NumberOfNamedEntries:资源目录中的命名目录项的数量。命名目录项是根据名称进行索引的子目录或叶节点。
5.NumberOfIdEntries:资源目录中的标识符目录项的数量。标识符目录项是根据标识符进行索引的子目录或叶节点。
IMAGE_RESOURCE_DIRECTORY 结构后面紧跟着一个由 IMAGE_RESOURCE_DIRECTORY_ENTRY 结构组成的数组,用于表示资源目录的子目录或叶节点。每个 IMAGE_RESOURCE_DIRECTORY_ENTRY 结构表示一个子目录或叶节点,并包含了子目录或叶节点的偏移地址或资源标识符。
通过 IMAGE_RESOURCE_DIRECTORY 的层次结构,可以遍历和访问 PE 资源表中的不同类型的资源以及它们的子目录。这样,操作系统和应用程序可以根据需要加载和使用所需的资源。
■资源目录项
IMAGE_RESOURCE_DIRECTORY_ENTRY 是 PE(可执行文件格式)中资源表中的一个目录项,用于表示资源目录的子目录或叶节点。
以下是 IMAGE_RESOURCE_DIRECTORY_ENTRY 的定义:
typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
union { //目录项的名字、字符串指针或ID号
struct {
DWORD NameOffset : 31; // 命名目录项的名称偏移地址
DWORD NameIsString : 1; // 标志,指示命名目录项是否为字符串
} DUMMYSTRUCTNAME;
DWORD Name; // 标识符目录项的资源标识符
WORD Id; // 标识符目录项的整数标识符
} DUMMYUNIONNAME;
union {
DWORD OffsetToData; // 子目录或叶节点的偏移地址
struct {
DWORD OffsetToDirectory : 31; // 子目录的偏移地址
DWORD DataIsDirectory : 1; // 标志,指示目录项是否是子目录
} DUMMYSTRUCTNAME2;
} DUMMYUNIONNAME2;
} IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;
IMAGE_RESOURCE_DIRECTORY_ENTRY 结构包含了以下几个重要的字段:
1.NameOffset:命名目录项的名称偏移地址。如果 NameIsString 标志位为 1,则该字段表示命名目录项的名称在资源表字符串表中的偏移地址;如果 NameIsString 标志位为 0,则该字段表示命名目录项的资源标识符ID的低 31 位。
2.NameIsString:标志位,指示命名目录项是否为字符串。如果该标志位为 1,则 NameOffset 字段表示命名目录项的名称在资源表字符串表中的偏移地址。表示命名目录项使用的是字符串名称,而不是整数标识符。此时,NameOffset 字段指向的是一个 IMAGE_RESOURCE_DIR_STRING_U 结构,其中存储了字符串的长度和内容。
以下是 IMAGE_RESOURCE_DIR_STRING_U 结构的定义:
typedef struct _IMAGE_RESOURCE_DIR_STRING_U {
WORD Length; // 字符串长度(以字节为单位)
WCHAR NameString[1]; // 字符串内容
} IMAGE_RESOURCE_DIR_STRING_U, *PIMAGE_RESOURCE_DIR_STRING_U;
3.Name:标识符目录项的资源标识符。如果 NameIsString 标志位为 0,则该字段表示标识符目录项的资源标识符。
4.Id:标识符目录项的整数标识符。如果 NameIsString 标志位为 1,则该字段表示标识符目录项的整数标识符。
5.OffsetToData:子目录或叶节点的偏移地址。如果 DataIsDirectory 标志位为 0,则该字段表示叶节点的偏移地址;如果 DataIsDirectory 标志位为 1,则该字段表示子目录的偏移地址。
6.OffsetToDirectory:子目录的偏移地址。如果 DataIsDirectory 标志位为 1,则该字段表示子目录的偏移地址。
7.DataIsDirectory:标志位,指示目录项是否是子目录。如果该标志位为 1,则 OffsetToData 字段表示子目录的偏移地址。
通过 IMAGE_RESOURCE_DIRECTORY_ENTRY 结构的字段,可以确定目录项是指向子目录还是叶节点,并获取相应的偏移地址或资源标识符。这样,可以在资源表中导航和访问不同类型的资源。
图7-4 资源目录及目录项之间的关系
■资源数据项
IMAGE_RESOURCE_DATA_ENTRY 是 PE(可执行文件格式)中资源表中的一个数据项,用于表示资源的实际数据。
以下是 IMAGE_RESOURCE_DATA_ENTRY 的定义:
typedef struct _IMAGE_RESOURCE_DATA_ENTRY {
DWORD OffsetToData; // 数据的偏移地址
DWORD Size; // 数据的大小
DWORD CodePage; // 数据的代码页
DWORD Reserved; // 保留字段,一般为 0
} IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;
IMAGE_RESOURCE_DATA_ENTRY 结构包含了以下几个字段:
1.OffsetToData:数据的偏移地址。该字段指示了资源数据在 PE 文件中的偏移位置。
2.Size:数据的大小,以字节为单位。该字段表示了资源数据的实际大小。
3.CodePage:数据的代码页。该字段指示了资源数据所使用的字符编码。
4.Reserved:保留字段,一般为 0。
通过解析 IMAGE_RESOURCE_DATA_ENTRY 结构,可以获取资源数据的偏移地址、大小和代码页信息。这些信息可以用于在 PE 文件中定位和提取资源数据。
在资源表中,IMAGE_RESOURCE_DIRECTORY_ENTRY 的 OffsetToData 字段指向一个 IMAGE_RESOURCE_DATA_ENTRY 结构,该结构描述了资源数据的位置和相关属性。通过这个结构,可以按需加载和使用 PE 文件中的资源数据。
图7-5 资源数据块定位中的资源数据项
提示
32位PE和64位PE文件中的资源表完全相同。
7.2.2 资源表定位
实验四十八:定位资源表