Last updated on May 9, 2024 pm
                  
                
              
            
            
              
                
                导入表工作原理
PE文件加载过程中,OS会先将文件“拉伸”,而后OS会将其“贴”在虚拟内存,此时即可根据导入表内申请的名单来寻找该Module运行所需要的其他支持。
一般来说是因为某个Module运行需要外部提供某些函数的实现支持。
导入表内描述了需要的函数的名称或者函数序号,以及这些函数位于的Module名称(一般是XXX.DLL文件),OS根据依赖的Module名称就可以将该Module也加载到内存中,并检查该Module的导出表,按序号或名称的方式从导出表中找到所需的函数地址。
当函数地址被找到后,OS即可将该函数的绝对地址直接贴回到申请导入的PE文件的导入表中(导入表中的IAT结构就专门干这事,下文会详细探讨)。
当我们联系汇编代码时,就可以想通这个道理:
静态引用的函数地址,例如某程序内部自实现的函数,其地址在汇编代码中表现为绝对地址(ImageBase + RVA): call XXXX
动态引用的函数地址,其地址依赖外部导入,故汇编代码中表现为 call dword ptr [XXXX]。这里的XXXX即为IAT中存储导入函数地址的位置,可以理解为一个函数指针。
导入表位置
1 2 3 4
   | typedef struct _PE_HEADERS_OPTIONAL { 	... 	PE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBERSOF_DIRECTORY_ENTRY]; }PEHOPTIONAL, *lpPEHOPTIONAL;
 
  | 
 
导入表位于OP头的DataDirectory数组的第2个元素,即DataDirectory[1] 的位置,存储导入表地址。
还记得导出表在哪里吗?正是 DataDirectory[0] 的位置。
导入表结构
1 2 3 4 5 6 7 8 9 10
   | typedef struct _DATA_IMPORT_DIRECTORY {      union {          DWORD   Characteristics;                      DWORD   OriginalFirstThunk;               };      DWORD   TimeDateStamp;                        DWORD   ForwarderChain;                       DWORD   Name;                                 DWORD   FirstThunk;                       } DATA_IMPORT_DIRECTORY, * PDATA_IMPORT_DIRECTORY;
 
  | 
 
一个PE程序会依赖多少外部的DLL ? 不知道,可能是一个,也可能是多个。
因此导入表注定不会只有一张,往往会有多张。
通过结构体的联合体成员Characteristics是否等于0,即可判断是否已遍历所有的导入表。
            其实可以将导入表的入口地址,解释为一个存储了DATA_IMPORT_DIRECTORY结构的数组成员。
           
INT与IAT
INT,即Import Name Table,直译为导入名称表。
IAT,即Import Address Table,直译为导入地址表。
DATA_IMPORT_DIRECTORY结构的OriginalFirstThunk指向INT,FirstThunk成员指向IAT。
需要注意的是,IAT表也可以通过OP头的DataDirectory数组来找到,它位于DataDirectory数组的第13个元素,即DataDirectory[12]。
INT表与IAT结构相同:
1 2 3 4 5 6 7 8
   | typedef struct _DATA_THUNK_DATA { 	union { 		DWORD ForwarderString;       		DWORD Function;              		DWORD Ordinal; 		DWORD AddressOfData;         	}; } DATA_THUNK_DATA, * PDATA_THUNK_DATA;
 
  | 
 
看似复杂的结构,其实整个结构都是联合体,一般来说直接将其视为四字节整型DWORD即可。
当该值为0时,即到达末尾结束,因此可将INT和IAT都看作一个四字节整型数组,即DWORD[]。
函数名称 or 函数序号
我们通过AddressOfData来获取数据。
AddressOfData为一个32bit的整型, 解析该值时需分两种情况:
- 该值最高位为0,那么该值实际上是一个RVA,指向NAME结构,通过该结构的Name成员即可获取函数名称:
 
