MemoryModule阅读与PE解析(二)---导入表
首先来了解导入表的概念
头文件:
#ifndef DLL_FUC
#define DLL_FUC extern"C" __declspec(dllimport)
#endif
实现:这里函数只是简单的输出 HelloA
void HelloExprotFunctionA()
{
printf("HelloA\n");
}
使用:
#include "../x86Dll/x86Dll.h"
#pragma comment(lib,"../debug/x86Dll.lib")
HelloExprotFunctionA();
我们调试反汇编发现,其调用指向内存0x00318378 内存位置的内容,即地址0x52D013A0h,F11进去之后发现确实跳转到了该地址,该地址即为HelloExprotFunctionA函数的地址。
使用ProcessHacker得到进程模块基地址如下:
由此算到上面的那个内存地址0xC83788的RVA为:0x18378
使用CFF Explorer 打开exe 然后查看RVA 对应内容发现:
保存的是一个RVA,得到了导入函数名,后面还有模块名称
我们发现exe文件中并没有HelloExprotFunctionA函数的地址,该位置出存储的仅仅是一个结构的地址而已,然而当程序运行起来之后,其存储的值为函数的地址,请看下图
图片来源:http://blog.youkuaiyun.com/evileagle/article/details/12357155
导入表中有一个成员FirstThunk指向IAT结构,在PE加载入内存之前,其值与OriginalFirstThunk相同,指向一个结构体,在PE被加载之后,FirstThunk 中的内容变为函数的真实地址,在其函数中对于导入函数的引用就是取的导入表中对应位置的地址,然后跳转到目标函数运行。
我们来看call 指令的地址,计算得到其RVA后在exe 中查看其内容如下:
我们发现其存储的就是一个绝对地址,而这个地址在exe 初始化的时候由于上一节提到的修正了重定向项,其值变为真实的IAT 对应的项的地址。
PE 未加载时,该函数引用的是一个VA----其刚好对应于IAT 中对应项的RVA。这样,我们就明白了PE 文件引用外部函数的过程,导入表的修订,就是将IAT中的对应的项的地址修正为所要引用的函数的真实地址即可,所谓的对应,就是与上图中函数编号-函数名的结构体对应,根据该结构体来获得PE引用的外部函数的(DLL 名称,函数名称)来得到对应的函数地址,然后填充IAT中对应的项即可。
简单介绍相关结构:
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 forterminating null import descriptor
DWORD OriginalFirstThunk;// PIMAGE_THUNK_DATA结构的其实RVA,为0表示结束
} DUMMYUNIONNAME;
DWORD TimeDateStamp; //0 如果没有绑定
// -1 绑定
// O.W. date/time stamp of DLL bound to (Old BIND)
DWORD ForwarderChain; //-1 if no forwarders
DWORD Name; // DLL 名称
DWORD FirstThunk; //IAT的RVA
}IMAGE_IMPORT_DESCRIPTOR;
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORDForwarderString; // PBYTE
DWORD Function; //PDWORD
DWORDOrdinal;
DWORDAddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
}IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32* PIMAGE_THUNK_DATA32;
这里存储的是DLL 的名称和序号,所谓需要就是DLL 中导出函数的一个序号,一般不用
typedef struct_IMAGE_IMPORT_BY_NAME{
WORD Hint;
BYTE Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
下面来看修正导入表的函数
static BOOL
BuildImportTable(PMEMORYMODULEmodule)
{
unsigned char *codeBase = module->codeBase;
PIMAGE_IMPORT_DESCRIPTOR importDesc;
BOOL result = TRUE;
PIMAGE_DATA_DIRECTORY directory =GET_HEADER_DICTIONARY(module,IMAGE_DIRECTORY_ENTRY_IMPORT);
if (directory->Size == 0) {
return TRUE;
}
importDesc = (PIMAGE_IMPORT_DESCRIPTOR) (codeBase + directory->VirtualAddress);
for (; !IsBadReadPtr(importDesc,sizeof(IMAGE_IMPORT_DESCRIPTOR)) && importDesc->Name; importDesc++) {
uintptr_t *thunkRef;
FARPROC *funcRef;
HCUSTOMMODULE *tmp;
HCUSTOMMODULE handle = module->loadLibrary((LPCSTR) (codeBase +importDesc->Name),module->userdata);
if (handle == NULL) {
SetLastError(ERROR_MOD_NOT_FOUND);
result = FALSE;
break;
}
tmp = (HCUSTOMMODULE *) realloc(module->modules, (module->numModules+1)*(sizeof(HCUSTOMMODULE)));
if (tmp == NULL) {
module->freeLibrary(handle,module->userdata);
SetLastError(ERROR_OUTOFMEMORY);
result = FALSE;
break;
}
module->modules = tmp;
module->modules[module->numModules++] = handle;
if (importDesc->OriginalFirstThunk) {
thunkRef = (uintptr_t *) (codeBase +importDesc->OriginalFirstThunk);
funcRef = (FARPROC *) (codeBase +importDesc->FirstThunk);
} else {
// no hint table
thunkRef = (uintptr_t *) (codeBase +importDesc->FirstThunk);
funcRef = (FARPROC *) (codeBase +importDesc->FirstThunk);
}
for (; *thunkRef; thunkRef++, funcRef++) {
if (IMAGE_SNAP_BY_ORDINAL(*thunkRef)) {
*funcRef = module->getProcAddress(handle,(LPCSTR)IMAGE_ORDINAL(*thunkRef),module->userdata);
} else {
PIMAGE_IMPORT_BY_NAME thunkData = (PIMAGE_IMPORT_BY_NAME) (codeBase + (*thunkRef));
*funcRef = module->getProcAddress(handle,(LPCSTR)&thunkData->Name,module->userdata);
}
if (*funcRef == 0) {
result = FALSE;
break;
}
}
if (!result) {
module->freeLibrary(handle,module->userdata);
SetLastError(ERROR_PROC_NOT_FOUND);
break;
}
}
return result;
}
IMAGE_SNAP_BY_ORDINAL 宏用于判断当前的导入函数是否是按照序号导入的,如果是的话,下面的宏IMAGE_ORDINAL用于取出序号。
#define IMAGE_ORDINAL_FLAG32 0x80000000
#define IMAGE_SNAP_BY_ORDINAL32(Ordinal) ((Ordinal &IMAGE_ORDINAL_FLAG32) != 0)
如果最高位为1,表示按照序号导入
#define IMAGE_ORDINAL32(Ordinal) (Ordinal & 0xffff)
表示取低四字节来表示序号。
然后导入表的修正已经结束了,剩下的一节介绍剩余知识。