PE文件资源简介

PE文件中的资源部分占PE文件很大的一个比例,资源包括光标,位图,菜单等等还有十几种标准类型,处理标准类型还有自定义类型。资源的组织方式很像磁盘目录的组织的方式,或者说像数据结构中的树型结构。PE文件中资源的组织方式如下图:

从上图可以看到资源块是由很多种结构组成的,第一层目录也就是根目录的第一个结构是一个IMAGE_RESOURCE_DIRECTORY结构,它也就是IMAGE_OPTIONAL_HEADER32结构中的数据目录的第三项IMAGE_DATA_DIRECTORY结构中VirtualAddress字段RVA所指向的内容。从上图可以看到第一二三层目录都是以一个IMAGE_RESOURCE_DIRECTORY结构开始后面跟着一系列的IMAGE_RESOURCE_DIRECTORY_ENTRY结构构成的。

整个资源的组织方式:第一层根目录,目录项就是描述有哪些类型的资源,然后每个目录项又各指向一个第二层目录的IMAGE_RESOURCE_DIRECTORY结构,第二层目录主要就是说明同种类型有哪些具体的资源,每个目录项里面包含该资源名称或者ID,然后每个目录项又各自指向一个第三层目录的结构,第三层目录的组织方式和第一二层一样,但是都只有一个目录项,目录项主要就是说明本资源的代码页,然后再指向一个资源数据入口,就是上图从左往右第4列的结构,该结构说明了一些资源的信息,并给出了资源数据的RVA。

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或者字符串的名称来定义。比如:

100                       ICON                  "Test.ico"              例1

HelpFile                HELP                   "Test.chm"           例2

第一种方式就是以ID定义的,第二种方式就是以名称定义的。所以NumberOfNamedEntries字段加上NumberOfIdEntries字段才是本目录种所有的入口数量。然后后跟在这个结构后面的IMAGE_RESOURCE_DIRECTORY_ENTRY结构,该结构一个结构描述一个目录项,结构的定义如下:

IMAGE_RESOURCE_DIRECTORY_ENTRY    STRUCT
    Name1    dd    ?            ;目录项的名字字符串指针或ID
    OffsetToData    dd   ?            ;目录项指针
IMAGE_RESOURCE_DIRECTORY_ENTRY    ENDS   

​

该结构很简单,第一个字段Name1是一个指向本目录项的一个名字字符串或者ID,如果该目录是第一一层目录,那么它就指向资源类型的字符串或者是资源类型的ID,如果是第二层目录,它就指向资源名称的字符串或者是资源名称的ID,如果是第三层目录,那么它表示的就是代码页。当该字段表示ID时,32位比特足矣,但是要表示字符串的话就只能用RVA这种方式来表示了,但是指向字符串时它其实不是字符串的RVA。当Name1字段的最高位为0时,该字段为ID,当该字段的最高位为1时,该字段的低位表示一个相对于资源块起始位置的偏移。

当Name1字段指向字符串时,它也并不是直接指向字符串的地址的,它指向一个IMAGE_RESOURCE_DIR_STRING_U结构,该结构的定义如下:
 

IMAGE_RESOURCE_DIR_STRING_U    STRUCT
    Length1    dw    ?    ;字符串的长度
    NameString dw    ?    ;UNICODE字符串,由于字符串是不定长的,所以这里只能用一个DW来表示一下
                          ;实际上如果长度是10,那么应该是 dw  10  dup (?)
IMAGE_RESOURCE_DIR_STRING_U    ENDS

第一个字段描述了字符串的长度,第二个字段就是字符串的位置了。

IMAGE_RESOURCE_DIRECTORY_ENTRY结构的第二个字段也是一个相对于资源起始位置的偏移,它指向下一层目录。如果该字段的最高位为1的话, 那么字段的低位指向指向一个IMAGE_RESOURCE_DIRECTORY结构,这样的情况只有在第一层和第二层种出现,当在第三层目录时,该字段高位为0,字段指向一个资源数据入口的结构。

在第三层目录中,目录项的Name1字段说明了资源的代码页,OffsetToData字段指向资源数据入口结构,该结构定义如下:

IMAGE_RESOURCE_DATA_ENTRY    STRUCT
    OffsetToData    dd    ?    ;资源数据的RVA
    Sizel    dd           ?    ;资源数据的长度
    CodePage    dd        ?    ;代码页
    Reserved    dd        ?    ;保留字段
IMAGE_RESOURCE_DATA_ENTRY    ENDS        

 这里的第一个字段是一个资源数据的RVA,而不再是一个相对于资源快起始位置的偏移了。第二个字段说明资源数据的长度。

那么这里也可以根据以上信息写一个查看PE文件资源的小程序。因为资源的组织方式类似于树形结构,所以我们可以像数据结构中的深度优先搜索遍历一样来遍历输出资源的信息。

遍历整个资源结构可以用一个循环,循环次数为该目录的目录项数,然后循环中要有一个判断,如果目录项指向的下一个结构是IMAGE_RESOURCE_DIRECTORY,那么则递归调用这个循环函数进入下一层目录,然后在下一层目录中再循环。如果目录项指向的是资源数据入口结构,那么就输出资源的各种信息。

资源文件和框架用的都是之前博客里的,实现RVA转换文件偏移和查找数据所在的节的函数也是之前博客里的。功能实现代码如下:

				.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				;测试最高位是否是1,如果是1那么它指向第二层目录的IMAGE_RESOURCE_DIRECTORY结构,如果是0则它指向第三层目录的IMAGE_RESOURCE_DATA_ENTRY
								and		ebx,7fffffffh		;将最高位清0
								add		ebx,_lpRes			;这个偏移加上资源数据块的地址得到它指向的第二层目录的文件地址
								.if		_dwLevel == 1		;如果当前目录是第一层
