PE文件结构(三) 输入表


 

参考

书:《加密与解密》

视频:小甲鱼 解密系列 视频

 

 

输入表

 

    输入函数,表示被程序调用但是它的代码不在程序代码中的,而在dll中的函数。对于这些函数,磁盘上的可执行文件只是保留相关的函数信息,如函数名,dll文件名等。在程序运行前,程序是没有保存这些函数在内存中的地址。当程序运行起来时,windows加载器会把相关的dll装入内存,并且将输入函数的指令与函数真在内存中正的地址联系起来。输入表(导入表)就是用来保存这些函数的信息的。

 

    在   IMAGE_OPTIONAL_HEADER 中的 DataDirectory[16]  数组保存了 输入表的RVA跟大小。通过RVA可以在OD中加载程序通过 ImageBase+RVA 找到 输入表,或者通过RVA计算出文件偏移地址,查看磁盘中的可执行文件,通过文件偏移地址找到输入表。

   

    输入表是以一个IMAGE_IMPORT_DESCRIPTOR(IID)数组 开始的,每一个被PE文件隐式的链接进来的dll都有一个IID,IID数组的最后一个单元用NULL表示。

 

IMAGE_IMPORT_DESCRIPTOR 结构:

 

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
	_ANONYMOUS_UNION union {              //00h
		DWORD Characteristics;
		DWORD OriginalFirstThunk; 
	} DUMMYUNIONNAME;
	DWORD TimeDateStamp;                  //04h
	DWORD ForwarderChain;                 //08h
	DWORD Name;                           //0Ch
	DWORD FirstThunk;                     //10h
} IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR;

 

 

 

    其中Name是dll名字的指针。OriginalFirstThunk指向一个IMAGE_THUNK_DATA数组叫做输入名称表Import Name Table(INT),用来保存函数,FirstThunk也指向IMAGE_THUNK_DATA数组叫做输入地址表Import Address Table(IAT)。

 

IMAGE_THUNK_DATA 结构:

 

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

 

 

    当IMAGE_THUNK_DATA 的值最高位为1时,表示函数是以序号方式输入,这时低31为被当作函数序号。当最高位是0时,表示函数是以字符串类型的函数名方式输入的,这时,IMAGE_THUNK_DATA 的值为指向 IMAGE_IMPORT_BY_NAME 的结构的RVA。

 

 

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


Hint 表示这个函数在其所驻留dll的输出表的序号,不是必须的。

 

Name 表示 函数名,是一个ASCII字符串以0结尾,大小不固定。

 

 

    INT保存的是这个程序导入这个dll中函数信息,它是固定的不会被修改。但是IAT会在程序加载时被重写,当程序加载时,它会被PE加载器重写成 这些函数的在内存中的真正地址。即把它原来指向的IMAGE_IMPORT_BY_NAME 改成 函数真正的地址。

 

 

那为什么要两个 IMAGE_THUNK_DATA 数组?

   

    当程序加载时,IAT 会被PE加载器重写,PE加载器先搜索INT,PE加载器迭代搜索INT数组中的每个指针,找出 INT所指向的IMAGE_IMPORT_BY_NAME结构中的函数在内存中的真正的地址,并把它替代原来IAT中的值。当完成后,INT就没有用了,程序只需要IAT就可以正常运行了。

 

看下面图,这个是可执行程序在磁盘中的时候:

 

这个是当程序被加载的是后:

 



实例分析:

 

    先找到输入表RVA,通过IMAGE_OPTIONAL_HEADER 中的最后一个项  IMAGE_DATA_DIRECTORY 可以知道 输入表相对与PE文件头的偏移量为80h可以找到输入表达RVA。

 

图片1

   

    但这个是RVA不是文件偏移地址。通过转换可以知道,输入表的文件偏移地址为850h,

 

 

 

    查看850h,即IID,可以看到这个程序有两个IID,即链接了 两个dll,看到一个IID,可以知道它的OriginalFirstThunk 是 2098h  FirstThunk  为200Ch,它们转换后的文件偏移地址分别为898h 和 80Ch

 

图片2  IID数组

 

 

 

  查看OriginalFirstThunk跟FirstThunk ,可以发现这里INT跟IAT的内容是一样的,先看看第一个函数的信息,因为第一位为1,所以这里00002122h 表示 IMAGE_IMPORT_BY_NAME 的RVA,转化为文件偏移值为922h

 

图片3  INT跟IAT

 

 

 

    查看922h 可以看函数名。

 

图片4:

 

 

 

 

 

    我们可以从上面看到程序在磁盘中时 INT 与IAT内容一样,都是指向 IMAGE_IMPORT_BY_NAME 。用OD加载程序,查看INT与IAT的内容

 

图片5 INT

 

图片6 IAT

 

    可以发现INT没有发生变化,IAT变成了例如77D3C702h,IAT中的RVA被改成了函数的真正的地址。

   

 

    查看77D3C702h,就可以看到这个函数

 

图片7