PE文件 - 导入表

导入表

在数据目录的第2项,下标为1的位置,记录了可执行文件导入了哪些动态库和函数,INT(导入函数名称表)和IAT(导入函数地址表),由于一个可执行文件可能要多个PE文件支持,所以此结构可能有多个,导入表就是一个结构体数组,以一个全零元素为结尾,每一个数组的元素,代表一个PE文件的导入信息

导入表格式

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;            
        DWORD   OriginalFirstThunk;         
    } DUMMYUNIONNAME;
    DWORD   TimeDateStamp;
    DWORD   ForwarderChain;                 
    DWORD   Name;
    DWORD   FirstThunk;                     
} IMAGE_IMPORT_DESCRIPTOR;

字段介绍

DWORD   OriginalFirstThunk;   

指向一个结构体数组的一个相对虚拟地址,结构体数组叫输入名称表(INT:import Name table)

DWORD   FirstThunk;

 指向一个结构体数组的一个相对虚拟地址,结构体数组叫输入地址表 (IAT:import address table)

注:这两个字段在PE文件未加载到内存中以前,都指向了相同类型的结构体_IMAGE_THUNK_DATA32 ,在文件加载到内存以后输入地址表(IAT)会由加载器把相应的PE文件的函数地址覆盖到这里,这时,输入地址表才是真正的函数地址
 

DWORD   Name;

导入的PE文件的名字的相对虚拟地址(RVA)

typedef struct _IMAGE_THUNK_DATA32 {
    union {
        DWORD ForwarderString;     
        DWORD Function;             
        DWORD Ordinal;
        DWORD AddressOfData;      
    } u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;

结构体介绍

结构体为联合体 也就是说只有四个字节

此结构体在PE文件未加载到内存的时候 IMAGE_IMPORT_DESCRIPTOR结构体的OriginalFirstThunk和FirstThunk存的数据是一样的 也就是都指向了这个结构体

字段介绍

Ordinal :加入函数时序号导入的 去掉最高位 也就是函数的序号了

(注:如何判断函数是序号导入的 通过查看Ordinal最高位 是否为1 是1去掉这个最高位的1 剩下的就是函数的序号

为什么这个字段的最高位为1就是以序号导入的?

这是因为 一个进程有4GB的虚拟内存 低两个G由用户使用 高两G为操作系统使用 也就是说一个函数最大的内存地址也就只能是 0x7fff ffff(转换成二进制最高位是0),超过这个内存就是操作系统的范围了,我们是不能使用的 所以最高位不可能为1)

 AddressOfData: 最高位不是1 这个字段指向了IMAGE_IMPORT_BY_NAME结构体

​​​​​​​typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD    Hint;
    CHAR   Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

字段介绍

Hint:函数导出时序号

Name[1]:函数的名字字符数组,0结尾

文件中找导入表信息

导入表地址

PE文件 - 导入表 一共导入了三个库 ,最后是全0结尾PE文件 - 导入表

 举例找ExitProcess的地址

 这个函数在kernel32.dll中 那我们现在就找这个dll

1.4行为一组 首先看0x0000227c(注:此地址为name字段,RVA 需转换FOA)指向了 导入动态库的名字

PE文件 - 导入表

这个库为user32.dll

PE文件 - 导入表

接着找下一项  还是看0x000022ba(name字段 rva需转换)

PE文件 - 导入表

动态库名称为kernel32.dll 我们要找的函数就在这个动态库里面

PE文件 - 导入表

接着找OriginalFirstThunk( 地址0x000020f0  rva需转换)  三个IMAGE_THUNK_DATA32 结构

我们先找第一个 0x00002296的位置 首先最高位不是0 说明不是函数序号导入 所以这个rva指向了

 IMAGE_IMPORT_BY_NAME结构PE文件 - 导入表

 此结构为 IMAGE_IMPORT_BY_NAME 前两个字节是函数导出时的序号 后面为函数的名字 以0结尾 这个函数不是我们要找的函数继续

PE文件 - 导入表

下一个函数名字的地址为0x2288 函数名称就是我们要找的 这个函数在函数名称表(INT)下标为1的地方 相对应的在函数地址表(IAT)下标为1的地方找函数的地址

PE文件 - 导入表

 我们再看导入表 函数地址表(iat)的地址为0x20f0 我们转过去查看

PE文件 - 导入表

惊奇的发现和函数名称表存储的信息一模一样 正如我们之前所说在文件未加载到内存之前 是无法得到真正的函数地址的,所以两个表是一模一样的

PE文件 - 导入表

 PE文件 - 导入表

内存中找导入表信息

定位导入表 

内存中和文件中有所不同的是IAT表真正填写函数地址了

所有的rva不用转换了 直接加基地址即可

PE文件 - 导入表

 

老规矩还是找ExitProcess 还是在kernel32.dll中,和在文件中找的方法是一样的先看name字段为0x0000227c加基地址为0x0040227c 这个库为user32.dll

PE文件 - 导入表

 找下一个动态库 我们看name的字段地址为0x22ba 加基地址为0x004022ba

PE文件 - 导入表

第二项导入目录表 我们看INT为0x000020f0加基地址0x004020f0

PE文件 - 导入表

三个指向函数名的rva 我们直接找0x00002288加基地址为0x00402288 下标项为1

PE文件 - 导入表

PE文件 - 导入表

 我们接着找IAT表0x00002010加基地址0x00402010 跳过来以后我们把内存转为地址查看

PE文件 - 导入表

这就是我们的IAT函数地址表 我们要找的函数就在函数地址表的下标为1的位置

PE文件 - 导入表

windows加载导入表的流程

1.首先会检查IAT表是否为空,如果为空则循环遍历到下一个导入表

2.如果不为空则读取导入表的name字段得到dll名称,并调用lodalibaray得到动态库的模块句柄

3.接着找INT表,判断函数是否为序号导出

        (1)如果是序号导出直接调用GetProcAddress获取函数地址填写进IAT

        (2)如果名称导出获取每一个函数名称,并调用GetProcAddress函数获取函数地址

4.把函数的地址填写进IAT表

导入表的两种情况

情况1:IAT表为空:

IAT表为空,说明这个导入表是无效的,应继续循环找下一个函数导入表

情况2:INT表为空

INT表为空则读取IAT表,把IAT表所指示的位置当函数名称在获取函数地址,并填写到IAT表,覆盖这个函数名称的地址

上一篇:Apache Shiro 学习记录5


下一篇:PE文件病毒