参考文章:https://blog.csdn.net/chenlycly/article/details/53378427

百度百科:Import Address Table 由于导入函数就是被程序调用但其执行代码又不在程序中的函数,这些函数的代码位于一个或者多个DLL 中.

当PE 文件被装入内存的时候,Windows 装载器才将DLL 装入,并将调用导入函数的指令和函数实际所处的地址联系起来(动态连接),这操作就需要导入表完成.其中导入地址表就指示函数实际地址。

PE文件什么时候开始加载导入表:

在exe加载入内存 修复完重定位表中的数据的时候,就开始加载导入表

如何定位导入表

在扩展PE头是一个名为_IMAGE_OPTIONAL_HEADER的结构体

其中存在一个结构体数组为IMAGE_DATA_DIRECTORY,个数有16个,总占128字节,这里的第2个位置就是IMAGE_DATA_DIRECTORY

PE 导入表

IMAGE_DATA_DIRECTORY的结构如下:

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress; //虚拟地址,存储当前导出表的地址,这里地址是RVA
    DWORD   Size; //存储 当前导出表的大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

因为导入表由一堆PE文件组成,所以一般导入表都是有许多个的,导入表中存储的为当前PE需要依赖的函数等

其中关于导入表的结构体的名称为:_IMAGE_IMPORT_DESCRIPTOR,结构体如下:

导入表的具体结构为:占20个字节

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;            // 0 for terminating null import descriptor
        DWORD   OriginalFirstThunk;         // RVA,指向IMAGE_THUNK_DATA结构数组
    };
    DWORD   TimeDateStamp;                  // 时间戳

    DWORD   ForwarderChain;                 // -1 if no forwarders
    DWORD   Name;                           // RVA,指向dll名字,该名字以0结尾
    DWORD   FirstThunk;                     // RVA,指向IMAGE_THUNK_DATA结构数组
} IMAGE_IMPORT_DESCRIPTOR;

如何判断导入表的个数?依赖6个模块

每20个字节为一个dll的导入表的相关信息,以20个0为结束符号

PE 导入表

第一个导入表其中Name成员为当前导入表的名字的地址,地址为014592,如果文件对齐和内存对齐大小不一样那需要先转换为FOA再去查找!

当前导入表的模块为KERNEL32.dll

PE 导入表


已经知道了各个导入表的名称,现在再继续了解下导入表中导入了哪些函数吧

其中有两个成员为相同的结构体数组OriginalFirstThunk,FirstThunk都指向IMAGE_THUNK_DATA结构体数组

下面的图是:PE文件加载前的情况图

PE 导入表

IMAGE_THUNK_DATA结构体如下:发现是一个联合体,实际上该结构体的字节为4字节

typedef struct _IMAGE_THUNK_DATA32 {
    union {
        PBYTE  ForwarderString; 
        PDWORD Function;
        DWORD Ordinal; //序号
        PIMAGE_IMPORT_BY_NAME  AddressOfData; //指定IMAGE_IMPORT_BY_NAME
    } u1;
} IMAGE_THUNK_DATA32;

虽然OriginalFirstThunk,FirstThunk指向的结构体都一样,但是名称不一样OriginalFirstThunk指向的表叫导入名称表(INT),FirstThunk指向的表叫导入地址表(IAT)

当前OriginalFirstThunk指向的地址为014310,结束符为4字节的0结束

PE 导入表

那么就可以说明当前PE文件就依赖当前这个DLL文件的这么多函数!

例如图中有有00014566 00014554 00014546

如果这些地址的最高位是为1的话,那么除去最高位的值就是函数的导出序号

如果这些地址的最高为不是为1的话,那么这个值就是一个RVA,指向IMAGE_IMPORT_BY_NAME结构体,结构体如下:占4个字节

typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD    Hint; //可能为空,编译器决定,如果不为空,是函数在导出表的索引
    BYTE    Name[1]; //函数名称,标识符只显示函数的首字母,以0结尾
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

可以跟下00014566,然后往后,最后以0结尾

PE 导入表

那么到目前位置,就确定了两个,1、 如何分析该PE的导入表的DLL 2、该PE的导入表中的DLL的导入函数有哪些


那么还有最后一个 如果确定当前PE文件的导入表中导入的函数地址如何确定呢?

下面的图是:PE文件加载后的情况图

PE 导入表

当PE文件执行后,当前导入表中的使用的函数地址都会添加IAT表中,也就是函数地址表中,这时候IAT和INT就开始有差异了!

问题:那为什么PE文件不直接是加载前的就好了,这样的话还可以去掉INT,直接通过IAT来寻找?

答: 如果IAT中的表的地址丢失了,就需要修正,就可以通过INT表来找到函数名称,然后通过GetProcAddress来重新获得函数地址,这个用法会在以后的注入手段中用到,比如修复IAT表就需要通过遍历INT表来实现

打印导入表:

实现的代码如下:

void PrintfImportTable(PVOID pFileBuffer){
    PIMAGE_DOS_HEADER pDosHeader = NULL;    
    PIMAGE_NT_HEADERS pNTHeader = NULL; 
    PIMAGE_FILE_HEADER pPEHeader = NULL;    
    PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;  
    PIMAGE_SECTION_HEADER pSectionHeader = NULL;
	PIMAGE_IMPORT_DESCRIPTOR pIMPORT_DESCRIPTOR;
	PIMAGE_IMPORT_BY_NAME pImage_IMPORT_BY_NAME;


	char ImportTableDllName[10] = {0};
	char FunctionName[20] = {0};

	PDWORD OriginalFirstThunk_INT = NULL;
	PDWORD FirstThunk_IAT = NULL;

	DWORD RVA = 0;
	DWORD FOA = 0;
	DWORD Original = 0;
	
    pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
    pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);
    pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);  
    pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER); 
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + IMAGE_SIZEOF_NT_OPTIONAL_HEADER);

	//获取导入表的位置
	RVA_TO_FOA(pFileBuffer,pOptionHeader->DataDirectory[1].VirtualAddress,&FOA);

	//每个导入表的相关信息占20个字节
	pIMPORT_DESCRIPTOR = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pFileBuffer + (DWORD)FOA);
	
	//这里可以进行while操作,这里while的判断依据为 pIMPORT_DESCRIPTOR个数

	printf("=========================================");
	
	while(pIMPORT_DESCRIPTOR->FirstThunk && pIMPORT_DESCRIPTOR->OriginalFirstThunk){
		//这里打印的是INT表
		//获取当前导入表DLL的名字
		strcpy(ImportTableDllName,(PVOID)((DWORD)pFileBuffer + (DWORD)pIMPORT_DESCRIPTOR->Name));
		
		printf("当前打印的导出表的DLL为: %s \n", ImportTableDllName);
		printf("\n");

		//printf("TimeDateStamp: %x\n",pIMPORT_DESCRIPTOR->TimeDateStamp);

		printf("INT表打印\n");
		//OriginalFirstThunk转换FOA
		RVA_TO_FOA(pFileBuffer,pIMPORT_DESCRIPTOR->OriginalFirstThunk,&FOA);
		
		OriginalFirstThunk_INT = (PDWORD)((DWORD)pFileBuffer + (DWORD)FOA);
		
		//printf("%x",*OriginalFirstThunk_INT);
		printf("\n");
		while(*OriginalFirstThunk_INT){
			//printf("%x\n ",*OriginalFirstThunk_INT);
			if((*OriginalFirstThunk_INT) & 0X80000000){
				//高位为1 则 除去最高位的值就是函数的导出序号
				Original = *OriginalFirstThunk_INT & 0x7FFFFFFF;	//去除最高标志位。
				printf("按序号导入: %08Xh -- %08dd\n", Original, Original);	//16进制 -- 10 进制
			}else{
				//高位不为1 则指向IMAGE_IMPORT_BY_NAME
				RVA_TO_FOA(pFileBuffer,*OriginalFirstThunk_INT,&FOA);
				pImage_IMPORT_BY_NAME = (PIMAGE_IMPORT_BY_NAME)FOA;
				strcpy(FunctionName,(PVOID)((DWORD)pFileBuffer + (DWORD)&(pImage_IMPORT_BY_NAME->Name)));
				printf("按函数名导入 函数名为: %s \n",FunctionName);
			}
			OriginalFirstThunk_INT++;
		}

		printf("\n");
		

		//继续如上操作进行打印操作
		//这里打印的是iat表

		printf("IAT表打印\n");
		//FirstThunk转换FOA
		RVA_TO_FOA(pFileBuffer,pIMPORT_DESCRIPTOR->FirstThunk,&FOA);

		FirstThunk_IAT = (PDWORD)((DWORD)pFileBuffer + (DWORD)FOA);
		
		//printf("%x",*OriginalFirstThunk_INT);
		printf("\n");
		while(*FirstThunk_IAT){
			//printf("%x\n ",*FirstThunk_IAT);
			if((*FirstThunk_IAT) & 0X80000000){
				//高位为1 则 除去最高位的值就是函数的导出序号
				Original = *FirstThunk_IAT & 0x7FFFFFF;	//去除最高标志位。
				printf("按序号导入: %08Xh -- %08dd\n", Original, Original);	//16进制 -- 10 进制
			}else{
				//高位不为1 则指向IMAGE_IMPORT_BY_NAME
				RVA_TO_FOA(pFileBuffer,*FirstThunk_IAT,&FOA);
				pImage_IMPORT_BY_NAME = (PIMAGE_IMPORT_BY_NAME)FOA;
				strcpy(FunctionName,(PVOID)((DWORD)pFileBuffer + (DWORD)&(pImage_IMPORT_BY_NAME->Name)));
				printf("按函数名导入 函数名为: %s \n",FunctionName);
			}
			FirstThunk_IAT++;
		}
		
		printf("=========================================");
		printf("\n");
		
		pIMPORT_DESCRIPTOR++;		
	}
}

PE 导入表

相关文章:

  • 2022-02-10
  • 2022-02-11
  • 2021-08-17
  • 2022-01-10
  • 2021-07-06
  • 2021-07-13
  • 2021-09-10
猜你喜欢
  • 2021-07-09
  • 2021-06-12
  • 2021-09-30
  • 2022-12-23
  • 2022-01-08
  • 2021-08-06
  • 2021-12-18
相关资源
相似解决方案