PE文件(十)重定位表

重定位表的引入

程序加载过程

在win32下,每一个PE文件(其可能由多个子PE文件组成)在运行时,操作系统会给分配一个独立的4GB虚拟内存,内存地址从0x00000000到0xFFFFFFFF。其中低2G为用户程序空间,高2G为操作系统内核空间。并且操作系统会将该文件中数据拉伸成内存中数据,从ImageBase开始,分配SizeOfImage大小空间

当一个主PE文件由很多个子PE文件组成,当我们运行主PE文件时,所有的PE文件共享操作系统分配的一个4GB虚拟空间。如下图我们用OD打开ipmsg.exe程序去查看其模块,可以发现,ipmsg.exe由很多其他PE文件组成,这些文件也叫做模块

在上图中,各PE文件的base也就是其在内存中的起始位置,size也就是其在内存中大小

上图是一个PE文件在4GB虚拟内存中的分布,它由多个PE文件组成。从ImageBase(0x00400000)开始,分配空间SizeOfImage(0x3D000)大小

之后装载需要用到的.dll:把ws2help.dll装载到从ImageBase(0x71A10000)开始,分配空间大小为SizeofImage(0x8000)。其他.dll也是如此

最后把EIP指向EOP(AddressOfEntryPoint),这个程序就可以执行了

注意:我们可以自定义每个PE文件的sizeofimage,具体流程是:打开VC->右键你的项目->setting->选择Link->Category设置为Output->在Base address选项中就可以自定义ImageBase了,之后这个程序编译以后,ImageBase就变成了我们所修改的了

程序编译时的问题

问题一:DLL装载地址冲突

一般情况下一个PE文件自身的子.exe文件的ImageBase不会和别的子.exe文件的ImageBase发生冲突

但是默认情况下DLL的ImageBase为0x10000000,因此如果一个PE文件的多个DLL没有合理的修改分配装载起始地址,就可能出现其ImageBase都是同一个地址,造成装载冲突

注意:如果一个DLL在装载到虚拟内存中时,操作系统发现其他DLL已经占用这块空间了,那么这个DLL会依据模块对齐粒度,往后找空余的空间存入,此时,.dll便有了其内存空间,这个过程,本人称作内存再分配。在程序每次运行时,内存再分配的情况都是不一样的

模块对齐粒度:和结构体的字节对齐一样是为了提高搜索的速度(空间换时间),模块间地址也是要对齐的。模块对齐粒度默认 为0x10000,也就是64K

问题二:编译后的绝对地址

一个.dll或.exePE文件中的全局变量,在程序编译完成后,其全局变量的绝对地址就写入PE文件了。如以下文件中a(人为的全局变量)和%d(系统的全局变量),编译完后地址值都是不变的。

 

假设如果这个PE文件在装载时,某DLL装载地址冲突,系统会对该.dll文件进行再分配内存空间。由于这个.dll文件的全局变量地址在编译完成后就不再改变,当程序执行时,程序会按这个绝对地址去寻找全局变量使用。而该.dll文件由于装载时再分配内存,就会出现找不到这个全局变量的问题。

不单是全局变量,.DLL中对外提供的函数的地址在编译完后也是不再改变的,而如果此时DLL装载时其分配内存空间发生了改变了,通过这些不变的函数地址也找不到需要的函数了

重定位表引入

所以如果一个PE文件出现了内存装载冲突的情况,那么就需要重定位表,来记录下来,有哪些地方的数据需要做修改、重新定位,保证在内存再分配后,操作系统能正确找到这些数据

比如再问题二中:这个PE文件各子PE文件没有按照其本来ImageBase去装载,而是进行了内存再分配。那么我们写入的全局变量a的存储地址就会发生变化,但是由于硬编码已经生成:A1 30 4A 42 00,那么重定位表就会把30 4A 42 00这个数据的地址记下来,等到运行时操作系统会根据重定位表找到这个数据,做一个重定位修改,即把0x00424a30这个绝对地址修改成它现在所在的绝对地址,保证全局变量a可以被准确找到,修改的操作由操作系统进行负责。