;*********************************************************************************************************************
;第一层:资源类型
;*********************************************************************************************************************
										mov		eax,[esi].Name1			;获得该目录项的名字字符串结构的偏移或者ID(当最高位为1时该字段标识偏移,为0时表示ID)
										.if		eax & 80000000h			;如果最高位为1
												and		eax,7fffffffh	;将最高位清0
												add		eax,_lpRes		;得到名字字符串结构的地址
;**********************************************************************************************************************
;处理IMAGE_RESOURCE_DIR_STRING_U结构并将UNICODE转换成ANSI字符串
;**********************************************************************************************************************
												movzx	ecx,word ptr [eax]		;获取字符串的长度
												add		eax,2					;将EAX指向字符串的位置
												mov		edx,eax
												invoke	WideCharToMultiByte,CP_ACP,\
														WC_COMPOSITECHECK,edx,ecx,\
														addr @szResName,sizeof @szResName,\
														NULL,NULL
												lea		eax,@szResName			;将转换后的字符串的地址放入AX
										.else
;***********************************************************************************************************************
;当资源类型为标准类型的时候查表得到资源的字符串说明
;***********************************************************************************************************************
												.if		eax <= 10h		;如果是预定义的资源类型
														dec		eax		;因为序号是从1开始,减一变成从0开始
														mov		ecx,sizeof szType
														mul		ecx
														add		eax,offset szType		;获得该资源类型对应的字符串位置的地址
												.else					;否则就是自定义类型
														invoke	wsprintf,addr @szResName,\
																addr szLevel1byID,eax																																																																									
														lea		eax,@szResName
												.endif
										.endif
										invoke	wsprintf,addr @szBuffer,addr szLevel1,eax			;将资源类型的字符串说明写入缓冲区
;***************************************************************************************************************************
;第二层:资源ID(或名称)
;***************************************************************************************************************************
								.elseif _dwLevel == 2
										mov		edx,[esi].Name1
										.if		edx & 80000000h
;***************************************************************************************************************************
;资源以字符串方式命名
;***************************************************************************************************************************
												and		edx,7fffffffh
												add		edx,_lpRes
												movzx	ecx,word ptr [edx]
												add		edx,2
												invoke	WideCharToMultiByte,CP_ACP,\				;转换UNICODE码
														WC_COMPOSITECHECK,edx,ecx,\
														addr @szResName,sizeof @szResName,\
														NULL,NULL
												invoke	wsprintf,addr @szBuffer,\
														addr szLevel2byName,addr @szResName
										.else
;***************************************************************************************************************************
;资源以ID命名
;***************************************************************************************************************************
												invoke	wsprintf,addr @szBuffer,\					;将ID写入缓冲区
														addr szLevel2byID,edx
										.endif
								.else
										.break	;如果是第三层则跳出循环,函数回到上一层递归
								.endif
								invoke	_AppendInfo,addr @szBuffer									;显示资源信息
								invoke	_ProcessRes,_lpFile,_lpRes,ebx,@dwNextLevel					;ebx现在指向下一层目录的IMAGE_RESOURCE_DIRECTORY结构
;***************************************************************************************************************************
;不是资源目录则显示资源详细信息(在第3层情况)
;***************************************************************************************************************************
						.else
								add		ebx,_lpRes						;ebx现在指向资源数据
								mov		ecx,[esi].Name1					;代码页(第三层目录)
								assume	ebx:ptr IMAGE_RESOURCE_DATA_ENTRY
								mov		eax,[ebx].OffsetToData			;获取指向资源数据的RVA
								invoke	_RVAToOffset,_lpFile,eax
								invoke	wsprintf,addr @szBuffer,addr szResData,\
										eax,ecx,[ebx].Size1							;显示资源数据的文件偏移代码页和长度
								invoke	_AppendInfo,addr @szBuffer
						.endif
						add		esi,sizeof IMAGE_RESOURCE_DIRECTORY_ENTRY			;esi移动到下一个目录项的位置
						pop		ecx
						dec		ecx
				.endw
;*************************************************************************************************************************
_Ret:					assume	esi:nothing
						assume	ebx:nothing
						popad
						ret
_ProcessRes				endp

_ProcessPeFile	proc	_lpFile,_lpPeHead,_dwSize
				local	@szBuffer[1024]:byte,@szSectionName[16]:byte
				
				pushad
				mov		esi,_lpPeHead
				assume	esi:ptr IMAGE_NT_HEADERS
;********************************************************************************************************************
;检测是否存在资源
;********************************************************************************************************************
				mov		eax,[esi].OptionalHeader.DataDirectory[8*2].VirtualAddress			;从数据目录中取得资源结构的RVA
				.if		! eax
						invoke	MessageBox,hWinMain,addr szErrNoRes,NULL,MB_OK				;如果结构为空则输出提示信息
						jmp		_Ret
				.endif
				push	eax
				invoke	_RVAToOffset,_lpFile,eax
				add		eax,_lpFile															;获取资源在文件中的地址
				mov		esi,eax
				pop		eax
				invoke	_GetRVASection,_lpFile,eax											;获取数据所在节区名字
				invoke	wsprintf,addr @szBuffer,addr szMsg,addr szFileName,eax
				invoke	SetWindowText,hWinEdit,addr @szBuffer
				invoke	_ProcessRes,_lpFile,esi,esi,1
;***************************************************************************************************************************
_Ret:
				assume	esi:nothing
				popad
				ret
				
_ProcessPeFile	endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
				
				
						
				




 代码编译链接出来后是这个样子的:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值