对于需要对软件汉化,修改界面的这节内容挺重要
我们可以通过RVA定位到资源表的位置
资源定义
Windows 将程序的各种界面定义为资源,包括加速键(Accelerator)、位图(Bitmap)、光标(Cursor)、对话框(Dialog Box)、图标(Icon)、菜单(Menu)、串表(String Table)、工具栏(Toolbar)和版本信息(Version Information)等。
资源有很多种类型,每种类型的资源中可能存在多个资源项,这些资源项,用不同的ID 或名称来区分。但是,要将这么多种类型的不同ID 的资源有序地组织起来是一件非常痛苦的事情,因此采取类似磁盘目录结构的方式保存。
常用资源类型如下:
//资源类型名称映射表
typedef struct tagRES_ID_NAME_TABLE
{
LPSTR id;
char name[_MAX_PATH];
}RES_ID_NAME_TABLE;
static RES_ID_NAME_TABLE ResIdNameTable[0x17] = {
{0, "Unknown"},
{RT_CURSOR, "光标"},
{RT_BITMAP, "位图"},
{RT_ICON, "图标"},
{RT_MENU, "菜单"},
{RT_DIALOG, "对话框"},
{RT_STRING, "字符串表"},
{RT_FONTDIR, "字体目录"},
{RT_FONT, "字体"},
{RT_ACCELERATOR, "加速器表"},
{RT_RCDATA, "自定义资源"},
{RT_MESSAGETABLE, "消息表"},
{RT_GROUP_CURSOR, "图标组"},
{0, "Unknown"},
{RT_GROUP_ICON, "光标组"},
{0, "Unknown"},
{RT_VERSION, "版本信息"},
{RT_PLUGPLAY, "即插即用资源"},
{RT_VXD, "Vxd"},
{RT_ANICURSOR, "动态光标"},
{RT_ANIICON, "动态图标"},
{RT_HTML, "HTML文档"},
{RT_MANIFEST, "XPManifest"},
};
资源结构体解析
首先,资源结构体分很多,但是有用的就三个
IMAGE_RESOURCE_DIRECTORY 根目录(资源目录头)
IMAGE_RESOURCE_DIRECTORY_ENTRY 子目录(资源目录项)其中根目录下可以有很多子目录(也就是说根目录下会有子目录的)
IMAGE_RESOURCE_DATA_ENTRY 文件(资源数据)

