WIN32汇编语言程序设计——获取重定位表信息的实例分析(CHAR17)

一、哪些代码需要重定位

 

在32位代码中,涉及直接寻址的指令都是需要重定位的。

二、重定位需要的数据

重定位需要的算法可以定义为:将直接寻址指令中的双字地址加上模块实际装入地址与模块建议装入地址之差。为了进行这个运算,需要有3个数据,首先是需要修正的机器码地址;其次是模块的建议装入地址;最后是模块的实际装入地址。

在这3个数据中,模块的建议装入地址已经在PE文件头中定义了,而模块的实际装入地址是Windows装载器确定的,到装载文件的时候自然会知道,所以第二个问题的答案很简单,那就是应该被保存在重定位表中的仅仅是需要修正的代码的地址。

三、 重定位表的位置

重定位表的位置和大小可以从数据目录中的第6个IMAGE_DATA_DIRECTORY结构中获取。

四、 重定位表的结构

PE文件中重定位表的组织方法就是采用按页分割的方法。因为在比较靠近的重定位表项中,32位指针的高位地址总是相同的,如果把这些相近表项的高位地址统一表示,那么就可以省略一部分的空间。

当按照一个内存页来分割时,在一个页面中寻址需要的指针位数是12位(一页等于4096字节,等于2的12次方),假如将这12位凑齐16位放入一个字类型的数据中,并用一个附加的双字来表示页的起始指针,另一个双字来表示本页中重定位项数的话,那么占用的总空间会是4+4+2×n 字节大小。

从PE文件头的数据目录中得到重定位表的地址后,这个地址指向的就是顺序排列在一起的很多重定位块,每一块用来描述一个内存页中的所有重定位项。 

每个重定位块以一个IMAGE_BASE_RELOCATION结构开头,后面跟着在本页面中使用的所有重定位项,每个重定位项占用16位的地址(也就是一个word),结构的定义是这样的:

IMAGE_BASE_RELOCATION STRUCT

VirtualAddress dd ? ;重定位内存页的起始RVA

SizeOfBlock dd ? ;重定位块的长度

IMAGE_BASE_RELOCATION ENDS 

VirtualAddress字段是当前页面起始地址的RVA值,本块中所有重定位项中的12位地址加上这个起始地址后就得到了真正的RVA值。SizeOfBlock字段定义的是当前重定位块的大小,从这个字段的值可以算出块中重定位项的数量,由于SizeOfBlock=4+4+2×n,也就是sizeof IMAGE_BASE_RELOCATION+2×n,所以重定位项的数量 n 就等于(SizeOfBlock-sizeof IMAGE_BASE_RELOCATION)÷2。

IMAGE_BASE_RELOCATION结构后面跟着的n个字就是重定位项,每个重定位项的16位数据位中的低12位就是需要重定位的数据在页面中的地址,剩下的高4位也没有被浪费,它们被用来描述当前重定位项的种类,其定义如表17.7所示。

表17.7 重定位项高4位的含义

虽然高4位定义了多种重定位项的属性,但实际上在PE文件中只能看到0和3这两种情况。

所有的重定位块最终以一个VirtualAddress字段为0的IMAGE_BASE_RELOCATION结构作为结束读者现在一定明白了为什么可执行文件的代码总是从装入地址的1000h处开始定义的了[2](比如,装入00400000h处的.exe文件的代码总是从00401000h开始,而装入10000000h处的.dll文件的代码总是从10001000h处开始),要是代码从装入地址处开始定义,那么第一页代码的重定位块的VirtualAddress字段就会是0,这就和重定位块的结束方式冲突了。 

 五、  分析书本实例(CHA17-Reloc文件夹—_ProcessPeFile)

1. _ProcessPeFile子程序

(1) 参数的意义

① 子程序参数1:已经读取到内存中的文件头的地址。通过内存映射读取到整个PE文件在内存中的起始位置。

② 子程序参数2:PE文件头在内存中的偏移地址。也就是在MAGE_DOS_HEADER 结构中取出的e_lfanew的值。

③ 子程序参数3:整个PE文件的长度。通过打开文件后拿到的句柄,用GetFileSize函数拿到文件的长度。
(2) 获得重定位表的位置并将其与esi关联。

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_ProcessPeFile	proc	_lpFile,_lpPeHead,_dwSize
		local	@szBuffer[1024]:byte,@szSectionName[16]:byte

		pushad
		mov	esi,_lpPeHead
		assume	esi:ptr IMAGE_NT_HEADERS
