PE文件重定位表

要链接PE文件中的重定位表的话,我们首先要知道哪些指令需要重定位,还有重定位的算法是怎样的。汇编中有些指令要用到内存地址,并且在编译的时候这些地址就固定在机器码里面了,如果我们程序的实际装入地址与模块的减一装入地址是相同的,那么这个指令就没问题,但是当实际装入地址与建议装入地址不相同时,如果没有重定位就会出问题了。

举个例子,假如我们在程序中写的代码是: mov eax,dword ptr [00400ffc] 。那么这条指令的机器码就是  ALFC0F4000  ,可以看到,地址已经是嵌入到机器码中了,如果程序的起始位置是建议装入地址00400000h,那么指令没有问题,但如果程序没有开始于建议装入地址,比如说程序开始地址在00500000h,那么正确的机器码应该是ALFC0F5000,而这个时候的机器码其实还是原来的ALFC0F4000,这样的话,程序运行就会出错了。

所以我们需要重定位,重定位的算法大致可以描述为:将直接寻址指令中的双字地址加上模块实际装入地址与模块建议装入地址之差。

那么,为了进行这个运算,我们需要三个数据:需要修正的机器码的地址,模块的建议装入地址,模块的实际装入地址。

知道这些后应该还有一个问题,重定位表中装的到底是什么呢?根据上面重定位需要的三个数据,模块的建议装入地址我们知道(在PE文件头的IMAGE_OPTIONAL_HEADER32结构中),模块的实际装入地址在程序装入后我们自然也可以知道,所以唯一少的就是需要修正的机器码的地址。

重定位表的结构定义如下:

IMAGE_BASE_RELOCATION    STRUCT
    VirtualAddress    dd    ?    ;重定位内存页的起始RVA
    SizeOfBlock       dd    ?    ;重定位块的长度
IMAGE_BASE_RELOCATION    ENDS   

那么这里有个问题,重定位不是应该装的都是需要修正的机器码的地址吗,为什么还有这种结构?其实想一下,正常情况下,一个地址要用到32个比特也就是4个字节,而一个程序中往往有很多的地址需要重定位,所以全都这样来的话会占用很多内存空间,所以为了优化,重定位表也采用了类似偏移的这种方法。就是因为在比较靠近的重定位表项中,32位的地址的高位总是相同,所以我们可以把这个高位相同的重定位项放一起,然后在同一个块中只存储他们的低位,这样就可以节省很多空间了。

那么按照优化后的方法到底是怎么存储的呢?首先我们知道一个内存页是4KB,也就是4096个字节,也就是2的12次方,所以当我们用一个块表示一个内存页的需要重定位的地址时,我们只需要那个内存页的起始RVA,然后一个重定位项只需要12个比特就能寻址整个内存页。但是一般我们需要对齐,所以一个重定位项用16比特来表示。

这样的话,重定位表就变成了首先一个IMAGE_BASE_RELOCATION头,然后后面跟着一堆重定位项,然后后面再紧接着另一个内存页的重定位块,依次这样下去。一个重定位块描述一个内存页。上述结构中第二个字段SizeOfBlock表示的重定位块的长度是包括了这个头的,所以我们计算重定位项数时应该应该用这个字段的数据减8再除以2。

另外,上面说了重定位项虽然12位就够寻址一个内存页,但是我们为了对齐就扩充成了16位,其实那高4位也没有浪费。高四位被用来描述重定位的种类了。重定位项高四位的含义:

重定位项高4位的含义
高4位的值         在 Windows.inc中出现的预定义值                                 含                           义
0IMAGE_REL_BASE_ABSOLUTE这个重定位项无意义,只是作为对齐用
1IMAGE_REL_BASE_HIGH重定位地址指向的双字中,仅仅高16位需要被修正
2IMAGE_REL_BASE_LOW重定位地址指向的双字中,仅仅低16位需要被修正
3IMAGE_REL_BASE_HIGHLOW重定位地址指向的双字中32位都需要被修正,这是修正算法中举例的情况
4IMAGE_REL_BASE_HIGHADJ需要32位,高16位位于偏移量,低16位位于下一个偏移量数组元素,组合为一个带符号数,加上32位的一个数,然后加上8000然后把高16位保存在偏移量的16位域内
5IMAGE_REL_BASE_MIPS_JMPADDR 
6IMAGE_REL_BASE_SECTION 
7IMAGE_REL_BASE_REL32 

