WINDOWS+PE权威指南读书笔记(12)

目录

资源表

资源分类和定义:

位图、光标、图标资源:

菜单资源:

对话框资源:

自定义资源:

PE 资源表组织:

资源表的组织方式:

资源表数据定位:

资源目录头IMAGE_RESOURCE_DIRECTORY:

资源目录项 IMAGE_RESOURCE_DIRECTORY_ENTRY:

资源数据项 IMAGE_RESOURCE_DATA_ENTRY:

三级结构中目录项的区别:

资源表遍历:

PE 资源深度解析:

资源脚本示例:

使用 PElnfo 分析资源表:


资源表

PE 中的相关资源可以通过程序进行深度定位,所获取的二进制字节码与资源脚本语句之间是一一对应的。本章将对这

两者的关系进行深入的解析,还会通过直接读资源数据和使用 Windows API 函数两种方法对资源表进行编程。

资源分类和定义:

程序中常用的六类资源包括:

口 位图资源

口 光标资源

口 图标资源

口 菜单资源

口 对话框资源

口 自定义资源

资源数据在 PE 里是最复杂的一种。其难度主要体现在对资源数据的遍历定位上,以及资源块的不易阅读性。因为即使通过信息定位方法找到了资源块,其内部结构还需要进一步解析。

当初次将资源概念应用到 PE 时,人们普遍认为 16 个类型就足以包含所有的资源数据了;所以,这 16 个类型的资源在追加进 PE 时,并不是赤裸裸地进入,而是全部附加了一些数据结构(尽管这些结构很简单),这些数据结构实现了对不同类别资源块的描述与组织。操作每一个资源块之前,都需要明确该资源在 PE 中追加的资源头部数据结构,这便成为用户使用这些资源的主要障碍。

随着计算机技术的发展,新的技术不断诵现,新的资源类型也被定义。微软首先开始在这 16 个基本类型后追加新的类型,如多媒体、XML、超文本、MANIFEST、VXD 等。随着这些新的信息越来越多,慢慢地,微软意识到,维护这样的信息难度很大,于是就放弃了对这些新信息的扩展,同时,对新加入的资源类别的数据组织方式也不再进行明示。这为程序设计者在操作资源数据时设置了障碍。

现在看来,应该是资源类型的最初定义出现了问题,至少让人感觉不是那么优雅。好在PE 中针对资源的数据结构定义是良好的,例如,IMAGE_RESOURCE_DIRECTORY_ENTRY结构中的第一个字段用来表示资源类别。

定义数据结构的工程师在这里用了一个很巧妙的规定,使得资源类型可以被用户随意定义。数据结构规定,该字段高位如果为1,说明对应的资源类别是一个非标准的新类型,用户可以通过扩展这个字段定义一个新的不在预定义里的资源类型。

虽然在 16 种基础资源类别里也为开发者留了一个扩展,即 RT_RCDATA (表 7-1 中第 10项),鉴于新资源类别定义的不公开特性(就是前面说的不再进行明示),许多第三方软件不得不扩展该类别,将所有新定义的类型全部设置为RT_RCDATA (在有的书中称此资源类别为二进制)。

位图、光标、图标资源:

口 位图定义: nameID BITMAP [DISCARDABLE] 位图文件名

口 光标定义: nameID CURSOR [DISCARDABLE] 光标文件名

口 图标定义: nameID ICON [DISCARDABLE] 图标文件名

1) nameID 表示该资源的名字,在程序中使用资源时需要用到它,类似于文件的句柄。

2) BITMAP、CURSOR、ICON 表示资源的类型。

3) DISCARDABLE 关键字是可选项,表示在不用的时候可以从内存中暂时卸载掉。

注意:当文件名包含空格时,需要使用英文半角状态下的双引 引起来。

脚本定义语句举例如下:

以上定义都是合法有效的。看一个实例:

以上脚本定义了程序的图标 main.ico

