PE 重定位表

参考文章:https://blog.csdn.net/apollon_krj/article/details/77370452

重定位表的作用:

小知识:一般每个系统自带的dll,比如kernel32.dll等,为了不造成imagebase的相同,每个系统dll加载都有个固定的高地址空间,一般地址都是为0x7开头

重定位表(Relocation Table)用于在程序加载到内存中时,进行内存地址的修正。

为什么要进行内存地址的修正?

我们举个例子来说:test.exe可执行程序需要三个动态链接库dll(a.dll,b.dll,c.dll),假设test.exe的ImageBase为400000H,而a.dll、b.dll、c.dll的基址ImageBase均为1000000H。

那么操作系统的加载程序在将test.exe加载进内存时,直接复制其程序到400000H开始的虚拟内存中,接着继续加载a.dll、b.dll、c.dll:假设先加载a.dll,如果test.exe的ImageBase + SizeOfImage + 1000H不大于1000000H,则a.dll直接复制到1000000H开始的内存中;当b.dll加载时,虽然其基址也为1000000H,但是由于1000000H已经被a.dll占用,则b.dll需要重新分配基址,比如加载程序经过计算将其分配到1200000H的地址,c.dll同样经过计算将其加载到150000H的地址。如下图所示:

重点来了:但是b.dll和c.dll中有些地址是根据ImageBase固定的,被写死了的,而且是绝对地址不是相对偏移地址

比如b.dll中存在一个call 0X01034560,这是一个绝对地址,其相对于ImageBase的地址为δ = 0X01034560 - 0X01000000 = 0X34560H;而此时的内存中b.dll存在的地址是1200000H开始的内存,加载器分配的ImageBase和b.dll中原来默认的ImageBase(1000000H)相差了200000H,因此该call的值也应该加上这个差值,被修正为0X01234560H,那么δ = 0X01234560H - 0X01200000H = 0X34560H则相对不变。否则call的地址不修正会导致call指令跳转的地址不是实际要跳转的地址,获取不到正确的函数指令,程序则不能正常运行。

一般每一个PE文件都有一个重定位表。当加载器加载程序时,如果加载器为某PE(.exe、.dll)分配的基址与其自身默认记录的ImageBase不相同,那么该程序文件加载完毕后就需要修正重定位表中的所有需要修正的地址。如果加载器分配的基址和该程序文件中记录默认的ImageBase相同,则不需要修正,重定位表对于该dll也是没有效用的。

所以说当PE文件分配的基址与其自身默认记录的ImageBase不相同,并且其中存在上面类似的call(绝对地址)的话,那么需要修正的地址可能有很多,所以用一张表记录那些"绝对地址"的地址,将来加载进内存时,可能需要被修正,那么存储这些"绝对地址"的表就叫做重定位表!

例子代码:

#include <stdio.h>

int main(){
	printf("1"); //断点分析
	return 0;
}
8:        printf("1");
00401028   push        offset string "%x" (0042201c) 
0040102D   call        printf (00401080) //可以发现该call的位置是固定的,0x00401080
00401032   add         esp,4

当前程序的ImageBase是0x400000,所以如果程序能够按照预定的ImageBase来加载的话,那么就不需要重定位表,这也是为什么exe很少有重定位表,而DLL大多都有重定位表的原因

那么如果没有预定呢?

结果就是该函数无法进行调用,调用的位置还是0x00401080,但是这个位置是别的进程在使用!

所以一个EXE中,需要修正的地方会很多,那我们如何来记录都有哪些地方需要修正呢?

重定位就是来解决这样的事情!

重定位就是 我们要把偏移,以及各种需要修正的位置,变为正确的

重定位表中所记录的信息就是记录了 在哪个地址上是需要来进行重定位修复的


重定位表的结构

重定位表位于数据目录项第六个结构,该结构体中的虚拟地址所指向的结构体名称就是_IMAGE_BASE_RELOCATION

typedef struct _IMAGE_BASE_RELOCATION {
    DWORD   VirtualAddress; //
    DWORD   SizeOfBlock; //存储的值以字节为单位,决定重定位块的大小
} IMAGE_BASE_RELOCATION;

这里和导出表有不一样的地方:

1、重定位表是由许多个重定位块组成的

2、重定位表的特点,结尾的时候会有VirtualAddressSizeOfBlock以4个字节的0结尾来作为结束标志的重定位块

void printfRELOCATION(PVOID pFileBuffer){

    PIMAGE_DOS_HEADER pDosHeader = NULL;    
    PIMAGE_NT_HEADERS pNTHeader = NULL; 
    PIMAGE_FILE_HEADER pPEHeader = NULL;    
    PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;  
    PIMAGE_SECTION_HEADER pSectionHeader = NULL;
    PIMAGE_BASE_RELOCATION pRelocationDirectory = NULL;
    DWORD FOA;
    DWORD RVA_Data;
    int NumberOfRelocation = 0;
    PWORD Location = NULL;
    WORD reloData;
    int i;

    pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
    pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);
    pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);  
    pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER); 
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + IMAGE_SIZEOF_NT_OPTIONAL_HEADER);

	// _IMAGE_DATA_DIRECTORY中的指向重定位表的虚拟地址转换为FOA地址
	//printf("%x\n",pOptionHeader->DataDirectory[5].VirtualAddress);

	printf("pRelocationDirectory_RVA:%x\n",pOptionHeader->DataDirectory[5].VirtualAddress);
	RVA_TO_FOA(pFileBuffer,pOptionHeader->DataDirectory[5].VirtualAddress,&FOA);
	printf("pRelocationDirectory_FOA:%x\n", FOA);

	pRelocationDirectory = (PIMAGE_BASE_RELOCATION)((DWORD)pFileBuffer+FOA); //定位第一张重定位表 文件中的地址

	while(pRelocationDirectory->SizeOfBlock && pRelocationDirectory->VirtualAddress){
		printf("VirtualAddress    :%08X\n", pRelocationDirectory->VirtualAddress);
		printf("SizeOfBlock       :%08X\n", pRelocationDirectory->SizeOfBlock);
		printf("================= BlockData Start ======================\n");

		
		NumberOfRelocation = (pRelocationDirectory->SizeOfBlock - 8)/2;// 每个重定位块中的数据项的数量

		Location = (PWORD)((DWORD)pRelocationDirectory + 8); // 加上8个字节


		for(i=0;i<NumberOfRelocation;i++){
			if(Location[i] >> 12 != 0){ //判断是否是垃圾数据
				// WORD类型的变量进行接收
				reloData = (Location[i] & 0xFFF); //这里进行与操作 只取后后面12位的数字
				
				RVA_Data = pRelocationDirectory->VirtualAddress + reloData; //这个是RVA的地址
				
				RVA_TO_FOA(pFileBuffer,RVA_Data,&FOA);
	
				printf("第[%04X]项  数据项的数据为:[%04X]  数据属性为:[%X]  RVA的地址为:[%08X]  重定位的数据:[%08X]\n",i+1,reloData,(Location[i] >> 12),RVA_Data,*(PDWORD)((DWORD)pFileBuffer+FOA));
			}
		}

		
		pRelocationDirectory = (PIMAGE_BASE_RELOCATION)((DWORD)pRelocationDirectory + (DWORD)pRelocationDirectory->SizeOfBlock); //上面的for循环完成之后,跳转到下个重定位块 继续如上的操作
	}
}
posted @ 2020-02-18 21:55  zpchcbd  阅读(701)  评论(0编辑  收藏  举报