《逆向工程核心原理》学习笔记5 PE文件学习——PE头核心IAT,EAT

《逆向工程核心原理》学习笔记5

PE头核心——IAT,EAT

一 IAT——导入地址表
用于记录程序正在使用哪些库的哪些函数的表格
其通过记录各函数的实际地址的位置,并记下
CALL DWORD PTR DS:[实际地址位置]这样一条汇编指令
当执行该PE文件的时候,PE装载器将该函数的地址写到DS:[实际地址位置]处

疑问:为啥不直接将该函数的地址写到CALL指令中呢?

解答:第一,在不同的操作系统,不同的语言(人类语言),不同的服务包(what?)中,dll(动态链接库)的版本有所不同,也就是说,在不同的环境下,库函数在内存中的位置也有所不同,所以只有在执行文件后才知道该函数被加载到哪。所以只能靠PE装载器在执行该文件后找到该函数的位置,再将它放到之前安排的位置上。
第二,dll重定位。dll文件本身也是PE文件,它本身也有ImageBase,即优先加载地址,当两个dll文件的ImageBase发生冲突时,后来的那个dll要进行重定位,PE装载器会重新为它分配一段内存空间,这也导致了函数所在的地址无法在加载前确定。
第三,PE头中表示地址用的是相对虚拟地址(RVA)。
我们知道,RVA+ImageBase=VA
因此,实际地址受ImageBase影响

二 IMAGE_IMPORT_DESCRIPTOR
IMAGE_IMPORT_DESCRIPTOR结构体记录了PE文件需要导入哪些库文件。该结构体位于PE体里,它的地址(RVA)保存在Import Directory[1]中,是可选头中的成员。要在文件中查看它的位置,必须将RVA转换成RAW。

typedef struct _IMAGE_IMPORT_DESCRIPTOR
{
       union
       {
       DWORD Characteristics;
       DWORD OriginalFirstThunk;       //INT(Import Name Table)的地址(RVA)
       };
       DWORD TimeDateStamp;
       DWORD ForwarderChain;
       DWORD Name;                     //库名称字符串的地址(RVA)
       DWORD FirstThunk;               //IAT(Import Address Table)的地址(RVA)
} IMAGE_IMPORT_DESCRIPTOR;

typedef struct _IMAGE_IMPORT_BY_NAME
{
      WORD Hint;                              //ordinal
      BYTE   Name[1];                         //函数名字符串
 } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME; 

有了IID(IMAGE_IMPORT_DESCRIPTOR)后,PE装载器就可以开始将函数导入到IAT里了,其步骤如下
1.通过IID中的Name成员,获取库名
2.装载相应的库,LoadLibrary();
3.读取OriginalFirstThunk成员,获得INT的地址
4.读取INT中数组的值,得到IMAGE_IMPORT_BY_NAME的地址
5.使用IMAGE_IMPORT_BY_NAME的Name[1]或者Hint(ordinal)项,得到需要导入的函数的起始地址
6.读取FirstThunk的值,找到IAT的位置
7.将获得的函数地址(RVA)导入相应IAT数组值
8.重复4~7,直到INT中遇到NULL

三 EAT
只有通过EAT才能准确求得相应的函数的起始地址

四 IMAGE_EXPORT_DIRECTORY

typedef struct _IMAGE_EXPORT_DIRECTORY
{
DWORD  Charcteristics;  
DWORD  TimeDateStamp;          //creation time date stamp
WORD  MajorVersion;
WORD  MinorVersion;           
DWORD  Name;                   //address of library file name
DWORD  Base;                   //ordinal base
DWORD  NumberOfFunctions;       //实际Export的函数的个数
DWORD  NumberOfNames;          //Export中具名的函数个数
DWORD  AddressOfFunctions;     //Export函数地址数组,数组元素个数等于NumberOfFunctions
DWORD  AddressOfNames;         //函数名地址数组,数组元素个数等于NumberOfNames;
DWORD AddressOfNameOrdinals;   //Ordinal地址数组,数组元素个数等于NumberOfNames;
}  IMAGE_EXPORT_DIRECTORY,  *PIMAGE_EXPORT_DIRECTORY;

有了这个结构体后,需要从库中获得一个叫GetProcAdress()的API函数,这个API引用EAT获取指定的API的地址。
过程如下:
1.通过AddressOfNames找到函数名数组。
2.函数名数组有字符串地址,通过strcmp()字符串,查找指定的函数名称(数组索引为name_index)。
3.利用AddressOfNameOrdinals找到ordinal数组。
4.在ordinal数组当中用name_index查找对应的ordinal值。
5.通过AddressOfFunctions转到EAT(函数地址数组)
6在函数地址数组当中用查找到的ordinal值作数组索引,获得指定的函数起始地址。

疑问:是不是name_index和ordinal始终相等?如果始终相等,直接用name_index就是了何必要用ordinal…

解答:有些函数没有名字,所以会出现index!=ordinal的情况

总而言之,通俗地,不规范地说,程序在执行的时候,既要求程序记住自己借了哪些函数,放在哪,而且库也要记住自己借出去哪些函数,这些都是靠IAT和EAT来完成的。

你可能感兴趣的:(《逆向工程核心原理》学习笔记5 PE文件学习——PE头核心IAT,EAT)