PE文件之资源表
资源(光标、图标、菜单等)一般写在资源脚本文件中,通过RC编译器进行编译供可执行程序使用。在资源文件中,字符串都以Unicode的形式存储。
在PE文件格式中,这些资源的二进制格式统一放在一个单独的节区中:.rsrc。
如何找到一个程序的资源呢?我们可以通过PE文件可选头中的数据目录的第三项来定位程序资源:

在获取资源块地址的时候,注意不要使用查找".rsrc"节起始地址的方法,虽然在一般情况下资源总是在".rsrc"节中,但这并不是必然的。
得到了资源块的RVA和大小之后,我们就来解析这些字节,看看它们的组织形式。
资源的组织方式

上图很直观地展现了资源的组织方式,三种类型的结构加上资源数据组成了资源块。
资源目录头IMAGE_RESOURCE_DIRECTORY


写过资源脚本文件的都知道,我们可以使用资源名称(字符串)或者资源ID来命名一个资源:

所以IMAGE_RESOURCE_DIRECTORY的最后两项的总和,才是IMAGE_RESOURCE_DIRECTORY下面IMAGE_RESOURCE_DIRECTORY_ENTRY结构的数量。
资源目录项IMAGE_RESOURCE_DIRECTORY_ENTRY

Name1字段
这个字段用于定义资源目录项的名称或者ID,在不同级别的目录中,有着不同的含义:
- 一级目录:表示资源的类型
- 二级目录:表示资源的名称
- 三级目录:表示资源的代码页编号
对于一级目录,该字段作为资源类型的来使用,当资源类型以ID来表示的时候(最高位是0),并且ID的数值在1到16之间,那么这种类型是系统定义的类型,如果ID数值大于16,这是用户自定义的类型:

当字段作为ID使用的时候,是可以放入一个双字的,如果使用字符串定义的时候,一个双字是不够的,这就需要将两种
情况分别对待,区分的方法是使用字段的最高位(位31)。当位31是0的时候,表示字段的值作为ID使用;而位31为1的时候,字段的低位作为指针使用,但由于资源名称字符串是使用UNICODE来编码的,所以这个指针并不直接指向字符串,而是指向一个IMAGE_RESOURCE_DIR_STRING_U结构,这个结构包含UNICODE字符串的长度和字符串本身,其定义如下:

OffsetToData字段
这个字段是一个指针,在不同的级别中也有不同的含义:
- 在一二级目录中:当它的最高位(位31)为1时,低位数据指向下一层目录块的起始地址,也就是一个IMAGE_RESOURCE_DIRECTORY结构
- 在三级目录中:当字段的位31为0时,指针指向的是用来描述资源数据块情况的IMAGE_RESOURCE_DATA_ENTRY指针
注意,上面两个字段如果表示的是地址,那么这个地址是相对于资源块开始的地方的偏移地址(RESOURCES数据目录项中指定的RVA)。
资源数据项IMAGE_RESOURCE_DATA_ENTRY

结构中的OffsetToData字段的值是指向资源数据的指针,奇怪的是,这个指针却是一个RVA值,而不是以资源块的起始地址为基址的,这是读者需要特别注意的地方。
结构中的第3个字段是CodePage,这个字段的名称有些奇怪,因为当前资源的代码页已经在第3层目录中指明了,在这里再定义一次有重复之嫌,在实际的应用中,这个字段好像未被使用,因为随便找一个PE文件看看就会发现这里的值总是为0。
通过工具解析资源
这里推荐使用Resource Hacker来查看一个程序的资源,该工具还可以编辑程序中的资源:
