11_资源

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 值.