PE文件资源表

本文详细介绍了PE文件的资源表结构,包括资源定义、资源结构体解析和PE中的资源存储。资源以类似磁盘目录的方式组织,如光标、位图、图标等,并通过IMAGE_RESOURCE_DIRECTORY、IMAGE_RESOURCE_DIRECTORY_ENTRY等结构体进行层次管理。资源类型、资源ID和资源代码页构成3层树型目录结构,通过层层索引找到资源。资源表位于数据目录表的第三项,包含资源类型、资源ID、语言ID和数据入口。

AI 时代程序员必备技能

Codex、Claude Code、Cursor、Hermes Agent、OpenClaw等工程化实战专栏 ,讲透 AI 如何接管脏活累活

对于需要对软件汉化,修改界面的这节内容挺重要

我们可以通过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 字节,这个结构的前两个成员有用处。

 

 

 

AI 时代程序员必备技能

Codex、Claude Code、Cursor、Hermes Agent、OpenClaw等工程化实战专栏 ,讲透 AI 如何接管脏活累活

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值