注意:上面的例子告诉我们,对应的外部文件可以使用绝对路径。

菜单资源:

其中,菜单 ID 可以是 16 位的整数,其赋值范围在 1 ~ 65535 之间。

菜单项的定义可以有三种,分别表示:

口 普通菜单项

口 菜单分隔符

口 弹出菜单

其语法结构分别如下所示:

以下是一个菜单实例,来自 chapter7pe.rc:

如以上代码所示,IDM_MAIN 为菜单句柄,该菜单包含两个弹出式菜单“文件”和“查看”。

“文件”弹出菜单:包含“打开” 分隔符和“退出”3 个菜单选项 ;

“查看”弹出菜单:包含“源文件“窗口透明度” 分隔符“大小”和“宽度”5 个菜单选项。

对话框资源:

在资源脚本的定义中,对话框最为复杂,其语法如下:

对话框的可选属性及描述见表 7-2:

以下是一个对话框实例,代码如下:

以上定义为对话框指定了标题文字、在屏幕上的坐标、使用的字体、对话框的样式 。 此外,还为对话框指定了一个菜单 IDM_MAIN,并在对话框上安排了一个富文本控件。

自定义资源:

当开发者需要在 PE 文件中附带自定义数据时,可以使用自定义资源。其在资源文件中的定义语法如下:

大部分情况下,都是将一个磁盘文件当做资源的内容。此时的语法简化为:

类型 ID 可以是大于 255 的数值或字符串,如可以定义如下自定义资源:

注意:MP3、FLASH 并不在常用的资源类别里。这里的资源类别的名字可以自行定义。

PE 资源表组织:

资源表的组织方式:

PE 的资源组织方式类似于操作系统的文件管理方式。从根目录开始,下设一级子目录、二级子目录和三级子目录;三级子目录下才是文件。其三级目录结构如图 7-1 所示。

一级子目录按照资源类型分类,如“光标”一级子目录、“位图”一级子目录、“菜单”一级子目录、“字符串”一级子目录“加速键”一级子目录等多个资源类型。

二级子目录按照资源的 ID 分类。例如,同样是“菜单”一级子目录的内容,其下可以有 :IDM OPEN 的ID 为2001、IDM_ EXIT的ID 为2002、IDM 1的ID 为4000 等多个菜单项。

三级子目录是按照资源的代码页分类,即不同的语言代码页对应不同的数据。其中,根据语言可以分为简体中文、英文、繁体中文等多个代码页。

三级目录后即为节点,也就是所说的 “文件”。这里的“文件”其实就是包含了资源数据的指针和大小等信息的一个数据结构而已。对所有资源数据块的访问均可从这里开始。

扩展阅读:资源目录结构单元

由于一、二、三级目录的数据结构是相同的,均是由一个资源目录头加上多个线性跟随着的资源目录项组成,笔者将主干和枝干的节点称为资源目录结构单元,如图 7-2 所示。

从数据结构角度来看,资源表是一个四层的二又排序树结构:

其中,第一层为主干,第二、三层为枝干,叶子节点为第四层。 主干和枝干的节点即为资源目录结构单元,完整示意见图 7-3。

资源表数据定位:

资源表是一张找述资源数据在 PE 中的分布情况的表。资源表是数据目录中注册的数据类型之一,其描述信息位于数据目录的第 3 个目录项中。使用PEDump 小工具获取 chapter7PE.exe 的数据目录表内容如下:

通过以上字节码可获得与资源表数据有关的两条信息:

口 资源表数据所在地址 RVA=0x000004000

口 资源表数据大小 =000006A0h

以下是使用PEInfo 小工具获取到的该文件所有的节信息:

资源目录头IMAGE_RESOURCE_DIRECTORY:

资源表数据从第一级资源目录开始。资源的每一级目录都会有一个资源目录头,它标识了该类资源的属性、创建日期和版本等信息,其中也包含了随后的目录项的数量描述信息。

详细结构定义如下:

