PE文件解析(四)
资源表
资源表的结构相对复杂,采用了类似磁盘目录结构的方式保存,通常目录有3层,第1层目录类似于一个文件系统的根目录,指出了整个资源表中有多少种不同的类型(如光标、菜单、快捷键等);第2层目录指明了当前类型的资源中,有多少个资源;而第3层目录被称为资源代码页
为了便于理解,先给出一张资源表的树形结构图

资源目录结构
1 | typedef struct _IMAGE_RESOURCE_DIRECTORY { |
其中最关键的属性是NumberOfNamedEntries和NumberOfIdEntries,他们指出了本目录种目录项的总和(即资源类型的总个数)
资源目录入口结构
1 | typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY { |
可以看到其中有两个union结构,我们可以简单的理解为,资源目录入口结构包含两个属性Name(ID)、OffsetToData,根据情况的不同,他们有不同的含义
- Name(ID)属性:定义了目录项的名称或ID,当最高位(NameIsString)为0时,表示该属性通过ID使用;为1时,表示该属性通过Name使用,且资源名称使用Unicode编码,低31位为Name的Offset,但NameOffset并不直接指向字符串,而是指向一个IMAGE_RESOURCE_DIR_STRING_U结构,该结构如下所示
- 当用于第一层目录时,定义的是资源的类型
- 当用于第二层目录时,定义的是资源的名称
- 当用于第三层目录时,定义的时代码页的编号
1 | typedef struct _IMAGE_RESOURCE_DIR_STRING_U { |
- OffsetToData属性:一个指针,当最高位为1时,低31位指向下一层IMAGE_RESOURCE_DIRECTORY的起始地址;当最高位为0时,指向IMAGE_RESOURCE_DATA_ENTRY结构
当NameOffset和OffsetToData作为Offset使用时,该指针从资源区块的开始处计算偏移,即它们并不是RVA
当IMAGE_RESOURCE_DIRECTORY_ENTRY用在第一层的目录时,其Name(ID)属性用于标记资源类型,而Windows有14种预定义的类型,且用ID标识,ID的数值在1到16之间
| ID值 | 资源类型 |
|---|---|
| 01h | 光标(Cursor) |
| 02h | 位图(Bitmap) |
| 03h | 图标(Icon) |
| 04h | 菜单(Menu) |
| 05h | 对话框(Dialog) |
| 06h | 字符串(String) |
| 07h | 字体目录(Front Directory) |
| 08h | 字体(Front) |
| 09h | 快捷键(Accelerators) |
| 0Ah | 未格式化资源(Unformatted) |
| 0Bh | 消息表(Message Table) |
| 0Ch | 光标组(Group Cursor) |
| 0Eh | 图标组(Group Icon) |
| 10h | 版本信息(Version Information) |
资源数据入口结构
1 | typedef struct _IMAGE_RESOURCE_DATA_ENTRY { |
该结构就是真正的资源数据的结构了,其中OffsetToData是RVA,而不是相对于资源块起始位置的Offset
实例
我这里用一个Win32程序为实例,先用PE Tools查看其资源表内容

根目录
可以看到其中共有7种不同的资源类(24好像是清单类型,暂时每找到相关文档信息),首先在16进制编辑器中观察其根目录

- 4字节Characteristics,此时为0
- 4字节TimeDateStamp,为0
- 2字节MajorVersion,为0
- 2字节MinorVersion,为0
- 2字节NumberOfNamedEntries,为0
- 2字节NumberOfIdEntries,为7
根目录下的IMAGE_RESOURCE_DIRECTORY_ENTRY
我们以Menu作为分析对象,它是第二个IMAGE_RESOURCE_DIRECTORY_ENTRY结构,因为该结构大小位8字节,所以它的位置应该是2400h + 8 = 2408h,我们在16进制编辑器中可以看到如下内容

可以看到其Name(ID)属性值为:0x00000004,其OffsetToData属性值为0x800000E8,我们进一步解析
- Name(ID)属性的最高位为0,表示它通过ID标识,由于是第一层目录中,它代表一个预定义的资源,查表我们发现04h是Menu
- OffsetToData属性的最高位为1,表示它指向下一层目录,其Offset是E8h,那么下一层目录的地址为2400h + E8h = 24E8h
第二层目录的IMAGE_RESOURCE_DIRECTORY
第二层目录如下所示

可以知道其NumberOfIdEntries为1,表示Menu资源下只有一个该类资源
紧跟其后的8字节就是IMAGE_RESOURCE_DIRECTORY_ENTRY结构,可以看到其Name(ID)属性值为:0x0000006D,其OffsetToData属性值为0x80003030
- Name(ID)属性的最高位为0,表示它通过ID标识,ID为6Dh,即十进制的109
- OffsetToData属性的最高位为1,表示它指向下一层目录,其Offset是330h,那么下一层目录的地址为2400h + 330h = 2730h
第三层目录的IMAGE_RESOURCE_DIRECTORY

可以知道其NumberOfIdEntries为1,表示该资源代码页只有一项
紧跟其后就是IMAGE_RESOURCE_DIRECTORY_ENTRY结构

可以看到其Name(ID)属性值为:0x00000804,其OffsetToData属性值为0x000004F8
- Name(ID)属性的最高位为0,表示它通过ID标识,ID为804h
- OffsetToData属性的最高位为0,表示它指向IMAGE_RESOURCE_DATA_ENTRY结构,其Offset是4F8h,那么该结构的地址为2400h + 4F8h = 28F8h
IMAGE_RESOURCE_DATA_ENTRY

- OffsetToData为01AE90h
- Size为50h
- CodePage为0
- Reserved为0
IMAGE_RESOURCE_DIR_STRING_U
因为示例程序比较简单,没有用到这个结构,所以这里单独找一个比较复杂的程序用来展示

可以看到根目录下第一类资源是用Name标识的,其名字为”AFX_DIALOG_LAYOUT”,我们在16进制中观察其IMAGE_RESOURCE_DIRECTORY_ENTRY

可以看到其Name(ID)属性值为:0x8000CB2E,其OffsetToData属性值为0x800000B0
- Name(ID)属性的最高位为1,表示它通过Name标识,且Name的Offset为CB2Eh,那么IMAGE_RESOURCE_DIR_STRING_U结构的地址为630000h + CB2Eh = 63CB2Eh
- OffsetToData属性的最高位为1,表示它指向下一层目录

可以看到IMAGE_RESOURCE_DIR_STRING_U结构中Length为11h,而NameString为Unicode表示的”AFX_DIALOG_LAYOUT”(可以注意到字符串并没有以0结尾,所以Length属性很重要)