PE文件结构-导入表详解

吃可爱长大的小学妹 提交于 2020-01-25 07:37:43

当PE文件运行时,PE文件将被系统加载进入内存中,此时Windows加载器会定位所有的导入的函数或者将定位到的内容填写到可执行文件的某个位置供使用,这个定位是需要借助于可执行文件的导入表来实现的。导入表中存放了所使用的DLL模块名称及导入函数的名称或者函数序列。

在PE文件中定位到PE头部的可选头的位置,可选头IMAGE_OPTIONAL_HEADER中最后一个成员:
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; 
 
IMAGE_DATA_DIRECTORY的结构如下:
 typedef struct _IMAGE_DATA_DIRECTORY
     {
        DWORD VirtualAddress;               //该目录的虚拟地址
        DWORD Size;                        //该目录的大小
    } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

最后一个成员为数组,数组中不同的索引值对应不同的数据目录

所以定位到数据目录中的第二个目录,该目录包含导入表的相对虚拟地址,根据相对于基地址的偏移量找到导入表的首地址,导入表是一个
IMAGE_IMPORT_DESCRIPTOR类型的数组,被导入的每一个DLL都对应数组中的一个IMAGE_IMPORT_DESCRIPTOR结构体,导入表以一个全为0的IMAGE_IMPORT_DESCRIPTOR结构结束。

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
DWORD    OriginalFirstThunk;   //指向导入名称表的相对虚拟地址,即一个IMAGE_THUNK_DATA的结构体数组
DWORD    TimeDateStamp;
DWORD     ForwarderChain;
DWORD     Name;                 //指向DLL名称的相对虚拟地址
DWORD      FirstThunk;         //指向导入地址表的相对虚拟地址,即一个IMAGE_THUNK_DATA的结构体数组
} IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;

每一个IMAGE_THUNK_DATA对应一个DLL导入的函数,同样IMAGE_THUNK_DATA的结构体数组以一个全为0的IMAGE_THUNK_DATA的结构体结束
 typedef struct _IMAGE_THUNK_DATA32 {   
 union {       
     PBYTE  ForwarderString;      
     PDWORD Function;                //被输入的函数的内存地址;       
     DWORD Ordinal;                   //被输入的API的序数值        
 PIMAGE_IMPORT_BY_NAME  AddressOfData;      //指向IMAGE_IMPORT_BY_NAME  
  } u1;
} IMAGE_THUNK_DATA32;

这个结构体的成员只有一个联合体,就相当于结构体实际上只是一个DWORD类型,只是不同时候代表的意义不同:
在磁盘上时,OriginalFirstThunk指向的IMAGE_THUNK_DATA中保存的是指向导入名称表的相对虚拟地址,此时 FirstThunk也指向如此,它们在磁盘中是没有差别的;
在文件被加载在内存中时,OriginalFirstThunk指向的IMAGE_THUNK_DATA中保存的仍然是指向导入名称表的相对虚拟地址,而FirstThunk指向的IMAGE_THUNK_DATA则变成了由Windows加载器填充的导入地址表的相对虚拟地址。


Function表示函数地址,如果是按序号导入Ordinal就有用了,若是按名字导入AddressOfData便指向名字信息。可以看出这个结构体就是一个大的union,union虽包含多个域但是在不同时刻代表不同的意义那到底应该是名字还是序号,该如何区分呢?可以通过结构体判断,如果结构体的最高位是1,就是按序号导入的,这时候,低31位就是导入序号,如果最高位是0,则AddressOfData是一个RVA,指向一个IMAGE_IMPORT_BY_NAME结构,用来保存名字信息,由于Ordinal和AddressOfData实际上是同一个内存空间,所以AddressOfData其实只有低31位可以表示RVA。
了解一下IMAGE_IMPORT_BY_NAME结构。

typedef struct _IMAGE_IMPORT_BY_NAME_
{
WORD Hint;
BYTE Name[1];
}IMAGE_IMPORT_BY_NAME,*PIMAGE_IMPORT_BY_NAME;

Hint: 保存着需要导入函数的序号,这个值不是必须的。
Name: 保存着需要导入函数的名称,函数名不可能由一个字节组成的,这里通过越界访问来达到访问字符串的功能。
 

 

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