PE体系
PE结构&整体叙述
PE结构&导入表
PE结构&导出表
PE结构&基址重定位表
PE结构&绑定导入实现
PE结构&延迟加载导入表
重定位表定位
PE重定位:
向进程的虚拟内存加载PE文件时,文件会被加载到PE头的ImageBase所指的地址处。如果是加载的DLL(SYS)文件,且在ImageBase位置处已经加载了DLL(SYS)文件,那么PE装载器就会将其加载到其他未被占用的空间。此时就会发生基址重定位。
使用SDK或VC++创建PE文件,EXE默认的ImageBase为00400000,DLL默认的ImageBase为10000000,使用DDK创建的SYS文件默认的ImageBase为10000。
创建好进程后,因为EXE文件会首先加载进内存,所以EXE文件中无需考虑基址重定位问题。但是需要考虑ASLR(地址随机化)。对于各OS的主要系统DLL,微软会根据不同版本分别赋予不同的ImageBase地址,例如同一系统的kernel32.dll和user32.dll等会被加载到自身固有的ImageBase,所以系统的DLL实际上也不会发生重定位问题
重定位表:
重定位表为数据目录中注册的数据类型之一,其描述信息处于数据目录的第6个目录项中,
重定位表所在地址RVA=0x1F000
重定位数据大小=39C
RVA转FOA后可以得到文件偏移地址为0x9800
基本操作原理
- 在应用程序中查找硬编码的地址位置
- 读取数值后,减去ImageBase(VA->RVA)
- 加上实际加载地址(RVA->VA)
上面三个步骤即可完成PE重定位,其中最关键的是查找硬编码地址的位置,查找过程中会使用到PE文件内部的Relocation Tables(重定位表),它记录了硬编码地址便宜,是在PE文件构建中的编译/链接阶段提供的。通过重定位表查找,本质上就是根据PE头的“基址重定位表”项进行的查找。
重定位表项IMAGE_BASE_RELOCATION
与导入表类似,重定位表指针指向的位置就是一个数组,而不像导出表一样只有一个结构,这个数组的每一项都是如下结构:
IMAGE_BASE_RELOCATION STRUCT
VirtualAddress dd ? ;重定位内存页的起始RVA
SizeOfBlack dd ? ;重定位块的长度
//WORD TypeOffset[1]; //以注释形式存在,非结构体成员,表示在该结构体下会出现WORD类型的数组,并且该数组元素的值就是硬编码在程序中的地址偏移。
IMAGE_BASE_RELOCATION ENDS
以下是对每一项的详细解释。
IMAGE_BASE_RELOCATION.VirtualAddress
+0000h,双字。重定位块RVA。由于直接寻址指令比较多,所以在一些PE文件中,存在大量的需要修正的重定位地址。按照常规计算,每个地址占4个字节,如果有n个重定位项,那么需要的空间为4 x n。在一页中的 所有地址只需要12位(因为Win32页面大小为1000h,也就是4096字节,即2的12次方)。而这12位只需要用一个字就能表达出来。即如果有n个重定向项,则只需要 2 x n个地址+4字节的起始RVA +4字节的本页的重定位项个数。将以上两位情况的表达式分别是:
sum1=4xn
sum2=2xn+4+4
解释:
sum1式子:1个重定位项就需要4个字节(每个地址占4个字节),2个重定位项就需要8个字节,3个重定位项就需要12个字节,4个重定位项就需要16个字节……n个重定位项就需要4xn个字节
sum2式子:在一页里面偏移只需要两个字节,即一页里面出现1个重定位项,就需要2x1个位置;一页里面出现2个重定位项,就需要2x2个位置;一页里面出现n个重定位项,就需要2xn个位置;
然后表示在哪一页的话,需要4个字节的位置,即sum1=2xn+4
表示(n表示的是总的,并非哪一页)本页的重定位项个数,又需要4个字节的位置,即sum1=2xn+4+4
(上面的式子强调的是4个字节的空间,并非强调RVA。。。)
很明显,当有大量的重定位地址时,sum1远大于sum2。重定位表的存储方式就是第二种方式,字段 IMAGE_BASE_RELOCATION.VirtualAddress就是Sum2中的第一个4,也就是页面的起始RVA
IMAGE_BASE_RELOCATION.SizeOfBlock
+0004h,双字。这个有的资料显示是重定位块的大小,有的资料显示是该页面所有的重定向字段个数,也就是第二个4。接下来我们查看一下:
从这里看出的它是重定位块的大小,从9800~984C是第一个重定位块的大小,984D开始就是第二个重定位块的VirtualAddress
。因为如果说个数的话,因为后面一个占了2字节,那么4Cx2比这个大得多。
数组和数组直接并不是相邻的,比如页面1的IMAGE_BASE_RELOCATION
后,并不是页面2的IMAGE_BASE_RELOCATION
,而是页面1的所有重定位表项;每个项大小为1个字,每个字的高四位被用来说明此重定位项类型,十六才是需要重定位的数据在页面中的地址。高四位含义如下:
在实际的PE文件中,我们只能看到0和3这两种情况,也就是说这一项要么是对齐用,要么是需要全部全部修正。
重定位表第一项的起始代码页面RVA是0x11000
长度为4C
第一块的第一个重定位项的值为0x379F,其中高四位为3,转换为二进制代码是0011,表示该重定位值的高位和低位均需要修正。低十二位为修正地址,该地址加上地址再加上代码页面的起始地址即为代码在内存的实际位置VA值。
VirtualAddress(11000) + Offset(79F) = 1179F(RVA)
然后再找点后面的,尽量算在main函数里面去,这样可以调试,即0x3929,0x3938
VirtualAddress(11000) + Offset(929) = 11929(RVA)
VirtualAddress(11000) + Offset(938) = 11938(RVA)
然后去找到RVA为11929,RVA为11938地址处:
看一下RVA 11929处,RVA为11938处是否实际存在要执行PE重定位操作的硬编码地址:
总结:
1.查看一下RVA 11929处,RVA为11938处硬编码的值:
RVA 11929处硬编码为:0x00417BD4
2.读取数值后,减去ImageBase值:
0x00417BD4- 0x00400000= 0x17BD4
3.然后用它加上实际加载地址即可:
如:0x17BD4+0x00800000=0x817BD4
对于程序内硬编码的地址,PE装载器都做如上的处理,根据实际加载的内存地址修正后,将得到的值覆盖到同一位置上。对一个IMAGE_BASE_RELOCATION结构体的所有TypeOffset都做如上处理,且对RVA 1000~2000地址区域对应的所有硬编码地址都要进行PE重定位处理。如果TypeOffset值为0,说明一个IMAGE_BASE_RELOCATION结构体结束。至此,完成重定位流程。