WIN32汇编语言教程:第17章 PE文件 · 17.4 资源(2)
如果要得到 ANSI 类型的以 0 结尾的字符串,需要将 NameString 字段中包括的 UNICODE 字符串用 WideCharToMultiByte 函数转换一下,具体的方法读者可以参考后面的例子。
● OffsetToData 字段
这个字段是一个指针,当它的最高位(位13)为 1 时,低位数据指向下一层目录块的起始地址,也就是一个 IMAGE_RESOURCE_DIRECTORY 结构,这种情况一般出现在第 1 层和第 2 层目录中;当字段的位 31 为 0 时,指针指向的是用来描述资源数据块情况的 IMAGE_RESOURCE_DATA_ENTRY 指针,这种情况出现在第 3 层目录中。
当将 Name1 字段和 OffsetToData 用做指针时需要注意两点,首先是不要忘记将最高位清除(使用 7FFFFFFFH 来 and 一下);其次就是这两个资源块开始的地方算起的偏移量,也就是根目录的起始位置算起的偏移量。
注意:千万不要将这两个指针作为 RVA 来对待,否则会得到错误和地址。正确的计算方法是将指针的值加上资源块首地址,结果才是真正的地址。
最后还需要说明的是,当 IMAGE_RESUOURCE_DIRECTORY_ENTRY 用在第 1 层目录中的时候,它的 Name1 字段是作为资源类型来使用的。当资源类型以 ID 定义(最高位等于 0),并且 ID 数值在 1 到 16 之间时,表示这是系统预定义的类型,ID 与类型的对应关系请参考表 17.6 ;如果资源类型是以 ID 定义的并且数值在 16 以上,表示这是一个自定义的类型。
表 17.6 预定义的资源类型
类型 ID 值 | 在 Windows.inc 中的预定义值 | 资源类型 |
1 | RT_CURSOR | 光标(cursor) |
2 | RT_BITMAP | 位图(bitmap) |
3 | RT_ICON | 图标(icon) |
4 | RT_MENU | 菜单(menu) |
5 | RT_DIALOG | 对话框(dialog) |
6 | RT_STRING | 字符串(string) |
7 | RT_FONTDIR | 字体目录(font directory) |
8 | RT_FONT | 字体(font) |
9 | RT_ACCELERATOR | 加速键(accelerators) |
10 | RT_RCDATA | 未格式化资源(unformatted) |
11 | (无) | 消息表(message table) |
12 | RT_GROUP_CURSOR | 光标组(group cursor) |
14 | RT_GROUP_ICON | 图标组(group icon) |
16 | RT_VERSION | 版本信息(version information) |
3。资源数据入口
沿着资源目录树按照根目录》资源类型》资源 ID 的顺序到达第 3 层目录后,这一层目录的 IMAGE_RESOUCE_DIRECTORY_ENTRY 结构的 OffsetToData 字段指向的是一个 IMAGE_RESOURCE_DATA_ENTRY 结构(如图 17.8 中的 E1 到 E5 所示)。
IMAGE_RESOURCE_DATA_ENTRY 结构的定义如下所示:
IMAGE_RESOURCE_DATA_ENTRY struct OffsetToData dd ? ;资源数据的 RVA Size1 dd ? ;资源数据的长度 CodePage dd ? ;代码页 Reserved dd ? ;保留字段 IMAGE_RESOURCE_DATA_ENTRY ends
IMAGE_RESOURCE_DATA_ENTRY 结构描述了资源数据所处的位置和大小,换句话说,就是经过了这么多层结构的长途跋涉以后,终于得到了某个资源的详细信息。
结构中的 OffsetToData 字段的值是指向资源数据的指针,奇怪的是,这个指针却是一个 RVA 值,而不是以资源块的起始地址为基址的,这上读者需要特别注意的地方。Size1 字段的值是资源数据的大小。结构中的第 3 个字段是 CodePage,这个字段的名称有些奇怪,因为当前资源的代码页已经在第 3 层目录中指明了,在这里再定义一次有重复之嫌,在实际的应用中,这个字段好像未被使用,因为随便找一个 PE 文件看看就会发现这里的值总是为 0。
17.4.3 查看 PE 文件中的资源列表举例
本节中的例子遍历 PE 文件中的资源目录树并显示每个资源的详细信息,例子的源代码放在本书光盘的 Chapter17\Resource 目录中,同样,为了节省篇幅,界面代码沿用前面的 Main.asm 和 Main.rc 文件,下面是 Main.asm 中包括的 _ProcessPeFile.asm 文件的内容:
.const szMsg db '文件名: %s',0dh,0ah db '------------------------------------------------',0dh,0ah db '资源所处的节:%s',0dh,0ah,0 szErrNoRes db '这个文件中没有包含资源!',0 szLevel1 db 0dh,0ah db '------------------------------------------------',0dh,0ah db '资源类型:%s',0dh,0ah db '------------------------------------------------',0dh,0ah,0 szLevel1byID db '%d (自定义编号)',0 szLevel2byID db ' ID: %d',0dh,0ah,0 szLevel2byName db ' Name: %s',0dh,0ah,0 szResData db ' 文件偏移:%08X (代码页=%04X, 长度%d字节)',0dh,0ah,0 szType db '光标 ',0 ;1 db '位图 ',0 ;2 db '图标 ',0 ;3 db '菜单 ',0 ;4 db '对话框 ',0 ;5 db '字符串 ',0 ;6 db '字体目录 ',0 ;7 db '字体 ',0 ;8 db '加速键 ',0 ;9 db '未格式化资源',0 ;10 db '消息表 ',0 ;11 db '光标组 ',0 ;12 db '未知类型 ',0 ;13 db '图标组 ',0 ;14 db '未知类型 ',0 ;15 db '版本信息 ',0 ;16 .code include _RvaToFileOffset.asm _ProcessRes proc _lpFile,_lpRes,_lpResDir,_dwLevel local @dwNextLevel,@szBuffer[1024]:byte local @szResName[256]:byte pushad mov eax,_dwLevel inc eax mov @dwNextLevel,eax ; 检查资源目录表,得到资源目录项的数量 mov esi,_lpResDir assume esi:ptr IMAGE_RESOURCE_DIRECTORY mov cx,[esi].NumberOfNamedEntries add cx,[esi].NumberOfIdEntries movzx ecx,cx add esi,sizeof IMAGE_RESOURCE_DIRECTORY assume esi:ptr IMAGE_RESOURCE_DIRECTORY_ENTRY ; 循环处理每个资源目录项 .while ecx > 0 push ecx mov ebx,[esi].OffsetToData .if ebx & 80000000h