导入表示PE数据组织的一个很重要的组成部分。通过分析导入表数据,可以获得诸如PE文件的指令中调用了多少外来的函数,以及这些外来函数都存在于哪些动态链接库里等信息。
导入表示数据目录中注册的数据类型之一,其描述信息位于数据目录的第2个目录项中。
导入表描述符IMAGE_IMPORT_DESCRIPTOR
导入表数据的起始是一组导入表描述结构,每组20个字节。最后一组为全0结构,表示导入表描述已经结束。Windows在查找导入表的时候并不一定要求最后一组的20个字节都为0,只要其中的字段Name是0就已经满足结束条件了。导入表的每一组都是一个结构,称为导入表描述符IMAGE_IMPORT_DESCRIPTOR,具体结构如下:
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;
OriginalFirstThunk:双字。因为它是指向另外数据结构的指针,该字段指向一个包含了一系列结构的数组。
指向的数组中的每个结构定义了一个导入函数的信息,最后以一个内容为全0的结构作为结束。指向的数组中每一项为一个结构,此结构名称为IMAGE_THUNK_DATA.该结构实际上只是一个双字,但在不同的时刻却拥有不同的解释。该字段有两种解释:
1 双字最高位为0,表示导入符号是一个数值,该数值是一个RVA。
2 双字最高位为1,表示导入符号是一个名称。
TimeDateStamp:双字。时间戳,一般不用,多为0.
ForwarderChain:双字。链表的前一个结构。
Name:双字。这里的Name是一个RVA,它指向该结构所对应的DLL文件的名称,而这个名称是以‘\0’结尾的ANSI字符串。
FirstThunk:双字。与OriginalFirstThunk相同,它指向的链表定义了针对name这个动态函数引入的所有动态函数。
IMAGE_THUNK_DATA结构如下:
struct _IMAGE_THUNK_DATA{
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal;
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA;
如果该结构最高位为0,表示导入符号是一个数值,该数值是一个RVA。根据RVA指向了另外一个结构IMAGE_IMPORT_BY_NAME,这个结构不确定,IMAGE_IMPORT_BY_NAME结构如下:
struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
CHAR Name[1];
} ;
Hint:双字。函数的编号,在DLL中对每个函数都进行编号,访问函数时可以通过名称访问,也可以通过编号访问。
Name:大小不确定,函数名字字符串的具体内容,以“\0”作为字符串结束标志。
导入表编程代码如下:
/************************************************************************/
/*
功能:打印导入表信息
参数:lpFileBuf:起始地址
返回:无
*/
/************************************************************************/
void PrintImport(PVOID lpFileBuf)
{
//获取DOS头
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpFileBuf;
//获取PE头
size_t stPEHeadAddr = (size_t)lpFileBuf + pDos ->e_lfanew;
PIMAGE_NT_HEADERS32 pNT = (PIMAGE_NT_HEADERS32)stPEHeadAddr;
//数据目录
PIMAGE_DATA_DIRECTORY pDir = pNT->OptionalHeader.DataDirectory;
//获取数据目录表中入表地址
PIMAGE_DATA_DIRECTORY pImportDir = (PIMAGE_DATA_DIRECTORY)&pDir[IMAGE_DIRECTORY_ENTRY_IMPORT];
//获取导入
PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)((size_t)lpFileBuf +
RVAToOffset(pImportDir->VirtualAddress, lpFileBuf));
//如果结束时,用name进行判断。
while ( pImport->Name )
{
PCSTR szName = (PCSTR)((size_t)lpFileBuf + RVAToOffset(pImport->Name,lpFileBuf));
printf("\n==========%s\t==========\n",szName);
printf("OrginalFirstThunk:%08X\t",pImport->OriginalFirstThunk);
printf("日期时间标志:%08X\t",pImport->TimeDateStamp);
printf("ForwarderChain:%08X\t",pImport->ForwarderChain);
printf("名称:%08X\t",pImport->Name);
printf("FirstThunk:%08X\t",pImport->FirstThunk);
//INT表
PIMAGE_THUNK_DATA32 pINT = (PIMAGE_THUNK_DATA32)((size_t)lpFileBuf + RVAToOffset(pImport->OriginalFirstThunk,lpFileBuf));
//如果结束时,pINT会是0
while (pINT->u1.AddressOfData)
{
if (!IMAGE_SNAP_BY_ORDINAL32(pINT->u1.AddressOfData))
{
PIMAGE_IMPORT_BY_NAME pByName =(PIMAGE_IMPORT_BY_NAME)((size_t)lpFileBuf + RVAToOffset( pINT->u1.AddressOfData , lpFileBuf));
printf("%08X\t%04X %s\n",pINT->u1.Function,pByName->Hint, pByName->Name);
}
else
{
printf("%04X[Null]\n",pINT->u1.Ordinal);
}
pINT++;
}
pImport++;
}
}