;********************************************************************
; 根据 IMAGE_DIRECTORY_ENTRY_BASERELOC 目录表找到重定位表位置
;********************************************************************
		mov	eax,[esi].OptionalHeader.DataDirectory[8*5].VirtualAddress
		.if	! eax
			invoke	MessageBox,hWinMain,addr szErrNoReloc,NULL,MB_OK
			jmp	_Ret
		.endif
		push	eax
		invoke	_RVAToOffset,_lpFile,eax	
		add	eax,_lpFile	;重定位表在PE文件中的地址
		mov	esi,eax
		pop	eax
		invoke	_GetRVASection,_lpFile,eax
		invoke	wsprintf,addr @szBuffer,addr szMsg,addr szFileName,eax
		invoke	SetWindowText,hWinEdit,addr @szBuffer
		assume	esi:ptr IMAGE_BASE_RELOCATION
;********************************************************************

(3) 开启一个循环,在IMAGE_BASE_RELOCATION结构的VirtualAddress字段不为零时[1],持续这个循环。首先将为DF标志位赋值零,即执行相关指令后SI+。然后用lodsd指令将esi位置VirtualAddress字段的双字内容移入eax中,完成后ESI+4指向结构的第二个字段SizeOfBlock。然后再次使用lodsd指令将第二个字段SizeOfBlock内容移入eax中,然后以eax的值减去IMAGE_BASE_RELOCATION结构的长度后除以2来得到重定位项的数量。最后将重定位数量赋值给ecx,将计数器edi归零。

;********************************************************************
; 循环处理每个重定位块
;********************************************************************
		.while	[esi].VirtualAddress
			cld
			lodsd			;eax = [esi].VirtualAddress
			mov	ebx,eax
			lodsd			;eax = [esi].SizeOfBlock
			sub	eax,sizeof IMAGE_BASE_RELOCATION
			shr	eax,1       ;将eax的值除以2放入eax中
			push	eax		;eax = 重定位项数量
			invoke	wsprintf,addr @szBuffer,addr szMsgRelocBlk,ebx,eax
			invoke	_AppendInfo,addr @szBuffer
			pop	ecx		;将重定位数量出栈到ecx
			xor	edi,edi
			

(4)将一个重定位块中的全部重定位机器码指针的RVA值拼接和显示到Richedit控件中。总体过程是校验重定位项的高4位,如果是3,则获取重定位项的低12位值,与重定位内存页的起始值相加,得到需要重定位机器码指针的RVA值,并拼接和显示到Richedit控件中。如果不是3,则该重定位项没有意义,只作为对齐使用,该位置的机器码将显示-1。重复以上过程,直到一个重定位块被校验完毕。因为在重复执行的过程中使用了lodsw,所以在重复执行结束后,esi会天然地指向最后一个重定位项后的位置,也就是下一个重定位块开始的位置,所以不需要再去调整esi指针。

			.repeat
				push	ecx	;将重定位数量入栈
				lodsw		;将重定位项的值移入ax
				mov	cx,ax	
				and	cx,0f000h	;取出重定位项的高4位
;********************************************************************
; 仅处理 IMAGE_REL_BASED_HIGHLOW 类型的重定位项
;********************************************************************
				.if	cx ==	03000h	;如果高4位的值是3,则意味着重定位地址指向的32位机器码都需要修正
					and	ax,0fffh	;取出ax中的低12位到ax
					movzx	eax,ax		
					add	eax,ebx		;将低12位的偏移地址加上起始RVA得到一个RVA地址
				.else
					mov	eax,-1		;如果最高4位不是3,该重定位项没有意义,只作为对齐使用
				.endif
				invoke	wsprintf,addr @szBuffer,addr szMsgReloc,eax
				inc	edi
				.if	edi ==	4	;每显示4个项目换行
					invoke	lstrcat,addr @szBuffer,addr szCrLf
					xor	edi,edi
				.endif
				invoke	_AppendInfo,addr @szBuffer
				pop	ecx
			.untilcxz
			.if	edi
				invoke	_AppendInfo,addr szCrLf
			.endif
		.endw

 注释:

[1] 因为最后一个全部为零IMAGE_BASE_RELOCATION结构标志着重定位表的结束,所以这里以零为循环结束的条件。

[2] 要是代码从装入地址处开始定义,那么第一页代码的重定位块的VirtualAddress字段就会是0,这就和重定位块的结束方式冲突了

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值