因为一个PE文件的.exe子PE文件一般只有一个,且是最先装载,所以装载位置和其ImageBase是一致的,不需要内存再分配,而.dll子PE文件有很多,所有操作系统需要考虑其装载的位置是不是预期的位置,如果不是,.dll子PE文件就需要提供重定位表

重定位表的位置

找到可选PE头中的最后一个成员数据目录项(有16个元素的结构体数组):找到第6个结构体,就是重定位表的数据目录

下图便是重定位表的位置:

根据重定位表数据目录的VirtualAddress,便可以获取重定位表的地址

重定位表结构

typedef struct _IMAGE_BASE_RELOCATION

{

DWORD VirtualAddress;

DWORD SizeOfBlock;

//在这里还有一堆未知的数据

} IMAGE_BASE_RELOCATION;

typedef IMAGE_BASE_RELOCATION ,* PIMAGE_BASE_RELOCATION;

该结构体虽然叫做重定位表结构体,但实际上本人认为它叫做块结构体更合适

这是因为一个完整重定位表是由多个块组成的,最后一个块的VirtualAddress和SizeOfBlock都是0x00000000,表示重定位表结束。如下图便是一个完整的重定位表:

1.VirtualAddress

宽度为4字节

由于4GB虚拟内存地址需要32位即4字节的数才能完整表示,所以当有10000个地方要修改,就需要记录下这10000个地方的地址,一个地址4字节,共需大小10000 * 4 = 40000字节空间,如此记录,一张表所占内存大小就过大了

因此操作系统会把一个PE文件分页,内存中一个页的大小为0x1000字节,相当于把文件分成了一小页一小页的。

那么如果在一页中有需要重定位的地方,重定位表就会给这个页安排一个块,这个块的VirtualAddress存储了此页的偏移起始地址(RVA)。由于一页的大小只有0x1000字节(4096),所以用12位二进制数就可以表示的下4096个地址,此时记录地址所需要的空间就大大减少了。由于内存对齐的缘故,所以把这个值用16位存放。多出来的高4位可以用来表示其他的含义,低12位表示需要修改的地方相对于所在页的偏移地址故具体项占16位,

2.SizeOfBlock

表示每个块的大小,单位为字节

3.具体项

在结构体中未知的一堆数据中,每两个字节叫一个具体项。

具体项的高4位表示类型:值为3,即0011。一个块中有多少个高4位为0011的具体项,就表示这个块当中有多少个地方需要做重定位修改

当具体项的值为0时,说明这个数据项的2个字节的数据用来做数据对齐用,可以不用修改。因此我们只需要关注高4位值为3的具体项就可以了。

一块中一共有多少个具体项用(此块的SizeofBlock - 4 - 4 )/ 2进行计算

具体项的低12位的值表示要修改的地方相对于所在块的VirtualAddress(也是项所在页)的偏移地址,该值加上该块的VirtualAddress的结果便是要修改的地方的在内存的绝对地址,这个地址上的数据则需要修改做重定位。

综上所述:一页中如果有要重定位的地方,重定位表给此页安排一块(一块对应一页)。此块的VirtualAddress存储此页的起始地址;具体项占16位,高4位表示类型,低12位表示要修改的地方相对于所在页的偏移地址。

页、块、节的关系

一个PE文件(可执行程序)运行时,装入虚拟内存时操作系统会对程序进行分页。重定位表会根据页进行分块。但程序中的节跟分页和分块没有任何关系

我们用LordPE打开一个有重定位表的PE文件,查看重定位表

发现:这个重定位表分了很多块,第175块中记录的是偏移地址为0xAF000的页中要修改重定位的地方,这些要修改的地方在.text节中。第176块中记录的是偏移地址为0xB000的页中要修改重定位的地方,这些要修改的地方在.data节中

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值