1 2 3 4
   | typedef struct _DATA_IMPORT_BY_NAME { 	WORD    Hint;        	BYTE    Name[1];     } DATA_IMPORT_BY_NAME, * PDATA_IMPORT_BY_NAME;
 
  | 
 
- 该值最高位为1,那么该值是一个函数序号,去掉最高位后剩下的31位即为函数序号。
 
PE加载前后的不同之处
在PE文件加载前,INT和IAT两张表中存储的内容完全一样,都是函数的序号或函数名称。
而当PE文件加载完成之后,即OS已经将全部所需的导入函数找到后,IAT表中存储的不再是函数名称或序号,而是导入函数的绝对地址。
即AddressOfData存储的内容,已经变成一个绝对地址,该地址即导入函数的入口地址。
此时INT表与IAT互为对照,INT内存储名字,IAT内存储地址。
编码实现打印导入表
主函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   | void work06() { 	 	PEFILE peFile{ 0 }; 	FILE* pFile = fopen("D:\\1.exe", "rb");
  	int fileLength = LoadFileToMemory(pFile, &peFile); 	int r = fclose(pFile);
  	if (peFile.h_pe == nullptr) { 		return; 	}
  	 	PDATA_IMPORT_DIRECTORY pImportTable = nullptr; 	GetImportTable(&pImportTable, &peFile);
  	PrintImportTable(pImportTable, &peFile); }
 
  | 
 
打印导入表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
   | void PrintImportTable(PDATA_IMPORT_DIRECTORY pImportTable, lpPEFILE lpPeFile) { 	DWORD imageBase = (DWORD)lpPeFile->h_dos;
  	while (pImportTable->Characteristics != NULL) { 		char* dllName = (char*)Rva2Foa(pImportTable->Name, lpPeFile) + imageBase; 		printf(" %s : \n OriginalFirstThunk :0x%x \n FirstThunk : 0x%x \n",  			dllName, pImportTable->OriginalFirstThunk, pImportTable->FirstThunk);
  		 		PDATA_THUNK_DATA pImportNameTable = 			(PDATA_THUNK_DATA)(imageBase + Rva2Foa(pImportTable->OriginalFirstThunk, lpPeFile)); 		printf("INT:\n"); 		PrintThunkData(pImportNameTable, lpPeFile); 		 		PDATA_THUNK_DATA pImportAddressTable =  			(PDATA_THUNK_DATA)(imageBase + Rva2Foa(pImportTable->FirstThunk, lpPeFile)); 		printf("IAT:\n"); 		PrintThunkData(pImportNameTable, lpPeFile);
  		for (int i = 0; i < 10; i++) { 			printf("**********"); 		} 		printf("\n"); 		pImportTable++; 	} }
 
  | 
 
打印IAT或INT表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
   |  void PrintThunkData(PDATA_THUNK_DATA thunkData, lpPEFILE lpPeFile) { 	printf("%8s\t%30s\t%10s\t%10s\t\n", "Adress:", "Name:", "Hint:", "OrderNo:");
  	DWORD imageBase = (DWORD)lpPeFile->h_dos; 	DWORD AddressOfData = thunkData->AddressOfData; 	while (AddressOfData != NULL) { 		DWORD orderNo = AddressOfData >> 31;
  		if (orderNo == 1) {       		orderNo = AddressOfData & 0x7FFFFFFF; 			printf("%8x\t%30s\t%10s\t%10d \n", AddressOfData, "Null", "Null", orderNo); 		} 		else { 			PDATA_IMPORT_BY_NAME name = 				(PDATA_IMPORT_BY_NAME)(Rva2Foa(AddressOfData, lpPeFile) + imageBase); 			printf("%8x\t%30s\t%10d\t%10d \n", AddressOfData, name->Name, name->Hint, orderNo); 		}
  		thunkData++; 		AddressOfData = thunkData->AddressOfData; 	} 	 }
 
  |