深入理解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++;
}
}
}
}

浙公网安备 33010602011771号