PE文件(3)重定位表
重定位表概念
程序编译时每个模块有一个优先加载地址ImageBase,这个值是链接器给出的,因此链接器生成的指令中的地址是在假设模块被加载到ImageBase前提之下生成的,那么一旦程序没有将模块加载到ImageBase时,那么程序中的指令地址就需要重新定位,从而需要重定位表的修正。
对于EXE应用程序来说,在自己独立的4G进程空间中,它是肯定加载到ImageBase默认基地址上的。
但是动态链接库DLL就不一样了,动态链接库都是映射到别的应用程序的空间中的,所以出现要载入的基地址被应用程序占据了也是很正常的,这时它就必须进行重定位了。
重定位的算法功能可以描述为:将直接寻址指令中的需要修正地址加上模块实际装入地址与模块建议(默认)装入地址之差。
参与运算的是这3个数据:需要修正的机器码地址, 模块的实际装入地址,模块的建议(默认)装入地址ImageBase.
而模块的建议(默认)装入地址已经ImageBase中定义了,而模块的实际装入地址是Windows加载器确定的,
因此,PE文件的重定位表中唯一保存的就是一组需要修正的地址。
(位置)重定位表一般会被单独存放在一个以“.reloc”命名的节中,但这并不是必然的,因为重定位表放在其他节中也是合法的,但是如果重定位表存在的话,它的地址肯定可以在数据目录中找到。
重定位表结构
PE文件中重定位表的组织方法就是采用类似的按页分割的方法,从PE文件头的数据目录中得到重定位表的地址后,这个地址指向的就是顺序排列在一起的很多重定位块,每一块用来描述一个内存页中的所有重定位项。
重定位表由一个个的重定位块组成,如果重定位表存在的话,必定是至少有一个重定位块。因为每个块只负责定位0x1000大小范围内的数据,因此如果要定位的数据范围比较大的话,就会有多个重定位块存在。
每个块的首部IMAGE_BASE_RELOCATION定义如下:
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;
} IMAGE_BASE_RELOCATION;
VirtualAddress; //重定位内存页的起始RVA
SizeOfBlock; //重定位块的大小,
该结构体有两个成员:一个是地址,一个是大小。
如下图所示:一个重定位表由多个大小SizeOfBlock的Block组成,(不同块的SizeOfBlock大小不一)。每一个块记录了1000H即4KB大小的内存中需要重定位信息的地址(一页大小),这些地址以VirtualAdress为该页的基址,偏移地址占两个字节(1000H最多需要12bit即可:0~FFFH)。所以两个字节的低12位为偏移地址,而高4位就是一个标记,当此标记为0011(3)时低12为才有效,否则该2个字节可能是为了对齐而产生的,并且为对齐而产生的字节其值全为0。
实际上,这里高四位一般只出现0或3两种情况,0代表这两个字节是为对齐而产生的,3代表此时低12位是一个重定位项的偏移地址。
由于重定位表的SizeOfBlock大小不确定,新的Block的重定位信息的结构体接着上一个Block4字节对齐后开始,而当出现一个_IMAGE_BASE_RELOCATION结构体的值全为0时,表明重定位表结束。