深入理解PE结构中的绑定导入表

以win10中notepa.exe为例

 

现在数据目录中找到IAT表

0x00C0 <==> 0xAE00(FOA) 发现IAT中存的值如下:

 我们找到导入表的位置:先找到数据目录项

 0xCFF8 <==> 0xBDF8(FOA) ,找到导入表的位置

0x 00 00 D1 E8  <====>0xBFE8

0xFF FF FF FF

0xFF FF FF FF

0x00 00 D1 D4<====>0xBFD4

0x 00 00 C0 00<====>0xAE00

 

 发现INT表和IAT表中的内容不一样了,这是为什么呢?

这个时候IAT里面的值 直接就是DLL中函数的绝对地址。

前边分析的是一样的,这里为什么不一样呢?

0x 00 00 D1 E8  <====>0xBFE8

0xFF FF FF FF      时间戳为-1表示已经绑定

0xFF FF FF FF

0x00 00 D1 D4<====>0xBFD4

0x 00 00 C0 00<====>0xAE00

因为时间戳为0xFF FF FF FF ,表示已经绑定,这个时候IAT里面的值在程序加载前都被修改为 dll函数中的绝对虚拟地址。

为什么能这样做呢?这样做的优缺点呢?

  前边讲过,

    程序加载前:INT里面存放的函数的名称、序号;IAT里面存的也是一样的。

    程序加载后,当exe中其他的dll也贴到虚拟内存之后,IAT里面存的才变成函数的地址。

提前绑定之后,当我们双击exe,exe加载到内存,dll贴到内存,这个时候,不用再去修改IAT表中的值了,exe可直接跑起来了,这样程序运行的更快。

这就是绑定的优点,程序运行的更快,

缺点呢?

缺点就是当dll占不住要用的ImageBase的时候,这个时候,首先的dll需要使用reloc把自身的重定位表修复,其次就是exe里面已经绑定的IAT里面存的地址是错误的。

 

        因此一般系统常用的几个程序 calc、mspaint、notepad常用的这几个程序都是使用绑定导入表的,因为这些程序常用的系统dll的位置都是固定的,因此一般都提前绑定。

 

那么问题来了,提前进行绑定,那么是什么时候开始绑定的呢,,绑定的是哪个版本的dll呢(因为dll也会更新)?

现在导入表中的时间戳为-1(0xFFFFFFFF),那么就需要另外一个结构来存储这个绑定时间,以及判断绑定的版本号

这个真正的时间就存储在绑定导入表中

绑定导入表:

绑定导入表存在数据目录项的第0xB项(0xC是IAT表)

lpOptionHeader1->DataDirectory[0xB].RVA

struct _BOUND_IMPORT_DESCRIPTOR{
            dword TimeDateStamp;               //真正的绑定时间,时间戳
            word OffsetModuleName;            //绑定的dll的名称的偏移(这个值的计算很有意思,绑定导入表的起始地址的RVA,例子中是0x02E0 + OffsetModuleName 这个值的和得到一个RVA,这个RVA指向的字符串就是dll名字
             word NumberOfModuleForwarderRefs;  //这个绑定的dll中可能需要导入其他的dll中的函数,这个值就是指依赖的其他dll有几个
};
  struct Module_Forwarder_Ref{
             dword TimeDateStamp;            //时间戳,检查引用的其他dll的时间戳
             dword OffsetModuleName;       //绑定dll中依赖的其他dll的名字,绑定导入表的起始地址的RVA,例子中是0x02E0 + OffsetModuleName 这个值的和得到一个RVA,这个RVA指向的字符串就是依赖dll的名字
             dword Reserved;                      //保留(未使用)
};

 

 如上图,绑定导入表存储的时候 和 重定位表类似,也是一块一块的;但是绑定导入表有区别,_BOUND_IMPORT_DESCRIPTOR后边跟的是Module_Forwarder_Ref,跟几个是由_BOUND_IMPORT_DESCRIPTOR->NumberOfModuleForwarderRefs决定的。

如果_BOUND_IMPORT_DESCRIPTOR->NumberOfModuleForwarderRefs = 0,那么_BOUND_IMPORT_DESCRIPTOR后边还是跟着_BOUND_IMPORT_DESCRIPTOR。

如果_BOUND_IMPORT_DESCRIPTOR->NumberOfModuleForwarderRefs = 3,那么后边就先跟着3个Module_Forwarder_Ref结构;然后才是后边的_BOUND_IMPORT_DESCRIPTOR。

我们知道一个PE文件中的标志NT头中有个字段是TimeDateStamp,而dll也是一个PE文件,那么dll头部中的这个TimeDateStamp就表示这个dll的生成时间

_BOUND_IMPORT_DESCRIPTOR--->TimeDateStamp这个时间戳 和 dll头部中的这个TimeDateStamp一比较,如果相同,那么绑定的版本没有变化。这个时间戳就是这样用的。

 Module_Forwarder_Ref  -->TimeDateStamp,因为一个dll,可能会用到其他的dll中的函数,这个时间 也是 与引入的其他dll的时间戳比较,也是判断版本是否发生变化的。