IMAGE_RESOURCE_DIRECTORY.Characteristics:

+0000h,双字。资源属性,保留为将来使用,必须为 0。

IMAGE_RESOURCE_DIRECTORYTimeDateStamp:

+0004h,双字。时间戳,即创建该资源的时间。

IMAGE_RESOURCE_DIRECTORY.MajorVersion 和

IMAGE_RESOURCE_DIRECTORY.MinorVersion:

+0008h,双字。资源的版本 。未用,大部分情况下为 0。

IMAGE_RESOURCE_DIRECTORY. NumberOfNamedEntries:

+000ch,双字。以名称命名的资源个数。

IMAGE_RESOURCE_DIRECTORY. NumberOfldEntries:

+000eh,双字。以ID 命名的资源个数。

资源目录项 IMAGE_RESOURCE_DIRECTORY_ENTRY:

紧跟在资源目录后的数据结构,就是在资源目录中声明的资源目录项。一个资源目录可能有多个资源目录项(以名称定义的资源目录项或以 ID 定义的资源目录项,或者两者组合),目录项和目录项之间是线性排列的。首先按照字母升序(不分大小写) 排列名称资源目录项,然后再按 ID 升序排列 ID 资源目录项。

图 7-4 是资源目录及目录项之间的关系示意图:

资源目录项数据结构的详细定义如下:

因为结构中使用了 union 类型,所以这个结构稍微难懂一些(公用体中每次只选一个)。每个 union 字段在不同的使用场合会具有不同的数据解释和不同的数据值。

IMAGE_RESOURCE_DIRECTORY_ENTRY.Name1:

+0000h,双字。第一个 union 字段,它定义了目录项的名称或者 ID。

该双字的高位(即 31 位) 如果为 1:

则表示低地址部分为一个指向 Unicode 字符串的指针(注意,这里的字符串不是 Ansi 字符串,所以另有规定)。

该双字的高位(即 31 位)如果为 0:

则表示该字段为一个编 。

资源中对字符串的定义全部采用 Unicode 编码,该指针并不直接指向一个以 “ ” 结尾的字符串所在地址,而是指向了结构 IMAGE_RESOURCE_DIR_STRING_U。该结构完善了指针的定义(即不仅包含指针,还包含指针指向的块长度,大家可以自己想想为什么这里需要长度字段),其详细定义如下:

IMAGE_RESOURCE_DIRECTORY_ENTRY.OffsetToData:

+0004h,双字。这个字段是一个指针,当它的高位(第 31 位) 为0时,指针指向的是描述资源数据块的指针,通常出现在第三级目录中;当高位为 1 时,低位数据指向下一级目录块的起始地址。有关这两个字段更详细的解释参见 7.2.6 小节。

提示:上面的OffsetToData和.Name1的地址并不是基于文件起始地址的,它的偏移是基于资源表的起始位置。一定要清楚这一点,否则在定位资源数据时会出现错误!

资源数据项 IMAGE_RESOURCE_DATA_ENTRY:

资源数据项其实就是前面所说的 “目录 – 文件” 结构中的 “文件”。它是通过三次目录定位后找到的一个数据结构,图 7-5 显示了资源数据项与三级目录和最终的资源数据块之间的关系。

如图7-5所示,第三级目录项中的字段 IMAGE_RESOURCE_DIRECTORY_ENTRY.OffsetToData (就是上一个字段)指向了资源数据项,而资源数据项中的 OffsetToData 字段则指向了资源数据块。

资源数据项的详细结构定义如下:

IMAGE_RESOURCE_DATA_ENTRY.OffsetToData:

+0000h,双字。该字段是一个指向资源数据块的指针,是一个 RVA 值,在文件中访问时需要注意转换成文件偏移。此处指向的资源数据块还不是赤裸裸的资源信息,而是附加了一些数据结构的资源块。

IMAGE_RESOURCE_DATA_ENTRY .Size1:

+0004h,双字。资源数据的大小。

