绑定导入表
1.关于绑定导入
一般情况下,在程序加载前IAT表和INT表中的内容相同,都是程序引用的dll中的函数的函数名或序号;
加载完成后IAT表中将替换为函数的真正地址;
但在加载前IAT表中直接写绝对地址是可以实现的;
加载前在IAT表中保存绝对地址的优点:启动程序快;
在启动程序时需要:申请4gb内存空间、贴exe、贴dll、将IAT表修复为地址等等;
如果直接用绝对地址,则省去了修复IAT表的操作;
缺点:
dll重定位时,如果dll没能占据自身ImageBase处的地址,则需要修复绝对地址;
dll被修改时,dll被修改,IAT表中对应的函数地址可能被改,需要修复函数地址;
例如windows提供的一些程序就使用了这种方式,比如记事本;
这种方式称为“绑定导入“;
2.如何判断绑定导入
在导入表中结构中有个属性:TimeDateStamp;
该属性表示时间戳;
如果值为0则表示当前的dll的函数没有被绑定,在程序加载时会调用系统函数获取函数地址;
如果值为-1则表示当前的dll的函数已经绑定,而且绑定的时间存在另外一张表里;那张表就是绑定导入表;
3.绑定导入表
PE加载EXE相关的DLL时,首先会根据IMAGE_IMPORT_DESCRIPTOR结构中的TimeDateStamp来判断是否要重新计算IAT表中的地址。
TimeDateStamp == 0 未绑定
TimeDateStamp == -1 已绑定 真正的绑定时间为IMAGE_BOUND_IMPORT_DESCRIPTOR的TimeDateStamp
绑定导入表位于数据目录的第12项;
绑定导入表的结构:
typedef struct _IMAGE_BOUND_IMPORT_DESCRIPTOR { DWORD TimeDateStamp; WORD OffsetModuleName; WORD NumberOfModuleForwarderRefs; // Array of zero or more IMAGE_BOUND_FORWARDER_REF follows } IMAGE_BOUND_IMPORT_DESCRIPTOR, *PIMAGE_BOUND_IMPORT_DESCRIPTOR;
TimeDateStamp ->时间戳;用来判断是否和绑定的dll是同一个版本;也就是看时间戳和dll的pe头中的时间戳是否一样;
OffsetModuleName ->dll的名字;注意保存的既不是RVA也不是FOA;
dll的名字计算公式为:第一个DESCRIPTOR的值+OffsetModuleName;
NumberOfModuleForwarderRefs ->当前dll另外依赖的dll数量;因为dll也可能依赖dll;
绑定导入表结构后面紧跟的并不一定是下一个绑定导入表;
如果NumberOfModuleForwarderRefs为N则还有N个另外的结构;
该结构也是用来描述dll的;
结构如下:
typedef struct _IMAGE_BOUND_FORWARDER_REF { DWORD TimeDateStamp; WORD OffsetModuleName; WORD Reserved; } IMAGE_BOUND_FORWARDER_REF, *PIMAGE_BOUND_FORWARDER_REF;
前两个属性和绑定导入表意义一样;
第三个属性Reserved为保留字段没意义;
绑定导入表的结构图:
当最后有一个全0的结构时表示已经没有绑定导入表;
注意:
当IMAGE_BOUND_IMPORT_DESCRIPTOR结构中的TimeDateStamp与DLL文件标准PE头中的TimeDateStamp值不相符时,
或者DLL需要重新定位的时候,就会重新计算IAT中的值.
4.解析绑定导入表
有两个需要注意的地方:
1】OffsetModuleName的计算方式:第一个DESCRIPTOR的值+OffsetModuleName
2】绑定导入表并不在任何一个节中,而是在头中,因此RVA转FOA的方式可能会和在节中时有所不同;
解析系统自带的记事本的导入表:
#include "stdafx.h" #include "PeTool.h" #include "string.h" #define SRC "C:\\Windows\\System32\\notepad.exe" //解析导入表 void printBound(){ //定义pe头结构指针 PIMAGE_DOS_HEADER dosHeader = NULL; //dos头指针 PIMAGE_FILE_HEADER peHeader = NULL; //pe头指针 PIMAGE_OPTIONAL_HEADER32 opHeader = NULL; //可选pe头指针 PIMAGE_DATA_DIRECTORY dataDir = NULL; //数据目录指针 PIMAGE_BOUND_IMPORT_DESCRIPTOR boundDir= NULL; //绑定导入表指针 PIMAGE_BOUND_FORWARDER_REF bondRef = NULL; //绑定依赖指针 //1.将文件读入内存 LPVOID pFileBuffer = NULL; DWORD fileSize = ReadPEFile(SRC, &pFileBuffer); if(!pFileBuffer){ printf("读取dll文件失败\n"); return; } //2.初始化头结构指针 dosHeader = (PIMAGE_DOS_HEADER) pFileBuffer; peHeader = (PIMAGE_FILE_HEADER) ((DWORD)pFileBuffer + dosHeader->e_lfanew + 4); opHeader = (PIMAGE_OPTIONAL_HEADER32) ((DWORD)peHeader + IMAGE_SIZEOF_FILE_HEADER); dataDir = opHeader ->DataDirectory; //绑定导入表在头中,而不再节中 DWORD boundDirRVA = dataDir[11].VirtualAddress; boundDir = (PIMAGE_BOUND_IMPORT_DESCRIPTOR)((DWORD)pFileBuffer + boundDirRVA); PIMAGE_BOUND_IMPORT_DESCRIPTOR currentBound = boundDir; //3.输出绑定导入表信息 while(currentBound -> TimeDateStamp){ getchar(); LPSTR moduleName = (LPSTR) ((DWORD)(currentBound->OffsetModuleName) + (DWORD)boundDir); printf("\n=================%s=============\n", moduleName); printf("时间戳:%d\n",currentBound->TimeDateStamp ); int i = currentBound->NumberOfModuleForwarderRefs; printf("依赖dll个数:%d\n", i); if(i>0){ bondRef = (PIMAGE_BOUND_FORWARDER_REF) ((DWORD) currentBound + 8); printf("***********依赖dll***********\n"); printf("依赖dll\t时间戳\n"); for(int j=0;j<i;j++){ LPSTR refName = LPSTR((DWORD)((bondRef + j)->OffsetModuleName) + (DWORD)boundDir); DWORD refTime = (bondRef + j) -> TimeDateStamp; printf("%s\t%d\n",refName,refTime); } currentBound = currentBound + (i+1); }else{ currentBound++; } } } int main(int argc, char* argv[]) { //输出导入表信息 printBound(); getchar(); }
结果: