调用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)

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;
这个结构体只有四个字节,在不同的情况下表示是意义不一样。
DWORD TimeDateStamp
名字看着是一个时间戳,但是如果该字段为0,FirstThunk也就是IAT和INT执行的是一个表。
当为-1的时候,IAT的表存放的是真实的地址。
DWORD Name
指向dll文件名称的RVA
FirstThunk 导入地址表(IAT)的
导入名称表叫 INT(Import Name Table),导入地址表叫IAT,两者对应结构体一样,只是用法和名称不同。

区分导入函数如何导入
导入函数可以通过序号导入和函数名称导入,通过区分_IMAGE_THUNK_DATA32结构体中的Ordinal来判断,如果二进制位的最高位是1就按照序号导入,如果不是1就是按照函数名称导入。
如果按照序号导入,则_IMAGE_THUNK_DATA32的低31位则为序号值。
如果按照函数名称导入则_IMAGE_THUNK_DATA32结构体AddressOfData字段指向一个结构体IMAGE_IMPORT_BY_NAME:
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; //导入函数序号,该值可以为0
CHAR Name[1];//存放导入函数的名称
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
INT和IAT的关系以及作用:
在PE没有被加载到内存时,IAT和INT指向的是同一个内存地址:

在PE文件被加载到内存中后,IAT表被替换为函数的入口地址:

代码解析导入表
void GetImportTable()
{
//拿到可选PE头的数据目录表的第二个元素的内容
IMAGE_DATA_DIRECTORY directory = pOptionalHeader->DataDirectory[1];
//拿到第一个导入表
PIMAGE_IMPORT_DESCRIPTOR ImportTable = PIMAGE_IMPORT_DESCRIPTOR(RvaToFoa(directory.VirtualAddress) + FileBuff);
//循环遍历每一个导入表
while (ImportTable->Name)
{
//输出该导入表的文件的名字
char* pName = RvaToFoa(ImportTable->Name) + FileBuff;
std::cout << pName << std::endl;
//输出导入表的函数名称表
PIMAGE_THUNK_DATA INT = PIMAGE_THUNK_DATA(RvaToFoa(ImportTable->OriginalFirstThunk) + FileBuff);
//导入表地址
PIMAGE_THUNK_DATA IAT = PIMAGE_THUNK_DATA(RvaToFoa(ImportTable->FirstThunk) + FileBuff);
PIMAGE_IMPORT_BY_NAME temp = { 0 };
while (INT->u1.AddressOfData)//当遍历到的是最后一个是时候是会为0,所以随便遍历一个就好
{
if (INT->u1.Ordinal & 0x80000000)//利用二进制首位第一个是否为1来判断是不是按照序号导入
{
printf("按序号导入时函数序号为%d\n", INT->u1.Ordinal & 0x7FFFFFFF);
}
else //如果 不为1表示按照名称导入
{
temp = (PIMAGE_IMPORT_BY_NAME)(RvaToFoa(INT->u1.AddressOfData) + FileBuff);
if (temp->Hint == NULL)
printf("函数名称为:%s,该函数没有序号\n", temp->Name);
printf("函数序号和名称:%x,%s\n", temp->Hint, temp->Name);
}
INT++;//INT在INT数组中下移
}
ImportTable++;
}
}
浙公网安备 33010602011771号