PE文件格式详解(七)――PE 文件的基底

来源:互联网 发布:ubuntu无法切换中文 编辑:程序博客网 时间:2024/04/28 08:14

这节是最后一节了,其实PE格式里面还有很多东西,比如资源,也是挺复杂的一个东东,不过我对它不感兴趣,写点儿自己感兴趣的东东吧――PE 文件的基底重定位(Base Relocations)。前面我们说过了每个模块有一个优先加载地址ImageBase,这个值是连接器给出的,因此连接器生成指令中的地址时是在假设模块被加载到ImageBase的前提之下生成的,这样一来一旦模块没有按照预期的加载到ImageBase,那么程序中的指令就需要修改。下面是一个例子:假设有一个可执行文件,基地址是0x400000。在这个image 偏移位置0x2134 处是一个指针,指向一个字符串。字符串始于实际地址0x404002 处,所以指针内容应该是0x404002。你可以把文件加载,但是加载器决定把它映像到实际地址0x600000处。连接器假设的基地址和实际加载的起始地址之间的差额称为delta。此例之delta 为0x20000。整个image 的位置提高了0x20000,其中的字符串当然也是(现在应该是0x604002)。所以指向字符串的指针就错误了,delta 应该加到指针值中。为了让Windows 加载器有能力做这样的调整,可执行文件内含许多个「基底重定位资料项」,给那些存放指针的位置(本例为0x2134)使用。加载器必须把delta 加到各个位址上。本例之中加载器应该把0x20000 加给原来的指针值(0x404002),并将结果0x604002写回原处。下图显示这个过程:

1205W34C60E131

图1

    上面出现了一个名词「基底重定位资料项」,加载器就是利用它才知道如果模块没有按照预期的位置被加载,那么哪些指令需要修改的。因此这节我们研究的重点将是「基底重定位资料项」。首先加载器也是通过数据目录来定位「基底重定位资料项」的。然后让我们来看看「基底重定位资料项」的真实面目吧。「基底重定位资料项」被包装为一系列连续区段,长短不一。每一个区段描述image 中的一个4K page (也就是一页)的重定位信息(因为每页中需要重地位的指令数目不同,所以区段的长短不一)。它们以一个IMAGE_BASE_RELOCATION 结构做为开始,格式如下:

DWORD VirtualAddress

    此一字段内含这些个「基底重定位资料项」的起始RVA 值。每一个「基底重定位资料项」的偏移位置(即下面的TypeOffset域的低12位,它是指令相对它所在页的第一条指令的偏移)必须加上此值才能够构成一个真正的RVA,指向「基底重定位资料项」。

DWORD SizeOfBlock

    结构大小,再加上所有跟随在后的「基底重定位数据项」(都是WORDs)。为了决定区块中的「基底重定位资料项」的个数,先把此值减去IMAGE_BASE_RELOCATION 结构大小(8 个字节),再除以2(WORD 大小)。如果此字段为44,就表示有18 个「基底重定位资料项」:

( 44 - sizeof(IMAGE_BASE_RELOCATION) ) / sizeof(WORD) = 18

WORD TypeOffset

    这并不是单独一个WORD,事实上它是一个WORDs 数组。数组元素个数可由上一个式子计算获得。每一个WORD 的最底部12 个位代表「基底重定位数据项」偏移位置,但必须再加上「基底重定位数据项」区块表头的VirtualAddress 字段值。最高的4个位是「基底重定位数据项」的型态。对于在Intel CPUs 中执行的PE 文件,你将看到两种型态:

0(IMAGE_REL_BASED_ABSOLUTE):此一「基底重定位数据项」无意义,只是用来充数而已,使所有「基底重定位数据项」的总共大小成为DWORD 的倍数。

3(IMAGE_REL_BASED_HIGHLOW):把delta 值加到欲计算的RVA 值去。另外还有其它型态,在WINNT.H 中定义。它们大部份是给i386 以外的CPU 使用。

下面列出一些「基底重定位数据项」,请注意其中的RVA 值已经被IMAGE_BASE_RELOCATION 结构中的VirtualAddress 字段校正过了。

Virtual Address: 00001000 Size: 0000012C

00001032 HIGHLOW

0000106D HIGHLOW

000010AF HIGHLOW

000010C5 HIGHLOW

// Rest of chunk omitted...

Virtual Address: 00002000 Size: 0000009C

000020A6 HIGHLOW

00002110 HIGHLOW

00002136 HIGHLOW

00002156 HIGHLOW

// Rest of chunk omitted...

Virtual Address: 00003000 Size: 00000114

0000300A HIGHLOW

0000301E HIGHLOW

0000303B HIGHLOW

0000306A HIGHLOW

// Rest of chunk omitted...

    通过上面的实例我们可以看出相邻区段的Virtual Address整好相差0x1000,也就是4k,这证实了上面所说的“每一个区段描述image 中的一个4K page (也就是一页)的重定位信息”同时我们还可以看到第一个区段的Virtual Address是00001000,这整好是通常的.text段的RVA。

    现在问题基本明了了,一旦模块没有加载到预期的位置,那么加载器就会根据「基底重定位数据项」去修正哪些修要修正的指令,这样程序就可以正确执行了 。