PE文件(十二)导入表

avatar
作者
筋斗云
阅读量:0

导入表

导入表的引入

当一个PE文件(如.dll/.exe等)需要使用别的模块的函数,也叫做依赖某模块,就需要一个清单来记录使用的模块(一般为.dll文件,为方便理解,以后我们将模块都认为是.dll文件)及使用的函数的相关信息(如使用了哪些DLL、使用了这个DLL里的哪些函数、叫什么名、去哪找等),这个清单就叫做导入表。

定位导入表

一个PE文件的数据目录第二个结构体就是导入表数据目录,根据导入表数据目录的VirtualAddress成员经过RVA转FOA找到导出表

导入表结构

如下是导入表结构:

struct _IMAGE_IMPORT_DESCRIPTOR{                       

    union{                     

        DWORD Characteristics;                                

        DWORD OriginalFirstThunk;

    };                     

    DWORD TimeDateStamp;

    DWORD ForwarderChain;                                     

    DWORD Name;

    DWORD FirstThunk; 

};

一个导入表大小为20字节

DWORD OriginalFirstThunk:该值为指向导入名称表(即INT表)的RVA

DWORD FirstThunk:该值为导出地址表(即IAT表)的RVA

DWORD TimeDateStamp:时间戳,用于判断该.dll文件是否有绑定导入表或IAT表中是否已经绑定绝对地址。当该值为0x00000000时,表示表示这个导入表结构对应的DLL中的函数绝对地址没有绑定到IAT表中。当该值为0xFFFFFFFF时,表示这个导入表结构对应的DLL中函数绝对地址已经绑定到IAT表中

DWORD Name:指向使用到的.dll名字字符串的RVA,该字符串以0结尾。如果Name指向的.dll名称为“user32.dll”,那么这个导入表中记录的就是该PE文件使用user32.dll的相关信息

注意:

1.当一个导入表后跟了一个导入表大小即20个字节长度的0时,表示该PE文件的所有导入表结束。

2.一般来说,程序通过导入表中的信息来装载对应的.dll文件到虚拟内存中(比如通过导入表的所有Name成员,获取系统要装载的.DLL的名字)。但导入表中的某些信息则需要在.DLL装载内存完成后(比如IAT表)才能修复

3.如果导入表的某个结构中OriginalFirstThunk和FirstThunk的值都为0,这意味着该PE文没有使用这个DLL中的任何函数。此时操作系统就不会加载这个导入表结构对应的DLL

4.一个PE文件使用到的.dll文件的数量就是该PE文件的导入表数量。

INT表

定位INT表

INT表定位:通过OriginalFirstThunk找到INT表

INT表结构

结构如下图:

上图是一般情况下INT表的样子,元素有IMAGE_THUNK_DATA也有数值,但实际上这些元素都是属于IMAGE_THUNK_DATA32这一种结构体的不同形式表现。实际上的INT表如下:

图中表的成员为结构体,结构如下:

struct _IMAGE_THUNK_DATA32{                         

    union{                        

        BYTE ForwarderString;                          

        DWORD Function;                         

        DWORD Ordinal;     

        _IMAGE_IMPORT_BY_NAME* AddressOfData;

    };                         

};                  

这个结构体其实就是一个联合体,4字节大小。

元素表现形式不同的原因:一个.DLL中的函数可以以函数名称导出,也可以以序号(NONAME)导出,所以当一个PE文件使用别的.DLL中的函数时要考虑这个函数的名字和序号两种情况。因此INT表中的元素有IMAGE_THUNK_DATA和序号两种形式。

元素形式的判断方法如下:

如果INT表的元素最高位为1:那么除去最高位剩下31位的值,就是函数的导出序号

如果INT表的元素最高位为0:那么这个值为指向IMAGE_IMPORT_BY_NAME结构体的RVA

注意:

1.INT表中的元素个数就是该PE文件使用INT表对应的.dll文件函数的数量

2.当找到INT表后往后依次遍历出现有连续四字节长度的0时,表示该INT表结束

定位导入函数名称表

定位导入函数名称表:通过INT表中 _IMAGE_IMPORT_BY_NAME* AddressOfData元素,即可找到函数名称表的偏移地址

导入函数名称表结构

_IMAGE_IMPORT_BY_NAME结构体:

struct _IMAGE_IMPORT_BY_NAME{                       

    WORD Hint;

    BYTE Name[1];  //函数名称,以0结尾  

};                      

Hint:这个值由编译器决定是否为空,如果不为空,表示函数在导出表中的索引。一般不使用这个值

Name:由于函数名称的字符个数是不确定的,所以此处只给出一个1字节的字符数组用来存储函数名称的第一个字符。通过Name获取该字符串的首地址,再依次往后遍历,直到遇到字符串的结尾字符\0,便是完整的函数名称

IAT表

IAT表的引入

当我们在程序中使用其他模块中的函数时,我们以程序调用MessageBoxA()为例,进行演示。程序运行后我们进入反汇编开始观察。

我们会发现,系统在调用该函数时,call后跟的不是一个绝对地址,而是一个间接地址。

我们在内存中查找该地址

我们发现,这个地址内容是一张表,记录着我们使用的函数的实际地址。我们在这个程序只使用了MessageBoxA()函数,所以上表中只有第一行四个字节记录了数据,也就是MessageBoxA()函数的地址。而这张表就是IAT表,也叫做导入函数地址表

定位IAT表

方法一:通过导入表中的FirstThunk(RVA)成员,找到IAT表。

方法二:通过PE文件数据目录的第13个结构体中的VirtualAddress(RVA)成员,找到IAT表

导入表和IAT表

IAT表在程序运行前后是不一样的,因此导入表和IAT表的具体关系结构有以下两种:

PE文件运行前:

PE文件运行后:

PE文件运行前:IAT表中的内容和INT表的内容是一样的

PE文件运行后:IAT表中的内容就变成了该PE文件使用的对应的.dll中的函数在内存中的绝对地址

IAT表修改过程

1.装载PE文件到内存:当PE文件运行后,系统先装载.exe,再装载各个使用到的.dll到PE文件的虚拟内存中。

2.调用GetProcAddress()函数:内存装载完成后,系统首先遍历导出表中每个结构的INT表,无论遍历到的是函数名还是序号,都会作为其中一个参数传给系统函数GetProcAddress()

3. IAT表修改:GetProcAddress()函数根据函数名或序号返回对应函数在内存中的绝对地址,接着把绝对地址存入到IAT表的对应位置

4.修改call语句地址:

PE文件运行前:call [0x....]后面间接寻址的地址0x....是INT表中某个元素的地址;

PE文件运行后:call [0x....]后面间接寻址的地址0x....改成了IAT表中某个元素的地址;

INT表和IAT表的关系

原因一:PE文件加载后,INT表作为函数名字的备份。

原因二:当PE文件加壳或加密以后,函数地址发生改变。当程序运行后,IAT表中原本的记录的函数的地址就错了,就无法通过IAT表找函数了。此时通过INT表查找函数到函数地址就可以修复IAT表了。具体流程如下:PE文件运行前后INT表中的元素都指向函数名称或者序号。我们通过遍历该程序导入表的INT表,最终在某一个导出表结构中的INT表中找到要查找的函数名称(或序号)。此时通过将LoadLibrary()返回该函数所属的.dll的句柄(即所属DLL的ImageBase)和函数名称传入GetProcAddress()函数,就可以得到该函数地址。此时就可以修改IAT表了

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!