IAT表和导入表

余生长醉 提交于 2019-12-04 23:29:49
1.关于IAT(import address table)表
当exe程序中调用dll中的函数时,反汇编可以看到,call后面并不是跟的实际函数的地址,而是给了一个地址;
这些连起来就是一张表,就是IAT表;
 
1)内存镜像中的dll中函数的调用;
例如:一个exe中调用系统提供的dll中的MessageBox函数时:
可以看到call的地址是42d2c4;
 
查看42d2c4中的内容:
可以看到:42d2c4中保存的是762b0026;
762b0026是dll的领空;也就是dll中MessageBox函数的地址;
 
总结:
    exe程序调用dll中的函数时,会使用FF15call;
    call的并不是实际的函数地址,而是该函数对应的IAT表的地址;
    通过IAT表来找到实际的函数地址;
 
2)文件镜像中的IAT相关
接下来取文件中找42d2c4中保存的内容;
需要将RVA转成FOA;
42d2c4-400000 = 2d2c4;在.idata节;
foa = 2d2c4 - 2d000 + 2b000 = 2b2c4;
可以看到:文件镜像中保存的值为2d2f4;
 
继续追到2d2f4,对应的foa为2b2f4
看到并不是MessageBox的地址,而是一MessageBox先关的描述;
 
结论:IAT表在文件和内存中是不一样的;
 
3)原因分析
对于一个exe程序,在运行时会加载到独立的4gb空间;
exe程序是可以按ImageBase来加载的;
但该exe引用的dll并不能保证,因为ImageBase可能被占用;
因此对于dll来说有重定位表来记录dll中的可能需要修改绝对地址;
对于引用dll的exe程序来说,因为无法确定dll中引用的函数的地址,所以不会再文件镜像中将引用dll的函数的地址写死;
而是保存的是这些函数相关的信息;
当程序运行后,并且所有dll都加载到该程序的4gb空间后,此时dll中函数的地址才确定;
IAT表中将保存dll中函数在内存中的真实地址;
因此IAT表中在文件和内存中不同;
 
4)IAT表总结
IAT表在程序执行前和程序执行后是不一样的;
程序在编译时给分了一个空间,保存一个偏移,这个偏移指向的是引用dll中的函数的函数名,而不是真正的函数地址;
当程序加载完毕,所有的dll都贴到程序的4gb空间后,会把IAT表中的值改为真正要用的函数的地址;因为dll的地址因不是按ImageBase加载而不一样;
 
2.导入表
比如说当去餐馆吃饭时,餐馆会提供一个菜单,告诉谁提供了哪些菜;
自己在点餐时也需要给餐馆提供一个单,来告诉自己点了哪些菜;
对于程序来说dll提供导出表,导出表中保存的是导出函数的清单以及地址;
程序也需要提供一个导入表,来说明使用了哪些函数;
 
1)导入表的结构
typedef struct _IMAGE_IMPORT_DESCRIPTOR {                                    
    union {                                    
        DWORD   Characteristics;                                               
        DWORD   OriginalFirstThunk;                                 //RVA 指向IMAGE_THUNK_DATA结构数组            
    };                                    
    DWORD   TimeDateStamp;                                       //时间戳            
    DWORD   ForwarderChain;                                                  
    DWORD   Name;                        //RVA,指向dll名字,该名字已0结尾            
    DWORD   FirstThunk;                                         //RVA,指向IMAGE_THUNK_DATA结构数组            
} IMAGE_IMPORT_DESCRIPTOR;                                    
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;         

                  

数据目录的第二项就是导入表;
导入表结构可能有多个,一个dll对应一个导入表结构;
其中比较重要的两个属性:
    OriginalFirstThunk    ->指向INT表(import name table);
        表中存的是一些IMAGE_THUNK_DATA结构;
        这个表以0结尾;
    FirstThunk    ->指向IAT表(import address table);
        IAT表有两种方式可以找到:1】通过数据目录的第13个结构;2】导入表结构的这个属性;
        
