目录 Table of Contents
11 资源
我们知道, windows 将程序的各种界面定义为资源, 包括加速键 (Accelerator), 位图 (Bitmap), 光标 (Cursor), 对话框 (Dialog Box), 图标 (Icon), 菜单 (Menu), 串表 (String Table), 工具栏 (Toolbar) 和版本信息 (Version Information) 等.
小实验 : 修改 360 压缩的窗口和图标
工具 : Resource Hacker, eXeScope
资源结构
资源是 PE 文件中非常重要的部分, 几乎所有的 PE 文件都包含着资源, 与导入表和导出表相比, 资源的组织方式要复杂很多, 看图就知道了 :
我们知道资源有很多类型, 每种类型的资源中可能存在多个资源项, 这些资源项用不同的 ID 进行区分. 但是要将这么多种类型的不同 ID 的资源有序地组织起来是一件非常痛苦的事情, 因此, 我们采取类似于磁盘目录结构的方式保存.
从图中我们可以看到, PE 文件中的资源是按照 "资源类型->资源 ID->资源代码页" 的 3 层树状结构来组织资源的, 通过层层索引才能够进入相应的子目录找到正确的资源.
资源目录结构
数据目录表中的 IMAGE_DIRECTORY_ENTRY_RESOURCE 条目 (第三项) 包含资源的 RVA 和大小. 资源目录结构中的每一个节点都是由 IMAGE_RESOURCE_DIRECTORY 结构和紧跟其后的数个 IMAGE_RESOURCE_DIRECTORY_ENTRY 结构组成的. (是不是很像我们之前提到的文件目录 ? 文件夹每个都长得一样, 一个嵌套另一个, 这样子可以实现将非常复杂的数据细化细分).
认识了这层关系后, 我们来看下 IMAGE_RESOURCE_DIRECTORY 这个结构, 该结构长度为 16 字节, 共有 6 个字段, 定义如下 :
IMAGE_RESOURCE_DIRECTORY struct
Characteristics dword ? ; 理论上为资源的属性, 不过事实上总是 0
TimeDateStamp dword ? ; 资源的产生时刻
MajorVersion word ? ; 理论上为资源的版本, 不过事实上总是 0
MinorVersion word ?
NumberOfNamedEntries word ? ; 以名称 (字符串) 命名的入口数量
NumberOfIdEntries word ? ; 以 ID (整型数字)命名的入口数量
IMAGE_RESOURCE_DIRECTORY ends
其实在这里边我们唯一要注意的就是 NumberOfNamedEntries 和 NumberOfIdEntries, 它们说明了本目录中目录项的数量. 两者加起来就是本目录中的目录项总和, 也就是后边跟着的 IMAGE_RESOURCE_DIRECTORY_ENTRY 数目.
资源目录入口的结构 (IMAGE_RESOURCE_DIRECTORY_ENTRY)
IMAGE_RESOURCE_DIRECTORY_ENTRY 紧跟在资源目录结构之后, 此结构长度为 8 个字节, 包含 2 个字段, 定义如下 :
IMAGE_RESOURCE_DIRECTORY_ENTRY struct
Name dword ? ; 目录项的名称字符串指针或者 ID
OffsetToData dword ? ; 目录项指针
IMAGE_RESOURCE_DIRECTORY_ENTRY ends
- Name 字段
Name 字段完全是个百变精灵, 该字段定义的是目录项的名称或 ID. 当结构用于第一层目录时, 定义的是资源类型; 当结构定义于第二层目录时, 定义的是资源的名称; 当结构用于第三层目录时, 定义的是代码页编号. 看上图.
注意 : 当最高位为 0 的时候, 表示字段的值作为 ID 使用; 当最高位为 1 的时候, 字段的低位作为指针使用 (资源名称字符串是用的 unicode 编码), 但是这个指针不是直接指向字符串, 而是指向一个 IMAGE_RESOURCE_DIR_STRING_U 结构.
IMAGE_RESOURCE_DIR_STRING_U 结构定义如下 :
IMAGE_RESOURCE_DIR_STRING_U struct
Length dword ? ; 字符串的长度
NameString dword ? ; unicode 字符串, 由于字符串是不定长的, 所以由 Length 制定长度
IMAGE_RESOURCE_DIR_STRING_U ends
- OffsetToData 字段
OffsetToData 字段是一个指针, 当最高位为 1 时, 低位数据指向下一层目录块的真实地址; 当最高位为 0 时, 指针指向 IMAGE_RESOURCE_DATA_ENTRY 结构.
注意 : 将 Name 和 OffsetToData 用作指针时需要注意, 该指针是从资源区块开始的地方算起的偏移量 (即根目录的起始位置的偏移量), 并不是我们习惯的 RVA 哦 !
最后, 在上图中我们看到, 在第一层的时候, IMAGE_RESOURCE_DIRECTORY_ENTRY 的 Name 字段作为资源类型使用, 具体类型匹配见下表 :
类型 ID 值 | 资源类型 |
---|---|
01H | 光标 |
02H | 位图 |
03H | 图标 |
04H | 菜单 |
05H | 对话框 |
07H | 字体目录 |
08H | 字体 |
09H | 加速键 |
0AH | 未格式资源 |
0BH | 消息表 |
0CH | 光标组 |
0DH | 图标组 |
0EH | 版本信息 |
资源数据入口
经过三层 IMAGE_RESOURCE_DIRECTORY_ENTRY (一般是 3 层, 偶尔更年期少一些.), 第三层目录结构中的 OffsetOfData 指向 IMAGE_RESOURCE_DATA_ENTRY 结构. 该结构描述了资源数据的位置和大小. 定义如下 :
IMAGE_RESOURCE_DATA_ENTRY struct
OffsetToData dword ? ; 资源数据的 RVA
Size dword ? ; 资源数据的长度
CodePage dword ? ; 代码页, 一般为 0
Reserved dword ? ; 保留字段
IMAGE_RESOURCE_DATA_ENTRY ends
千山万水, 此处的 IMAGE_RESOURCE_DATA_ENTRY 结构就是真正的资源数据了. 结构中的 OffsetToData 指向资源数据的指针, 其为 RVA 值.