首页
论坛
课程
招聘
[调试逆向] [系统底层] [原创]PE文件格式之重定位表
2018-9-10 19:19 6442

[调试逆向] [系统底层] [原创]PE文件格式之重定位表

2018-9-10 19:19
6442
这几天跟女朋友分手了,心里不爽,丧了几天……
不说废话开始:

       一、重定向表的作用:

    这个在网上有很多种解释,那我也说一下自己对这个表的解释,程序运行的时候一般有两种方式来调用函数就是OD的那个CALL,一个是基址+偏移,另一种就是写死的函数地址比如 CALL 0x78441354这样的,对于这样一个exe程序,他有两个dll:a.dll b.dll 如果两个的dll的基址(ImageBase)都是10000000h但是a.dll先加载了占用了这个地址,那么当b.dll加载的时候就会加载到其他的位置,比如12000000H的位置。
     但是在b.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指令跳转的地址不是实际要跳转的地址,获取不到正确的函数指令,程序则不能正常运行。
      由于一个dll中的需要修正的地址不止一两个,可能有很多,所以用一张表记录那些“写死”的地址,将来加载进内存时,可能需要一一修正,这张表称作为重定位表,一般每一个PE文件都有一个重定位表。当加载器加载程序时,如果加载器为某PE(.exe、.dll)分配的基址与其自身默认记录的ImageBase不相同,那么该程序文件加载完毕后就需要修正重定位表中的所有需要修正的地址。如果加载器分配的基址和该程序文件中记录默认的ImageBase相同,则不需要修正,重定位表对于该dll也是没有效用的。比如test.exe和a.dll的重定位表都是不起作用的(由于一般情况.exe运行时被第一个加载,所以exe文件一般没有重定位表,但是不代表所有exe都没有重定位表)。同理如果先加载b.dll后加载a.dll,那么b.dll的重定位表就不起作用了。 


二、重定位表的结构解析:

重定向表位于数据目录项的第6位:
typedef struct _IMAGE_BASE_RELOCATION {
    DWORD   VirtualAddress;//RVA
    DWORD   SizeOfBlock;
} IMAGE_BASE_RELOCATION,* PIMAGE_BASE_RELOCATION;
#define IMAGE_SIZEOF_BASE_RELOCATION         8
该结构体有两个成员:一个是地址,一个是大小。如下图所示:一个重定位表由多个大小SizeOfBlock的Block组成,(不同块的SizeOfBlock大小不一)。每一个块记录了1000H即4KB大小的内存中需要重定位信息的地址(一页大小),这些地址以VirtualAdress为该页的基址,偏移地址占两个字节(1000H最多需要12bit即可:0~FFFH)。所以两个字节的低12位为偏移地址,而高4位就是一个标记,当此标记为0011(3)时低12为才有效,否则该2个字节可能是为了对齐而产生的,并且为对齐而产生的字节其值全为0。

不会插入表格,贴个图,大家凑活着看,这个重定位表是通过页表来存储的信息的,而且有16个二进制位来表示,高四位是用来存储标记是否需要重定向的标记,VirtualAddress就相当于基址低12位就相当于偏移,这样就能表示出一块需要重定位的函数地址数据,如果标记是0那么就代表这个数据是用来填充保证对齐的数据无意义。

三、解析:

 1、通过IMAGE_DATA_DIRECTORY结构的VirtualAddress 属性 找到第一个IMAGE_BASE_RELOCATION