IMAGE_RESOURCE_DATA_ENTRY.CodePage:

+0008h,双字。代码页,未用,大多数情况下为 0。

IMAGE_RESOURCE_DATA_ENTRY.Reserved:

+000ch,双字。保留字段。总是为 0。

对资源表的大部分编程,只要能解析出该结构中指定资源块所处的地址和资源块的大小,资源表的使命也就算完成了。

三级结构中目录项的区别:

由于目录处的级别不同,目录中各字段所表述的内容也不一样; 尽管它们具有相同的数据结构和完全相同的字段,在不同级别的目录项中有些字段的含义是不一样的。本小节就专门研究三级目录中目录项各字段不一样的地方。

IMAGE_RESOURCE_DIRECTORY_ENTRY.Name1:

(1) 字段最高位 (即31位) 为1:

当结构用于第一层目录时:

表示这是一个非标准的类型。由该字段的低 31 位组成一个偏移值,该偏移是相对于资源基地址的特殊偏移地址。

该地址指向一个 IMAGE_RESOURCE_DIR_STRING_U 结构表示的 Unicode 字符串。字符串为非标准的类型的名字。类似于本章第1 节自定义资源中的 “DLLTYPE”。

当结构用于第二层目录时:

表示这是一个非标准的命名。由该字段的低 31 位组成一个偏移值,该偏移是相对于资源基地址的特殊偏移地址。该地址指向一个 IMAGE_RESOURCE_DIR_STRING_U 结构表示的 Unicode 字符串。字符串为非标准的类型下的命名,类似于本章第 1 节自定义资源中的 “DIB_WINRESULT”。

当结构用于第三层目录时:

表示这是一个非标准的语言 (没有预定义的代码页)。由该字段的低 31 位组成一个偏移值,该偏移是相对于资源基地址的特殊偏移地址。该地址指向一个IMAGE_RESOURCE_DIR_STRING_U 结构表示的 Unicode 字符串。字符串为非标准的语言的名字。

(2) 字段第 31 位为0:

当结构用于第一层目录时:(预定义类型)

表示这是标准的类型 (预定义的类型)。由该字段的低 16 位组成整数标识符 ID,由于该类型已定义,所以可以通过该标识符获取预先定义的名字。例如该值为03h,则名字表示预定义当中的 “ICON ”。

当结构用于第二层目录时:(自定义名字)

表示这是标准的命名(预定义的类型)。由该字段的低 16 位组成整数标识符 ID 来定义名字。

当结构用于第三层目录时:(预定义语言)

表示这是标准的语言代码(预定义的类型)。由该字段的低16 位组成整数标识符 ID,可以通过该标识符获取预先定义的语言的名称。如 ID=2052,表示该语言为 Simpled_Chinese (简体中文)。大多数情况下,每个资源的代码页只定义一种。

IMAGE_RESOURCE_DIRECTORY_ENTRY.OffsetToData:

(1) 字段第 31 位为1:

当结构用于第一层目录时:

由该字段低 31 位组成一个整数偏移地址。该地址是相对于资源起始地址的偏移,该偏移指向下一个目录。

当结构用于第二层目录时:

由该字段低 31 位组成一个整数偏移地址。该地址是相对于资源起始地址的偏移,该偏移指向下一个目录。

第三层目录的该值第 31 位不为 1。

(2) 字段第 31 位为0:

第一层目录的该值第 31 位不为 0。

第二层目录的该值第 31 位不为 0。

当结构用于第三层目录时,表示该字段指向一个数据项 IMAGE_RESOURCE_DATA_ENTRY 。

注意:由低31 位组成的地址是基于资源起始地址的。

资源表遍历:

在 PE 文件头 IMAGE_OPTIONAL_HEADER32 的数据目录中,第 3 个 IMAGE_DATIA_DIRECTORY 结构描述的就是资源表。

以下是 PE 中资源表所在位置和大小的获取方法:

口资源表位置:IMAGE_DATA_DIRECTORY[8*2].VirtualAddress

口资源表大小:IMAGE_DATA_DIRECTORY[8*2].isize

更新PEInfo.exe,将资源表内容按照人性化方式显示出来:

和前面其他数据的遍历一样,需要在 PEInfo.asm 的 _openFile 中加入以下遍历代码:

代码清单 7-1 获取 PE 文件的资源信息的函数 _getResource (chapter7peinfo.asm )

代码 ~ 略 ·

以下内容显示了新的 PEInfo.exe 分析记事本程序的资源表数据的部分内容:

PE 资源深度解析:

PE 中的资源表结构只帮助我们查找指定资源(可以通过指定名称或指定编 )的资源数据块所在文件的位置和大小。而对于某个特定的资源,比如对话框或菜单,其详细的数据块格式需要在本小节中完成。本节以 PE.exe 为例,对 PE.exe 中的资源数据块进行深度解析。首先来看一下 PE.exe 的资源脚本定义,该定义在第 2 章中出现过。

资源脚本示例:

PE.exe 的资源定义在文件 pe.rc 中,文件中一共定义了三种资源 : 图标、菜单和对话框:

使用 PElnfo 分析资源表:

使用PEInfo 查看 PE.exe 中的资源表,内容如下:

在深度解析资源字节码之前,首先明确几个概念:

1:资源表中的 Unicode 字符:

资源文件中的所有字符串都以 Unicode 格式存储,每个字符都由一个 16 位(单字) 值表示,字符串以UNICODE_ NULL (该值是两个 “ ” 字节) 结束。资源编译器调用 WindowsAPI 中的 MultiByteToWideChar 函数将 ASCII 字符串转换为 Unicode 字符串,所有溢出的字符都被当做合法的 Unicode 字符直接存储。当这些字符串被程序以ASCII 字符读出(例如使用LoadString API) 时,系统将它们再由 Unicode 转换为 ASCII 字符。

仅有的例外是在 RCDATA 语句中的字符串。这些“伪”字符串并不是真正的字符串,只被当做一些字节的集合。用户可能会用 RCDATA 语句存储一些自定义的数据结构,如果一个 ”伪“ 字符串被自动转换为 Unicode 字符串存储起来,这个 “伪” 字符串就会按照 Unicode字符串的格式被存储,从而导致这个 “伪” 字符串字节码内容的改动。

假设这个 “伪” 符串是一个PE 文件的字节码,再次被释放以后,这个 PE 文件就可能无法运行了。因此,这些 “伪” 字符串必须以它的本来面目存储下来,即字节码。若想在 RCDATA 语句中包含 Unicode字符串,用户可以使用带 “L” 前缀的字符串。

2:资源字节码对齐:

为使二进制资源文件更容易读写,在 Win32 下,文件中的所有对象都是双字对齐的 ,包括头信息和数据项。这并不会改变资源数据结构中每个字段的顺序,但会在这些字段中间增加一些填充域,通常情况下,这些填充域的值均为 0。

资源中的大部分类型都遵循该规则,但有两个除外,一个是字体(font),另一个是字体目录 (fontdir)。因为这两个结构直接复制自别的文件,它们并不被资源编译器所使用。

3:一个字段的多重定义:

在接下来的数据结构中,大家会看到类似于 “[ 名称或序数 ]” 这样的字段描述。[ 名称或序数 ] 字段的第一个单字,标志这个字段到底是一个数字还是一个字符串。如果它等于 0xffff (一个非法的 Unicode 字符),那么在它后面的单字信息就是一个类型序 (一个数字), 否则,这个字段就是一个 Unicode 字符串。

如果类型域是一个数字,那它就代表一个标准的或者用户自定义的资源类型。所有标准的 Windows 资源类型都被赋予一个特定的值 (如下所示的预定义值),它包含了绝大多数资源类型的类型序数。

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2021年10月12日
下一篇 2021年10月12日

相关推荐