PE中的资源存储
PE 文件中的资源是按照 :资源类型 -> 资源ID -> 资源代码页 的3层树型目录结构来组织资源的,通过层层索引才能够进入相应的子目录,找到正确的资源。
资源表位于数据目录表的第三项,共动态分配字节,其中结构体中的成员指出的RVA偏移量,都是对于此结构体的地址作为基地址。
资源在PE中是以目录的形式存在的,一般有3层:资源类型,资源ID与资源代码页 。
资源目录结构中的每一个节点都是由IMAGE_RESOURCE_DIRECTORY结构为头部,和紧跟其后的数个IMAGE_RESOURCE_DIRECTORY_ENTRY 结构组成的。
IMAGE_RESOURCE_DIRECTORY, 负责指出后面数组中的成员个数
IMAGE_RESOURCE_DIRECTORY_ENTRY, 数组成员分别指向下一层目录结构
资源目录表中的
IMAGE_DIRECTORY_ENTRY_RESOURCE 条目(第三项)包含资源的 RVA 和大小。
资源表的几个重要数据结构
资源表的结构,实际上资源树的层次通常是:资源类型->资源ID->语言ID-> DataEntry->资源数据。
IMAGE_RESOURCE_DIRECTORY结构
资源目录头 IMAGE_RESOURCE_DIRECTORY (16 bytes) ,可以看做是管理文件的根目录。
我们把它简称为 dir,它表示一个目录,其后跟了多个dir entry,每个dir entry都是一个索引,它们都是目录的一部分。
IMAGE_RESOURCE_DIRECTORY 这个结构描述的是这个目录的信息,我们可以知道后面有多少个 dir entry。在16进制编辑器里,dir正好占据一个整行。
typedef struct _IMAGE_RESOURCE_DIRECTORY
{
DWORD Characteristics; //资源属性,不过事实上总是0
DWORD TimeDateStamp; //资源的产生时刻
WORD MajorVersion; //理论上为资源的版本,不过事实上总是0
WORD MinorVersion
WORD NumberOfNamedEntries; //以名称(字符串)命名的入口数量
WORD NumberOfIdEntries; //以ID(整型数字)命名的入口数量
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
首先我们看到资源目录头的结构体了,这里我们唯一要注意的就最后两个成员。
WORD NumberOfNamedEntries; 这是用户自定义资源类型的个数。
WORD NumberOfIdEntries; 这是典型资源例如位图,图标,对话框等资源类型的个数。
上面这两个值 说明了本目录中,目录项的数量。 它们加在一起就是 dir 后面紧跟的 dir entry 的个数。就是本目录中的目录项总和。也就是后边跟着的
IMAGE_RESOURCE_DIRECTORY_ENTRY 数目。
NumberOfNamedEntries 按照名称命名的数量,意思就是我们的资源是字符串命名加载的有多少个。
NumberOfIdEntries 按照ID命名的数量,意思就是我们的资源如果按照ID有多少个。
一般都是用ID的。
最后两个字段主要是资源的标识,是以ID的有多少个,以字符串标识的有多少个。
第一层
第一层起始于一个IMAGE_RESOURCE_DIRECTORY头,后面紧接着是IMAGE_RESOURCE_DIRECTORY_ENTRY数组。
数组个数 = NumberOfNamedEntries+NumberOfIdEntries 。
IMAGE_RESOURCE_DIRECTORY_ENTRY 使用的是Name与OffsetToDirectory,分别代表了资源类型与第二层的数据偏移地址。
在第一层的时候,IMAGE_RESOURCE_DIRECTORY_ENTRY 的Name 字段作为资源类型使用。
具体类型匹配见下表:

OffsetToDirectory数据偏移地址是相对整个资源结构来说的,也就是说首个第一层的起始偏移地址加上OffsetToDirectory就是第二层的偏移地址。
IMAGE_RESOURCE_DIRECTORY_ENTRY结构
称为资源目录项,或子目录。
我们把它简称为 dir entry,它紧跟在dir的后面,它代表了资源树上一个节点,节点本身的信息来自它的第一个成员(指向一个名称字符串或者本身就是一个ID),它更重要的信息是包含了一个偏移量(它的第二个成员),指向一个data entry 或者 dir。因此它颇类似一个链表中的节点的作用。在16进制编辑器里,每一行是两个dir entry。
IMAGE_RESOURCE_DIRECTORY_ENTRY (8 bytes)紧跟在资源目录结构后,此结构长度为 8 个字节,包含 2 个字段。该结构定义如下:
typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
union {
struct {
DWORD NameOffset : 31; //资源名偏移
DWORD NameIsString : 1; //资源名为字符串
};
DWORD Name; //资源/语言类型
DWORD Id; //资源数字ID
};
union {
DWORD OffsetToData; //数据偏移地址
struct {
DWORD OffsetToDirectory : 31; //子目录偏移地址
DWORD DataIsDirectory : 1; //数据为目录
};
};
} IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;
1、首先,它是联合体,有8个字节大小。
2、其中第一个DWORD大小,看高位,如果高位是1,那么低31位是指向新的目录项名称的结构体 IMAGE_RESOURCE_DIR_STRING_U
3、如果高位为0,则是ID号,这个ID号说的是 资源ID类型,比如3类型指的就是ICON,ID数值在1-16之间时,表示这是系统预定义的类型,否则,表示这是一个自定义的类型。
4、第二个DWORD量,也是RVA偏移,如果高位为1那么代表它还是一个目录,也就是指向了一个新的根目录了,如果不是,则指向文件偏移结构体了。
DWORD Name / Id; 第一个成员,取决于最高位的值。如果是用户定义的名称,它是一个偏移,
指向的是 IMAGE_RESOURCE_DIR_STRING_U。如果是一个 ID 号,那么它直接就是 ID 号本身。
Name 字段是个通用字段,字段定义的是目录项的名称或ID。
当结构用于第一层目录时,定义的是资源类型;
当结构用于第二层目录时,定义的是资源的名称;
当结构用于第三层目录时,定义的是代码页编号。
DWORD offsetToData / offsetToDirectory; 第二个成员,是一个偏移量,指向该name或者Id 节点的 data entry 或者下一级 dir。
这两个成员的具体含义都是由它们的最高位是 1 还是 0 而决定的。
Name DWORD 目录项的名称字符串指针或ID
OffsetToData DWORD 目录项指针
注意两点:
(1)资源名称是以长度为前导的unicode字符串。
(2)只有 data entry 中的 offset 是RVA,其他成员中的offset 都是距离资源表的偏移。
第二层
第二层从 一个IMAGE_RESOURCE_DIRECTORY头开始,后面紧接着是IMAGE_RESOURCE_DIRECTORY_ENTRY数组。
数组个数=NumberOfNamedEntries+NumberOfIdEntries。
IMAGE_RESOURCE_DIRECTORY_ENTRY 其中是 OffsetToDirectory与第一层一样,代表了第三层的数据偏移地址 。
如果NameIsString=1,说明该资源是以(UNICODE编码的)名称 定义的,NameOffset是名称的相对整个资源结构的偏移地址。相反,如果NameIsString=0,说明该资源以(整型数字) ID定义的。
NameOffset相对地址指向的是IMAGE_RESOURCE_DIR_STRING_U结构体,该结构体定义如下:
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;
IMAGE_RESOURCE_DIR_STRING_U:(长度不固定) // 表示的是一个Unicode字符串。
WORD Length; // 这个字符串的字符长度。
WCHAR NameString[]; // Unicode字符串的内容。
第三层
IMAGE_RESOUCE_DIRECTORY_ENTRY 结构体的个数为1个
第一个联合体已经不是标识的意思了,整个四个字节这个资源是什么语言。
第二个联合体最高位DataIsDirectory为0,说明联合体表示的是数据,由OffsetToData会得到一个结构体 IMAGE_RESOURCE_DATA_ENTRY
IMAGE_RESOURCE_DATA_ENTRY真正的资源数据
资源数据结构体,文件偏移结构体
资源数据入口经过三层 IAMGE_RESOURCE_DIRECTORY_ENTRY 查找,它们是第一层资源类型,第二层资源名,第三层是资源的 Language,到达第三层目录结构中 的IMAGE_RESOURCE_DATA_ENTRY 结构。它就是真正的资源数据了。结构中的OffsetToData指向资源数据的指针,其为 RVA 值。
IMAGE_RESOURCE_DATA_ENTRY :(16 bytes)简称 data entry,它表示叶子节点,不能再向下扩展。它指向一个资源的实际数据。
该结构描述了资源数据的位置和大小,定义如下:
typedef struct _IMAGE_RESOURCE_DATA_ENTRY {
DWORD OffsetToData; //资源数据的RVA,注意这是资源数据的RVA。(而非偏移量)
DWORD Size; //资源数据的尺寸(bytes)。
DWORD CodePage; //代码页, 一般为0,没什么用。
DWORD Reserved; //保留字段
} IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;
共 16 字节,这个结构的前两个成员有用处。
本文详细介绍了PE文件的资源表结构,包括资源定义、资源结构体解析和PE中的资源存储。资源以类似磁盘目录的方式组织,如光标、位图、图标等,并通过IMAGE_RESOURCE_DIRECTORY、IMAGE_RESOURCE_DIRECTORY_ENTRY等结构体进行层次管理。资源类型、资源ID和资源代码页构成3层树型目录结构,通过层层索引找到资源。资源表位于数据目录表的第三项,包含资源类型、资源ID、语言ID和数据入口。

1521

被折叠的 条评论
为什么被折叠?



