PE重定位

PE文件向进程中载入时,会被加载到PE头的ImageBase所指的地址处,如果加载的是DLL/SYS文件,该地址已经被其他DLL占用,那么就需要进行PE文件重定位.

PE文件在重定位时会使用基址重定位表,将硬编码在PE程序中的内存地址进行重新计算,让内存地址随加载地址变化而变化.

DLL/SYS重定位

假设TEST.EXE在载入时,A.DLL被载入到TEST.EXE的10000000地址处,此时B.DLL试图加载到相同地址,PE装载器就会将其装载到另一个未被占用的地址(该地址不能够和TEST.EXE和A.DLL所占的内存重叠).

image-20240122224357356

EXE重定位

创建好进程后,EXE首先被加载到内存,因此无需考虑重定位问题.

另外,Windows Vista之后引入了ASLR机制,这会让每次加载都会在不同的随机地址.

重定位操作

原理很简单:

image-20240122224618606

查找硬编码时,使用到PE文件中的基址重定位表,它记录了硬编码地址偏移(位置),在PE文件构建(编译/链接)时制定该表.

基址重定位表

该表位于PE头的DataDirectory数组的第6个元素:

image-20240122225417440

跳转到该地址可以看到基址重定位表的实际内容:

image-20240122225631757

IMAGE_BASE_RELOCATION结构体

重定位表中罗列了程序中所有的硬编码地址的偏移(位置).重定位表是IMAGE_BASE_RELOCATION结构体数组.

image-20240122225829232

第一个成员VirtualAddress为基准地址,实际上是一个RVA;第二个SizeOfBlock为重定位块的大小;最后一项为注释,并非结构体成员,表示结构体之下会出现WORD类型的数组,其元素值为硬编码的地址偏移.

根据下表的例子,可以知道TypeOffset数组成员的基准地址(起始地址)RVA为1000,块总大小为150.块末尾以0结束:

image-20240122230438099

每个元素值为2字节大小,最高4位为Type,低12位是Offset.

PE中Tpye常见为3(IMAGE_REL_BASED_HIGHLOW),PE+文件常见为A(IMAGE_REL_BASED_ABSOLUTE).

image-20240122231056230

接下来就可以定位硬编码了,公式如下:

VirtualAddress(1000)+Offset(420)=1420(RVA)

跳转到该地址就可以找到硬编码地址:

image-20240122231527291

然后就可以根据实际加载地址去进行重新计算了.其余的硬编码都如此依次处理.