WIN32汇编语言教程:第17章 PE文件 · 17.4 资源(1)
资源是PE文件中非常重要的部分,几乎所有的PE文件中都包含资源,与导入表和导出表相比,资源的组织方式要复杂得多,读者只要看看图17.8中的示意图,就知道笔者所言不虚。
图17.8 PE文件中资源的组织方式
如果一开始就扎进一堆与资源相关的数据结构中去分析各字段的含义,恐怕会越来越糊涂,要了解资源的话,重在理解资源整体上的组织结构。
我们知道,PE文件资源中的内容包括光标、图标、位图、菜单等十几种标准的类型,除此之外还可以使用自定义的类型(这些类型的资源在第05章中已经有所介绍)。每种类型的资源中可能存在多个资源项,这些资源项用不同的ID或者名称来分辨,在某个资源ID下,还可以同时存在不同代码页的版本。
要将这么多种类型的不同ID的资源有序地组织起来,用类似于磁盘目录结构的方式是很不错的。打个比方,假如在磁盘的根目录下按照类型建立若干个第2层目录,目录名是“光标”、“图标”、“位图”和“菜单”等,就可以将各种资源分类放入这些目录中,假设现在有n个光标,那么在“光标”目录中再以光标ID为名建立n个第3层子目录,进入某个子目录后,再以代码页为名称建立不同文件,这样所有的资源就按照树型目录的方式组织起来了。现在要查找某个资源的话,那么按照根目录→资源类型→资源ID→资源代码页这样的步骤一层层地进入相应的子目录并找到正确的资源。
如图17.8所示,PE文件中组织资源的方式与上面的构思及其相似,正是按照根目录→资源类型→资源ID的3层树型目录结构来组织资源的,只不过在第3层目录中放置的代码页“文件”不是资源本身而是一个用来描述资源的结构罢了,通过这个结构中的指针才能最后找到资源数据。
1. 获取资源的位置
资源数据块的位置和大小可以从PE文件头中的IMAGE_OPTIONAL_HEADER32结构的数据目录字段中获取,与资源对应的项目是数据目录中的第3个IMAGE_DATA_DIRECTORY结构(如表17.4所示),从这个结构的VirtualAddress字段得到的就是资源块地址的RVA值。
如图17.8中的A所示,从数据目录表中得到的资源块的起始地址就是资源根目录的起始地址,从这里开始就可以一层层地找到资源的所有信息了。
在获取资源块地址的时候,注意不要使用查找“.rsrc”节起始地址的方法,虽然在一般情况下资源总是在“.rsrc”节中,但这并不是必然的。
2. 资源目录
好了,现在继续深入一步,资源目录树的根目录地址已经得到了,那么整个目录树上的目录是如何描述的呢?注意图17.8左下角的图例在整个目录树中出现的位置,这样就可以发现:不管是根目录,还是第2层或第3层中的每个目录都是由一个IMAGE_RESOURCE_ DIRECTORY结构和紧跟其后的数个IMAGE_RESOURCE _DIRECTORY_ENTRY结构组成的,这两种结构一起组成了一个目录块。
IMAGE_RESOURCE_DIRECTORY结构中包含的是本目录的各种属性信息,其中有两个字段说明了本目录中的目录项数量,也就是后面的IMAGE_RESOURCE_DIRECTORY_ ENTRY结构的数量。
IMAGE_RESOURCE_DIRECTORY结构的定义如下所示:
IMAGE_RESOURCE_DIRECTORY STRUCT Characteristics dd ? ;理论上为资源的属性,不过事实上总是0 TimeDateStamp dd ? ;资源的产生时刻 MajorVersion dw ? ;理论上为资源的版本,不过事实上总是0 MinorVersion dw ? NumberOfNamedEntries dw ? ;以名称命名的入口数量 NumberOfIdEntries dw ? ;以ID命名的入口数量 IMAGE_RESOURCE_DIRECTORY ENDS
在这个结构中,最重要的是最后两个字段,它们说明了本目录中目录项的数量,那么为什么有两个字段呢?
原因是这样的:不管是资源种类,还是资源名称都可以用名称或者ID两种方式定义,比如在*.rc文件中这样定义:
100 ICON "Test.ico" //(例1) 101 WAVE "Test.wav" //(例2) HelpFile HELP "Test.chm" //(例3) 102 12345 "Test.bin" //(例4)
例1定义了一个ID为100的光标资源,光标的资源类型虽然写成“ICON”,但这只是一个助记符,在资源编译器里面会被换成数值型的类型ID,所有的标准类型资源都是以数值型ID定义的,在资源定义中,1到10h的ID编号保留给标准类型使用。
在例2中,标准的资源类型中并没有“WAVE”这一类型,这时资源的类型属于自定义型,类型的名称就是“WAVE”。
例3则定义了资源名称是“HelpFile”,类型名称为自定义字符串“HELP”的资源。
在例4中,资源的ID编号是102,而类型则是数值型ID,由于标准类型中并没有编号为12345的资源,所以这也是一个自定义类型的资源。
在IMAGE_RESOURCE_DIRECTORY结构中,对以ID命名和以字符串命名的情况是分别指定的:NumberOfNamedEntries字段是以字符串命名的资源数量,而NumberOfIdEntries字段的值是以ID命名的资源数量,所以两者的数量加起来才是本目录中的目录项总和,也就是当前IMAGE_RESOURCE_DIRECTORY结构后面紧跟的IMAGE_RESOURCE_DIRECTORY_ENTRY结构的数量。
现在来介绍一下IMAGE_RESOURCE_DIRECTORY_ENTRY结构,每个结构描述了一个目录项,IMAGE_RESOURCE_DIRECTORY_ENTRY结构是这样定义的:
IMAGE_RESOURCE_DIRECTORY_ENTRY STRUCT Name1 dd ? ;目录项的名称字符串指针或ID OffsetToData dd ? ;目录项指针 IMAGE_RESOURCE_DIRECTORY_ENTRY ENDS
结构中的两个字段说明如下:
● Name1字段
这个字段的名称应该是“Name”,同样是因为和关键字冲突的原因改为“Name1”,它定义了目录项的名称或者ID,这个字段的含义要看目录项用在什么地方,当结构用于第1层目录的时候(如图17.8中的B所示),这个字段定义的是资源的类型,也就是前面例子中的“ICON”,“WAVE”,“HELP”和12345等;当结构用于第2层目录的时候(如图17.8中的C1到C3),这个字段定义的是资源的名称,也就是前面例子中的100,101,“HelpFile”和102等;而当结构用于第3层目录的时候(如图17.8中的D1到D4),这里定义的是代码页编号。
读者肯定会发现一个问题:当字段作为ID使用的时候,是可以放入一个双字的,如果使用字符串定义的时候,一个双字是不够的,这就需要将两种情况分别对待,区分的方法是使用字段的最高位(位31)。当位31是0的时候,表示字段的值作为ID使用;而位31为1的时候,字段的低位作为指针使用,但由于资源名称字符串是使用UNICODE来编码的,所以这个指针并不直接指向字符串,而是指向一个IMAGE_RESOURCE_DIR_STRING_U结构,这个结构包含UNICODE字符串的长度和字符串本身,其定义如下:
IMAGE_RESOURCE_DIR_STRING_U STRUCT Length1 dw ? ;字符串的长度 NameString dw ? ;UNICODE字符串,由于字符串是不定长的,所以这里只能 ;用一个dw表示,实际上当长度为100的时候,这里的数据 ;是NameString dw 100 dup (?) IMAGE_RESOURCE_DIR_STRING_U ENDS