PE文件解析-导入表
调用dll文件函数的原理
程序在调用dll文件函数的时候,并不是把函数编译到当前程序中,
而是把函数的地址保存到了当前文件中
在文件当中,对应的函数地址部分存放的是函数名称
一个进程空间的exe dll文件如何被加载到内存
1 LoadLibraryA显示加载DLL文件,OS把exe调用到内存中后根据exe需要调用的dll文件,再把dll调用进内存中。
HMOULE 等于的是dll文件的IMAGEBASE,也就是首地址
2 GetProcessAddr(HMOUDLE,fun1); 就拿到dll中对应的函数地址
exe文件调用的动态链接库在内存和在硬盘中的区别
函数地址的不同,加载到内存中是一个具体的函数地址,而在硬盘中是一个函数名称,通过函数名称加载得到函数地址。
导入表
导入表和导出表所属位置相同,都是可选PE头的数据目录表里面的,但是是第二个元素
但是这个东西其实是一个数组,因为导入表和输出表不一样,有导入几个dll文件,就有几个导入表。在结束的时候有一个跟该结构体一样大小的为0的结构体作为判断。
导入表结构体
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real date\time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
结构体重点
OriginalFirstThunk
是导入表结构体的第一个字段,是一个共用体,该字段的第二个字段的意思是指向一个导入名称表(INT Import Name Table)
该表是一个IMAGE_THUNK_DATA的结构体
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal;
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
这个结构体只有四个字节,在不同的情况下表示是意义不一样,不仅可以是存放导入函数名称,也可以存放的是导入函数的序号。
区分导入函数如何导入
导入函数可以通过序号导入和函数名称导入,通过区分导入表结构体中DUMMYUNIONNAME的OriginalFirstThunk来判断,如果二进制位的最高位是1就按照序号导入,如果是存的名称,就是直接对应函数名称的RVA。
然后导入名称表又指向一个结构体IMAGE_IMPORT_BY_NAME
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; //导入函数索引
CHAR Name[1];//存放导入函数的名称
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
DWORD TimeDateStamp
名字看着是一个时间戳,但是如果该字段为0,FirstThunk也就是IAT和INT执行的是一个表。
当为-1的时候,IAT的表存放的是真实的地址。
DWORD Name
指向dll文件名称的RVA
FirstThunk 导入地址表(IAT)的
导入名称表叫 INT(Import Name Table),导入地址表叫IAT
导入地址表指向的也是一个IMAGE_THUNK_DATA32结构体
代码解析导入表
首先拿到可选PE头的数据目录表的第二个元素的内容。然后依据该元素指向的内容来遍历每一个导入表,再根据导入表里面的字段来遍历
void CPeUtil::GetImportTable()
{
//拿到可选PE头的数据目录表的第二个元素的内容
IMAGE_DATA_DIRECTORY directory = pOptionalHeader->DataDirectory[1];
//拿到第一个导入表
PIMAGE_IMPORT_DESCRIPTOR ImportTable = PIMAGE_IMPORT_DESCRIPTOR(RvaToFoa(directory.VirtualAddress) + FileBuff);
//循环遍历每一个导入表
while (ImportTable)
{
//输出该导入表的文件的名字
char* pName = RvaToFoa(ImportTable->Name) + FileBuff;
std::cout << pName << std::endl;
//输出导入表的函数名称表
PIMAGE_THUNK_DATA INT = PIMAGE_THUNK_DATA(RvaToFoa(ImportTable->OriginalFirstThunk)+FileBuff);
while (INT->u1.AddressOfData)//当遍历到的是最后一个是时候是会为0,所以随便遍历一个就好
{
if (INT->u1.Ordinal & 0x80000000)//利用二进制首位第一个是否为1来判断是不是按照序号导入
{
printf("函数序号为%d\n",INT->u1.Ordinal & 0x7FFFFFFF);
}
else //如果 不为1表示按照名称导入
{
printf("函数名称为%s\n", INT->u1.Function);
}
INT++;//INT在INT数组中下移
}
ImportTable++;
}
}