打印绑定导入表的代码:

void PrintfBindImportDirectory(void* FileBuffer)
{
	unsigned int* NTOffset = (unsigned int*)((char*)FileBuffer + 0x3c);

	PE_ntHeader* lpNtHeader=(PE_ntHeader*)((unsigned char*)FileBuffer + *NTOffset);

	if(lpNtHeader->SizeofOptionalHeader == 0xF0)
	{
		//lpOptionHeade
		PE_OptionalHeader64* lpOptionHeader1= (PE_OptionalHeader64*)((char*)FileBuffer + *NTOffset + 24);
		//绑定导入表的FOA,通过FOA找到绑定导入表
		unsigned int BindImportTableOffset =  RvaToFoa(FileBuffer,lpOptionHeader1->DataDirectory[0xB].RVA);
		//绑定导入表的RVA,这个值用来定位dll名称用的。 RVA + OffsetModuleName 这2个值相加才是最终的RVA,再转换为FOA才能找到dll名字
		unsigned int BindImportTableRVA = lpOptionHeader1->DataDirectory[0xB].RVA;

		if(lpOptionHeader1->DataDirectory[0xB].RVA == 0)
		{
			printf("绑定包导入表Bind_Import_Table不存在!\n");

		}else{
			//找到绑定导入表
			Image_Bound_Import* BoundImortTable= (Image_Bound_Import*)((char*)FileBuffer + RvaToFoa(FileBuffer,BindImportTableOffset));
			while(BoundImortTable->TimeDateStamp!=0 && BoundImortTable->OffsetModuleName!=0)
			{
				printf("************************\n");
				printf("导入的dll的时间戳:%08x\n",BoundImortTable->TimeDateStamp);
				printf("导入绑定的dll名字:%s\n",(char*)FileBuffer + RvaToFoa(FileBuffer,BindImportTableRVA + BoundImortTable->OffsetModuleName));
				printf("这个dll又用到其他%d个dll中的函数\n",BoundImortTable->NumberOfModuleForwarderRefs);
				
				for(int i=0;i<BoundImortTable->NumberOfModuleForwarderRefs;i++)
				{
					BoundImortTable++;
					printf("------%d-----\n",i+1);
					printf("又引入的dll的时间戳:%08x\n",BoundImortTable->TimeDateStamp);
					printf("又引用的另外的dll名字:%s\n",(char*)FileBuffer + RvaToFoa(FileBuffer,BindImportTableRVA +BoundImortTable->OffsetModuleName));
					printf("保留未用:0x0000\n");
				}

				BoundImortTable++;
			}
		
			
		}

	}else if(lpNtHeader->SizeofOptionalHeader == 0xE0)
	{
			//lpOptionHeade
		PE_OptionalHeader32* lpOptionHeader1= (PE_OptionalHeader32*)((char*)FileBuffer + *NTOffset + 24);
		//绑定导入表的FOA,通过FOA找到绑定导入表
		unsigned int BindImportTableOffset =  RvaToFoa(FileBuffer,lpOptionHeader1->DataDirectory[0xB].RVA);
		//绑定导入表的RVA,这个值用来定位dll名称用的。 RVA + OffsetModuleName 这2个值相加才是最终的RVA,再转换为FOA才能找到dll名字
		unsigned int BindImportTableRVA = lpOptionHeader1->DataDirectory[0xB].RVA;

		if(lpOptionHeader1->DataDirectory[0xB].RVA == 0)
		{
			printf("绑定包导入表Bind_Import_Table不存在!\n");

		}else{
			//找到绑定导入表
			Image_Bound_Import* BoundImortTable= (Image_Bound_Import*)((char*)FileBuffer + RvaToFoa(FileBuffer,BindImportTableOffset));
			while(BoundImortTable->TimeDateStamp!=0 && BoundImortTable->OffsetModuleName!=0)
			{
				printf("************************\n");
				printf("导入的dll的时间戳:%08x\n",BoundImortTable->TimeDateStamp);
				printf("导入绑定的dll名字:%s\n",(char*)FileBuffer + RvaToFoa(FileBuffer,BindImportTableRVA + BoundImortTable->OffsetModuleName));
				printf("这个dll又用到其他%d个dll中的函数\n",BoundImortTable->NumberOfModuleForwarderRefs);
				
				//BoundImortTable = BoundImortTable + BoundImortTable->NumberOfModuleForwarderRefs;				
				
				for(int i=0;i<BoundImortTable->NumberOfModuleForwarderRefs;i++)
				{
					BoundImortTable++;
					printf("------%d-----\n",i+1);
					printf("又引入的dll的时间戳:%08x\n",BoundImortTable->TimeDateStamp);
					printf("又引用的另外的dll名字:%s\n",(char*)FileBuffer + RvaToFoa(FileBuffer,BindImportTableRVA +BoundImortTable->OffsetModuleName));
					printf("保留未用:0x0000\n");
				}

				BoundImortTable++;
			}
		
			
		}

	}
}

  

posted @ 2023-08-10 13:49  一日学一日功  阅读(169)  评论(0)    收藏  举报