什么是导入表?
导入表记录了一个exe或者一个dll所用到的其他模块导出的函数。
数据目录表的第二个元素记录着导入包的位置,导出表我们上节课已经解析过 了,今天我们来解析导入表。
导入表结构体解析
导入名称表: INT
导入地址表:IAT
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;// 0 for terminating null import descriptor
DWORD OriginalFirstThunk;// RVA 指向(IMAGE_THUNK_DATA)结构体数组 INT
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 时间戳 0 if not bound
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;//RVA dll文件名称
DWORD FirstThunk; // RVA to IAT 导入地址表 IMAGE_THUNK_DATA数组
} IMAGE_IMPORT_DESCRIPTOR;
重要字段:
- OriginalFirstThunk:是导入表的导入名称表(INT)的RVA。
- Name:作为导入的DLL文件的名称,RVA。
- FirstThunk:是导入表的导入地址表(IAT)的RVA。
OriginalFirstThunk-->IMAGE_THUNK_DATA 如果IMAGE_THUNK_DATA 最高位为1 则是按序号导入,否则就是按名称导入的。
根据OriginalFirstThunk或者FirstThunk可以得到导入名称表INT和IAT,表示INT和IAT的结构体如下:
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal;
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
- Ordinal:用来标记此函数是按序号导入还是按照名称导入
INT或者IAT的 AddressOfData字段:是一个RVA,通过它可以得到按名称导入的表 PIMAGE_IMPORT_BY_NAME
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;//导入函数索引(根据编译器的不同这个地方放的东西不一样)
CHAR Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
- Name:每一个函数的名称
寻找导入表的位置
我们使用010editor工具来寻找导入表的位置:
-
首先找到数据目录表的第二个元素,它所存储的偏移及大小表示的是导入表的偏移和大小: 1B17Ch 和80大小。
-
然后在区段中寻找合适位置:可以看到,第五个区段的RVA是1B000h,在内存中的大小是2339d ,导入表的RVA正好位于此字段内,因此使用公式:
导入表的FOA=导入表的RVA - 区段的RVA +区段的FOA
,得到了导入表的在文件中的偏移地址: 1b17c - 1b000 + 8000 = 817C -
得到的 817C就是我们在文件中的偏移地址,搜索可得:这就是导入表的位置(注意:817C 加上文件基址才是真正的位置,010editor工具可以省略基址直接由偏移得到,但是在代码解析或则其他工具中,我们必须加上文件基址)。
代码解析导入表
void cPE::GetImportTable()
{
//获取导入表的地址
IMAGE_DATA_DIRECTORY ImPortAddr = pOptionHeader->DataDirectory[1];
//得到导入表
PIMAGE_IMPORT_DESCRIPTOR ImportTable = (PIMAGE_IMPORT_DESCRIPTOR)(RvaToFoa(ImPortAddr.VirtualAddress) + FileBuff);
if (ImportTable == NULL)
{
printf("导入表为空!\n");
return;
}
//导入表会有多个,导入表只要不为空,则它的字段也一定不为空,如果它的字段为空,则此导入表遍历到了最后
while (ImportTable->OriginalFirstThunk)
{
printf("TimeDataStamp=%d\n", ImportTable->TimeDateStamp);
//RVA Name
char* dllName=RvaToFoa(ImportTable->Name) + FileBuff;
printf("Dll文件名称: %s\n", dllName);
//得到导入名称表 INT
PIMAGE_THUNK_DATA pThunkData = (PIMAGE_THUNK_DATA)(RvaToFoa(ImportTable->OriginalFirstThunk) + FileBuff);
while (pThunkData->u1.Function)
{
//判断是否按序号导入
if (pThunkData->u1.Ordinal & IMAGE_ORDINAL_FLAG32) //0x80000000
{
printf("按序号导入: %d\n", IMAGE_ORDINAL32(pThunkData->u1.Ordinal));
}
else
{
//否则就是按名称导入
PIMAGE_IMPORT_BY_NAME importName = (PIMAGE_IMPORT_BY_NAME)(RvaToFoa(pThunkData->u1.AddressOfData) + FileBuff);
printf("按名称导入: %s\n", importName->Name);
}
pThunkData++;
}
ImportTable++;
}
}
运行如下:打印出调用此程序的导入的所有的dll文件所加载的函数,按名称或者序号导入。
可以看到:导入表的位置和我们在010editor中解析的导入表的位置一致。