zoukankan      html  css  js  c++  java
  • PE 导入表

    参考文章: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

    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为结束符号

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

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


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

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

    下面的图是: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文件就依赖当前这个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结尾

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


    那么还有最后一个 如果确定当前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 
    ", ImportTableDllName);
    		printf("
    ");
    		
    
    		printf("INT表打印
    ");
    		//OriginalFirstThunk转换FOA
    		RVA_TO_FOA(pFileBuffer,pIMPORT_DESCRIPTOR->OriginalFirstThunk,&FOA);
    		
    		OriginalFirstThunk_INT = (PDWORD)((DWORD)pFileBuffer + (DWORD)FOA);
    		
    		//printf("%x",*OriginalFirstThunk_INT);
    		printf("
    ");
    		while(*OriginalFirstThunk_INT){
    			//printf("%x
     ",*OriginalFirstThunk_INT);
    			if((*OriginalFirstThunk_INT) & 0X80000000){
    				//高位为1 则 除去最高位的值就是函数的导出序号
    				Original = *OriginalFirstThunk_INT & 0xFFF;	//去除最高标志位。
    				printf("按序号导入: %08Xh -- %08dd
    ", 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 
    ",FunctionName);
    			}
    			OriginalFirstThunk_INT++;
    		}
    
    		printf("
    ");
    
    		//继续如上操作进行打印操作
    		//这里打印的是iat表
    
    		printf("IAT表打印
    ");
    		//FirstThunk转换FOA
    		RVA_TO_FOA(pFileBuffer,pIMPORT_DESCRIPTOR->FirstThunk,&FOA);
    
    		FirstThunk_IAT = (PDWORD)((DWORD)pFileBuffer + (DWORD)FOA);
    		
    		//printf("%x",*OriginalFirstThunk_INT);
    		printf("
    ");
    		while(*FirstThunk_IAT){
    			//printf("%x
     ",*OriginalFirstThunk_INT);
    			if((*FirstThunk_IAT) & 0X80000000){
    				//高位为1 则 除去最高位的值就是函数的导出序号
    				Original = *FirstThunk_IAT & 0xFFF;	//去除最高标志位。
    				printf("按序号导入: %08Xh -- %08dd
    ", 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 
    ",FunctionName);
    			}
    			FirstThunk_IAT++;
    		}
    		
    		printf("=========================================");
    		printf("
    ");
    		
    		pIMPORT_DESCRIPTOR++;		
    	}
    }
    

  • 相关阅读:
    uwsgi wsgi nginx centos7.2部署flask
    以守护进程的方式部署flask
    新装Centos7.2 配置防火墙
    django 编程小结
    install plugin elasticsearch-analysis-ik
    为什么配置环境总是出现个各种问题呢?
    configure HDFS(hadoop 分布式文件系统) high available
    ConstraintLayout UI性能分析
    Android处理滑动与点击事件的冲突
    android自定义渐变圆环进度条
  • 原文地址:https://www.cnblogs.com/zpchcbd/p/12325556.html
Copyright © 2011-2022 走看看