2、判断一共有几块数据: 最后一个结构的VirtualAddress与SizeOfBlock都为0
3、具体项 宽度:2字节  也就是这个数据  内存中的页大小是1000H 也就是说2的12次方 就可以表示  一个页内所有的偏移地址 具体项的宽度是16字节 高四位  代表类型:值为3 代表的是需要修改的数据 值为0代表的是  用于数据对齐的数据,可以不用修改.也就是说 我们只关注  高4位的值为3的就可以了.
4、VirtualAddress 宽度:4字节 当前这一个块的数据,每一个低12位的值+VirtualAddress 才是 真正需要修复的数据的RVA 真正的RVA = VirtualAddress + 具体项的低12位
5、SizeOfBlock 宽度:4字节 当前块的总大小 具体项的数量 = (SizeOfBlock - 8)/2 
         但是这个地方查资料,在吾爱上有个大牛说重定向表是多少个就是多少个并不存在结束的标记位 直至某个块首结构的VirtualAddress为 0,表明重定位表结束。  这个地方没明白有点懵懂……(链接:https://www.52pojie.cn/thread-562035-1-1.html)
         上代码:
         
PIMAGE_DOS_HEADER  pDosHeader = NULL;
	PIMAGE_NT_HEADERS  pNTHeader = NULL;
	PIMAGE_FILE_HEADER pFileHeader = NULL;
	PIMAGE_OPTIONAL_HEADER pOptionalHeader = NULL;
	PIMAGE_DATA_DIRECTORY pDataDirectory = NULL;
	PIMAGE_BASE_RELOCATION     pRelocation = NULL;
	PIMAGE_SECTION_HEADER   pSectionHeader = NULL;

	DWORD RelocationFOA = 0;

	LPVOID pFileBuffer  = NULL;

	//打开文件
	ReadPEFile(INDLLPATH,&pFileBuffer);
	
	//判断文件是否打开成功
	if (!pFileBuffer){
		printf("文件打开失败/n");
		free(pFileBuffer);
	}


	//给各个头赋值

	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
	pFileHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 4);
	pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)(((DWORD)pFileHeader) + IMAGE_SIZEOF_FILE_HEADER);
	pDataDirectory = (PIMAGE_DATA_DIRECTORY)((DWORD)pOptionalHeader + 96);
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);

	for (size_t i = 0; i < 5; i++, pDataDirectory++);
	
	
	RelocationFOA = RVATOFOA(pDataDirectory->VirtualAddress, RelocationFOA, pFileBuffer);
	
	
	//定位到重定位表
	pRelocation = (PIMAGE_BASE_RELOCATION)((DWORD)pFileBuffer+RelocationFOA);
	
	BYTE secName[9] = { 0 };
	for (int i = 1; pRelocation->VirtualAddress && pRelocation->SizeOfBlock; i++)
	{
		DWORD size = (pRelocation->SizeOfBlock - 8) / 2;
		//先判断这个页属于哪个节里边 打印信息
		DWORD upper = 0;
		DWORD lower = 0;
		DWORD FOA = 0;

		FOA = RVATOFOA(pRelocation->VirtualAddress, FOA, pFileBuffer);
		for (size_t j = 0; j < pFileHeader->NumberOfSections; j++,pSectionHeader++)
		{
			lower = RVATOFOA(pSectionHeader->VirtualAddress, lower,pFileBuffer);
			upper = RVATOFOA(pSectionHeader->VirtualAddress + pSectionHeader->Misc.VirtualSize, upper, pFileBuffer);
			if (FOA >= lower&&FOA <= upper){
				memcpy(secName, pSectionHeader->Name, 9);   //将节的名字复制到名字空间
				break;
			}
		}

		//打印出所有的重定向表的所在节的名称
		printf("\t第%d个重定向表\t\t所在的节的名称%s\t\t\n",(i+1),secName);
		//定位到第一个块的首字节
		WORD* RelocaAddr = (WORD*)((BYTE*)pRelocation + 8);
    	printf("=================第%d个页中所有的重定向数据============================\n",i);
		for (DWORD  t = 0; t < size ; t++)
		{
			    //先判断是真正的重定向函数的基址,还是用来填充做为对齐的数据
			    //确定重定向表的函数物理偏移
				//低12位是函数的偏移   物理地址=基址+偏移
			DWORD RelocaAdd = (RelocaAddr[t] & 0X0FFF) + FOA;
			WORD test = RelocaAddr[t] >> 12;
			if (test == 0){
				printf("第%d位\t\t类型[%d]\n", t + 1, test);
			}
			else
			{
				printf("\t\t第%d位\t\t类型[%d]\t\t地址[%x]\n",t+1,test,RelocaAdd);
			}
		}
		memset(secName, 0, 9);
		//进行下一页的判断
		pRelocation = (PIMAGE_BASE_RELOCATION)((BYTE*)pRelocation + pRelocation->SizeOfBlock);
		
	}
	

}
运行结果:

大概就是这个思路,如果有什么不对的地方希望前辈可以指正,毕竟不断改错才能进步嘛。我接着去沉浸在失恋的阴影中了……


《0day安全 软件漏洞分析技术(第二版)》第三次再版印刷预售开始!