2)导入表在文件加载前和文件加载后有区别
1】pe文件加载前:
IAT表和INT表中保存的结构都是IMAGE_THUNK_DATA,且都是以0结尾;
在文件加载前两张表里的内容完全一样;
IMAGE_THUNK_DATA用来保存函数名以及函数序号等信息;
IMAGE_THUNK_DATA结构中只有一个联合,占4字节的空间;
 
IMAGE_THUNK_DATA结构:
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;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;

 

IMAGE_IMPORT_BY_NAME结构:
typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD    Hint;        //可能为空,编译器决定 如果不为空 是函数在导出表中的索引
    BYTE    Name[1];    //函数名称,以0结尾
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

 

2】pe文件加载后:
文件加载后,系统会调用通过函数名或函数序号找函数地址的函数GetProcAddr() ;
得到函数地址后,用函数地址替换IAT表中的数据;
 
使用两张表INT、IAT的原因:
    如果只有IAT表,在程序加载完后,IAT表中的函数名将替换成函数地址,就无法知道函数名,也就是留一个函数名的备份;
    
3.解析导入表
主要步骤:
    1】定位导入表:
        数据目录的第二个结构为导入表;
        通过该结构,可以找到导入表rva,转成foa可以在文件中找到导入表;
        每一个引用的dll都对应一个导入表,需要循环解析,直到遇到全0的导入表结构为止;
        也就是sizeOf(IMAGE_IMPORT_DESCRIPTOR) 个 0  代表导入表结束;
    2】解析dll名:
        导入表中储存了引用的dll的名字信息;
        在导入表结构的Name属性中;
    3】解析OriginalFirstThunk:
        OriginalFirstThunk指向INT表;
        INT表中保存了IMAGE_THUNK_DATA结构,该结构只有一个联合,占4字节;  
        判断最高位是否为1,如果是那么除去最高位的值就是函数的导出序号;有些函数匿名导出的,不能用函数名找地址;                
        如果不是,那么这个值是一个RVA 指向IMAGE_IMPORT_BY_NAME ;  
        IMAGE_IMPORT_BY_NAME结构中有2个属性;
            Hint    ->是函数在导出表中的索引,并不是函数的序号,没有实际作用;
            Name    ->函数名,只有1个字节,也就是函数名的开始,因为字符串长度不固定,从字符串开始到0结束为一个函数名;  
        调用 GetProcAddr(m,函数的名字或者导出序号) 函数可以找到函数地址;
        当IMAGE_THUNK_DATA结构为0时,表示该INT表结束;
    4】解析FirstThunk:
         FirstThunk指向IAT表,IAT表在程序加载前和INT表是一样的;   
 
输出导入表信息:
         
 
代码:
#include "stdafx.h"
#include "PeTool.h"
#include "string.h"
 
#define SRC "C:\\Users\\Administrator\\Desktop\\Hello.exe"
 
//解析导入表
void printImport(){
    //定义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_IMPORT_DESCRIPTOR importDir= 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;
    importDir = (PIMAGE_IMPORT_DESCRIPTOR) ((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, (dataDir + 1)->VirtualAddress ));
 
 
    //3.输出导入表信息
    while(importDir->OriginalFirstThunk){
        printf("按enter键显示下一个dll导入表信息:\n");
        getchar();
        LPSTR name = (LPSTR)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, importDir->Name ));
        printf("================%s================\n", name);
        //解析INT表
        PDWORD thunk =(PDWORD) ((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, (DWORD)importDir->OriginalFirstThunk)) ;
        while(*thunk){
            if((*thunk & 0x80000000) >> 31 == 1){    //如果第一位为1,表示为序号导入
                DWORD ord = *thunk & 0x7fffffff;
                printf("按序号导入:%d\n", ord);
            }else{
                PIMAGE_IMPORT_BY_NAME pName = (PIMAGE_IMPORT_BY_NAME)((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer, *thunk));
                printf("按函数名导入:%s\n",pName->Name);
            }
            //下一个INT表项
            thunk++;
        }
        //下一张导入表
        importDir++;
        printf("\n");
    }
}
 
int main(int argc, char* argv[])
{
    //输出导入表信息
    printImport();
    
    getchar();
}

 

 
结果:
   
        
                    
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!