PE文件(1)导入表
概念
导入函数的代码位于一个或者多个DLL中,在调用者程序中只保留一些相关的函数信息,包括函数名及其对应的DLL名等。
对于磁盘上的PE 文件来说,它无法得知这些输入函数将来在内存中的地址,只有当PE 文件被装入内存后,Windows的 加载器才将相关DLL 装入,并将调用导入函数的指令和函数实际所处的地址联系起来。
这就是“动态链接”的概念。动态链接是通过PE 文件中定义的“导入表”来完成的,导入表中保存的正是函数名和其对应的DLL 名等。
定位导入表
每一个程序中都有多个模块,包括第一模块exe和众多的dll模块,
其中在第一模块exe中有多张导入表,而在其他每张dll中则都有一张导出表。(一般情况下)
导入表就在exe进程空间的PEB结构的PE文件中(并不是PE文件在PEB结构中),而是该模块的PEB结构中的成员ImageBaseAddress成员是整个PE文件的基地址。所以首先打开对方进程空间PEB,利用PEB中的一个PE文件基地址,PE文件中有导入表的偏移,PE文件的基地址加上这个偏移得到导入表的基地址
1.获得exe模块下PEB结构中PE文件的基地址
PEB Peb = { 0 };
ULONG_PTR ModuleBase = 0;
ModuleBase = (ULONG_PTR)Peb.ImageBaseAddress;
//注意:以下的所有结构都是微软公开的
//整个获取过程是在讲目标进程的PE文件读取到自己的内存中实现的ReadProcessMemory,所以读取NT之前必须先读取Dos头,(NT头的基地址由Dos头确定ModuleBase + ImageDosHeader.e_lfanew,而Dos基地址也就是PE文件基地址是上述的Peb.ImageBaseAddress)
2.获得导入表的偏移:
IMAGE_NT_HEADERS32(NT头)结构体的
(OptionalHeader成员)IMAGE_OPTIONAL_HEADER32结构体中的
(DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]成员IMAGE_DATA_DIRECTORY结构体的VirtualAddress成员
DWORD v1 = 0;
v1 = ImageNtHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
最后得到导入表的基地址:(ModuleBase + v1)
导入表结构
可以看出导入表结构实际上就是三个结构体数组
1.
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk;//输入名称表INT的RAV
};
DWORD TimoeDateStamp; //文件生成时间
DWORD ForwaiderChain;
DWORD Name; //DLL名称的RVA(一个用null作为结束符的ASCII字符串的一个RVA,该字符串是该导入DLL文件的名称,如:KERNEL32.DLL)
DWORD FirstThunk; //输入地址表IAT的RAV
} IMAGE_IMPORT_DESCRIPTOR;
IMAGE_IMPORT_DESCRIPTOR ImageImportDescriptor = { 0 };//导入描述符结构
2.
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; //指向一个转向者字符串的RVA
DWORD Function; //被输入的函数的内存地址
DWORD Ordinal; //被输入的函数的序数值
DWORD AddressOfData; //指向IMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
typedef struct _IMAGE_THUNK_DATA64 {
union {
ULONGLONG ForwarderString; // PBYTE
ULONGLONG Function; // PDWORD
ULONGLONG Ordinal;
ULONGLONG AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA64;
typedef IMAGE_THUNK_DATA64 * PIMAGE_THUNK_DATA64;
当 IMAGE_THUNK_DATA 值的最高位为 1时,表示函数以序号方式导入,这时候低 31位被看作一个函数序号。(可以用预定义值IMAGE_ORDINAL_FLAG32或80000000h来对最高位进行测试)。
当 IMAGE_THUNK_DATA 值的最高位为 0时,表示函数以字符串类型的函数名方式导入,这时双字的值是一个 RVA,指向一IMAGE_IMPORT_BY_NAME 结构。
3.
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
CHAR Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
Hint 字段也表示函数的序号,不过这个字段是可选的,有些编译器总是将它设置为 0,
Name1字段定义了导入函数的名称字符串,这是一个以 0 为结尾的字符串。