最后于 2018-9-21 13:15 被kanxue编辑 ,原因:
收藏
点赞0
打赏
分享
最新回复 (17)
雪    币: 9191
活跃值: 活跃值 (3866)
能力值: ( LV13,RANK:365 )
在线值:
发帖
回帖
粉丝
TkBinary 活跃值 5 2018-9-10 22:52
2
0
就佩服老哥.女朋友有么用.代码才是真爱.
雪    币: 48
活跃值: 活跃值 (18)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wbqsohucom 活跃值 2018-9-12 17:29
3
0
mark
雪    币: 6348
活跃值: 活跃值 (186)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
小艾 活跃值 1 2018-9-12 19:22
4
0
IMAGE_BASE_RELOCATION 结构后面是隐藏数组
Count = (SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(USHORT);
雪    币: 2034
活跃值: 活跃值 (37)
能力值: ( LV7,RANK:104 )
在线值:
发帖
回帖
粉丝
1lu29e 活跃值 2018-9-12 19:28
5
0
小艾 IMAGE_BASE_RELOCATION 结构后面是隐藏数组 Count = (SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(USHO ...
隐藏数组?怎么讲?
雪    币: 2034
活跃值: 活跃值 (37)
能力值: ( LV7,RANK:104 )
在线值:
发帖
回帖
粉丝
1lu29e 活跃值 2018-9-12 19:28
6
0
张新琪 就佩服老哥.女朋友有么用.代码才是真爱.[em_13]
还是琪哥看得开啊
雪    币: 205
活跃值: 活跃值 (21)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
adylee 活跃值 2018-9-13 15:36
7
0
mark
雪    币: 670
活跃值: 活跃值 (55)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
瀚海云烟 活跃值 1 2018-9-13 16:27
8
0
女朋友的作用:第二杯半价 
最后于 2018-9-13 16:27 被瀚海云烟编辑 ,原因:
雪    币: 1280
活跃值: 活跃值 (280)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
开花的水管 活跃值 2018-9-18 09:06
9
0
瀚海云烟 女朋友的作用:第二杯半价&nbsp;
不要说得这么直白...
雪    币: 5
活跃值: 活跃值 (17)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
yber 活跃值 2018-10-1 14:09
10
0
Mark
雪    币: 233
活跃值: 活跃值 (17)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xingbing 活跃值 2018-10-1 17:23
11
0
写的真好,学习了。
雪    币: 291
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
米米_146496 活跃值 2019-6-30 05:02
12
0
请问如何打印物理页编号?
雪    币: 240
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
lai0301 活跃值 2019-6-30 09:24
13
0
看看!!!!!!
雪    币: 55
活跃值: 活跃值 (181)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
killpy 活跃值 2 2019-7-1 14:17
14
0
666啊 
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_lowsphez 活跃值 2020-3-10 15:39
15
0
 pDataDirectory = (PIMAGE_DATA_DIRECTORY)((DWORD)pOptionalHeader + 96);
RelocationFOA = RVATOFOA(pDataDirectory->VirtualAddress, RelocationFOA, pFileBuffer);
你这里转的是DataDirectory[16]中的第一个数据项,搞笑呢?
雪    币: 1190
活跃值: 活跃值 (236)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
htpidk 活跃值 2020-3-10 16:51
16
0
mb_lowsphez pDataDirectory = (PIMAGE_DATA_DIRECTORY)((DWORD)pOptionalHeader + 96); RelocationFOA = RVATOFOA(pD ...
是不是漏看了这一行 for (size_t i = 0; i < 5; i++, pDataDirectory++);
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_lowsphez 活跃值 2020-3-13 14:44
17
0
htpidk 是不是漏看了这一行 for (size_t i = 0; i < 5; i++, pDataDirectory++);
     我是这么写的:rva转foa
       DWORD nBaseRLC_Addr_rva = pNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
       DWORD nBaseRLC_Addr_foa = RVATOFOA(nBaseRLC_Addr_rva, pFileBuffer);
另外判断特征是不是【3】我是这么写的:

                       PWORD recaddr = (PWORD)((DWORD)nBaseRLC + sizeof(IMAGE_BASE_RELOCATION) +        i * 2);
                       DWORD testcharac = (*recaddr>> 12);
                       
                       if (testcharac == 0x03) {
                               DWORD relcoAddr = (*recaddr & 0x0FFF) + nBaseRLC->VirtualAddress;//把特征值过滤掉
              
                               printf("第%d个                类型[%d]                地址%#x                地址%#x\n", i + 1, testcharac, relcoAddr, recaddr);
                       }
                       else
                               printf("第%d个                类型[%d]                地址%#x\n", i + 1, testcharac, recaddr);
最后于 2020-3-13 14:45 被mb_lowsphez编辑 ,原因:
雪    币: 0
活跃值: 活跃值 (19)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
y4ung 活跃值 2020-10-13 19:51
18
0

第二段“原来默认的ImageBase(1000000H)相差了200000H”  这里的1000000H少了个0吧 XD


好像是前面的“比如12000000H的位置” 多打了个0?我看书里提到“默认情况下,EXE文件在内存中的基地址是0x00400000,DLL文件是0x10000000”

最后于 2020-10-13 20:22 被y4ung编辑 ,原因: 好像回复错了
游客
登录 | 注册 方可回帖
返回