虽然上面定义了这么多含义,但是一般在PE文件中只能看到0和3这两种情况。所有重定位块都以一个VirtualAddress字段为0的IMAGE_BASE_RELOCATION结构结束,所以PE文件的代码总是从装入地址的1000h 处开始的,如果代码直接从装入地址开始,那么这个内存页的RVA就是0,那么重定位表中描述这个内存页的IMAGE_BASE_RELOCATION结构中的VirtualAddress字段就为0了,这样会把第一个重定位块就当成结尾了。

现在我们可以根据这个写一个程序来显示PE文件中重定位表的信息,资源文件和框架用的和之前PE文件头博客中的是一样的。功能函数代码如下:


				.const
szMsg	db		'文件名:	%s',0dh,0ah
		db		'-------------------------------------------------------',0dh,0ah
		db		'重定位表所处的节:	%s',0dh,0ah,0
szMsgRelocBlk	db			0dh,0ah
		db		'-------------------------------------------------------',0dh,0ah
		db		'重定位基地址:		%08X',0dh,0ah
		db		'重定位项的数量:	%d',0dh,0ah
		db		'-------------------------------------------------------',0dh,0ah
		db		'需要重定位的地址列表',0dh,0ah
		db		'-------------------------------------------------------',0dh,0ah,0
szAll	db		'总共重定位的项:		%d',0dh,0ah,0
szMsgReloc		db		'%08X	',0
szCrlf			db		0dh,0ah,0
szErrNoReloc	db		'这个文件中不包括重定位信息!',0
				.code
include			_RvaToFileOffset.asm
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_ProcessPeFile	proc	_lpFile,_lpPeHead,_dwSize
				local	@szBuffer[1024]:byte,@szSectionName[16]:byte,@szBuall[256]:byte,@dwAll
				
				pushad
				mov		esi,_lpPeHead
				assume	esi:ptr IMAGE_NT_HEADERS		;PE文件头
;**************************************************************************************************************
;根据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														;获取重定位表的文件地址
				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
				mov		@dwAll,0
;*******************************************************************************************************************
;循环处理每个重定位块
;*******************************************************************************************************************
				.while	[esi].VirtualAddress
						cld
						lodsd								;mov eax,[esi]  esi+4
						mov		ebx,eax						;取得重定位块指向的页的RVA
						lodsd
						sub		eax,sizeof IMAGE_BASE_RELOCATION		;用重定位块的长度减去头的长度得到重定位项的长度
						shr		eax,1									;因为重定位项是两个字节为单位的,所以除以二得到重定位项的个数
						add		@dwAll,eax
						
						push	eax
						invoke	wsprintf,addr @szBuffer,addr szMsgRelocBlk,ebx,eax
						invoke	_AppendInfo,addr @szBuffer
						pop		ecx
						xor		edi,edi
						.repeat
								push	ecx
								lodsw					;mov eax,word ptr [esi]  esi+2		重定位项
								mov		cx,ax
								and		cx,0f000h		;将低12位清0,只保留用来描述重定位项种类的高4位
;**********************************************************************************************************************
;仅处理IMAGE_REL_BASED_HIGHLOW类型的重定位项(即重定位指向的双字都需要修改)
;**********************************************************************************************************************
								.if		cx == 03000h
										and		ax,0fffh		;将重定位项的高四位都清0,只留下真正的重定位信息
										movzx	eax,ax
										add		eax,ebx			;将重定位项里面的相对于该重定位块指向的页的起始位置的偏移加上该重定位块指向的页的起始位置的RVA得到重定位位置
								.else
										mov		eax,-1
								.endif
								invoke	wsprintf,addr @szBuffer,addr szMsgReloc,eax
								inc		edi
								.if		edi == 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
				invoke	wsprintf,addr @szBuall,offset szAll,@dwAll
				invoke _AppendInfo,addr @szBuall
				
_Ret:
				assume	esi:nothing
				popad
				ret
				
_ProcessPeFile	endp
						
				
				

程序运行:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值