目录
导入表是PE数据组织中的一个很重要的组成部分,它是为实现代码重用而设置的.通过分析导入表数据,可以获得诸如PE文件的指令中调用了多少外来的函数,以及这些外来函数都存在于哪些动态链接库里等信息.
Windows加载器在运行PE时会将导入表中声明的动态链接库一并加载到进程的地址空间,并修正指令代码中调用的函数地址.
在数据目录项中一共有四种类型的数据与导入表有关(导入表、导入函数地址表、绑定导入表、延迟加载导入表).
导入数据所在的节通常被命名为.idata,它包含了PE映像中所有导入的符号.导入信息在EXE和DLL中几乎都存在.
导入表定位以及解析(手动FileBuffer)
IMAGE_IMPORT_DESCRIPTOR
结构及成员含义如下:
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk; //导入名称表(INT)的地址RVA
} DUMMYUNIONNAME;
DWORD TimeDateStamp; //时间戳多数情况可忽略.如果是0xFFFFFFFF为绑定导入
DWORD ForwarderChain; //链表的前一个结构
DWORD Name; //导入DLL文件名的地址RVA
DWORD FirstThunk; //导入地址表(IAT)的地址RVA
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
//成员OriginalFirstThunk与FirstThunk都指向此结构:
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;
//如果结构IMAGE_THUNK_DATA32成员最高有效位(MSB)为1时低31位为导出序号.否则指向此结构.
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; //导出序号(有些编译器不会填充此值)
CHAR Name[1]; //该值长度不准确,以0结尾的字符串.导出函数名.
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
定位导入表:
IMAGE_OPTIONAL_HEADER -> DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT(1)].VirtualAddress + ImageBase.(如果在文件中解析只需将RVA转换为FOA加上当前文件首地址即可定位导入表).
通过WinHex工具查看PE文件默认属性
上述解析得知:
内存对齐 = 1000h
文件对齐 = 1000h
导出表RVA = 0x00022A10(对齐相同FOA为0x00022A10h)(RVA与FOA转换讲解)
前两个导入表解析如下:
成员含义:
Name该成员为DLL名指针,指向以"\0"结尾的ANIS字符串(RVA).
第一个导入表指向00022FFCh.对应字符串数据为: MFC42.DLL
第二个导入表指向00023096h.对应字符串数据为: MSVCRT.dll
OriginalFirstThunk该成员为导入名称表(INT import name table)RVA,指向IMAGE_THUNK_DATA结构体数组,通过结构体成员是否为0来判断结束标记.该成员解析存在两种状态,当最高有效位为1时低31位为函数导出序号,否则为RVA指向结构体IMAGE_IMPORT_BY_NAME.
第一个导入表指向00022AC8h.对应数据为:
指向数据MSB均为1(低31位为导出函数序号)
第二个导入表指向00022F28h.对应数据为:
指向数据MSB均为0(该值为RVA指向IMAGE_IMPORT_BY_NAME结构体)
FirstThunk该成员为导入地址表(IAT import address table)RVA,在程序运行前同OriginalFirstThunk结构一样通过判断MSB来决定存储内容.程序运行时,还是指向IMAGE_THUNK_DATA结构体数组,但是内容为对应函数地址.
第一个导入表指向0001D040h.对应数据为:
文件运行前同INT表指向数据一样
第二个导入表指向0001D4A0h.对应数据为:
文件运行前同INT表指向数据一样
通过文件解析可以获得如下数据:
第一个导入表:
第二个导入表:
导入表定位以及解析(手动ImageBuffer)
运行程序,通过WinHex解析内存中对应INT 和 IAT数据
文件ImageBase为400000h,只需将之前解析值(RVA)+ImageBase即可.
第一个导入表:
OriginalFirstThunk
第一个导入表指向00022AC8h + ImageBase(400000h).对应数据为:
与文件中数据一致,并未发生任何改变.
FirstThunk
第一个导入表指向0001D040h+ ImageBase(400000h).对应数据为:
通过OD / DBG 查看对应数据
运行时IAT表中存储的值61E3FFF0h为内存中真正函数地址
MFC42.DLL模块
通过内存解析可以获得如下数据:
如果要实现内存加载,无模块等都需要用到导入表.解析导入表获得导入模块,通过函数名或者序号去对应模块导出表中查找对应函数地址并填充IAT表.
导出表详解(可自行实现API GetProcAddress)
代码定位导入表并打印其数据
读取文件代码
PVOID FileToMem(IN PCHAR szFilePath, OUT LPDWORD dwFileSize)
{
//打开文件
FILE* pFile = fopen(szFilePath, "rb");
if (!pFile)
{
printf("FileToMem fopen Fail \r\n");
return NULL;
}
//获取文件长度
fseek(pFile, 0, SEEK_END); //SEEK_END文件结尾
DWORD Size = ftell(pFile);
fseek(pFile, 0, SEEK_SET); //SEEK_SET文件开头
//申请存储文件数据缓冲区
PCHAR pFileBuffer = (PCHAR)malloc(Size);
if (!pFileBuffer)
{
printf("FileToMem malloc Fail \r\n");
fclose(pFile);
return NULL;
}
//读取文件数据
fread(pFileBuffer, Size, 1, pFile);
//判断是否为可执行文件
if (*(PSHORT)pFileBuffer != IMAGE_DOS_SIGNATURE)
{
printf("Error: MZ \r\n");
fclose(pFile);
free(pFileBuffer);
return NULL;
}
if (*(PDWORD)(pFileBuffer + *(PDWORD)(pFileBuffer + 0x3C)) != IMAGE_NT_SIGNATURE)
{
printf("Error: PE \r\n");
fclose(pFile);
free(pFileBuffer);
return NULL;
}
if (dwFileSize)
{
*dwFileSize = Size;
}
fclose(pFile);
return pFileBuffer;
}
输出文件代码
VOID MemToFile(IN PCHAR szFilePath, IN PVOID pFileBuffer, IN DWORD dwFileSize)
{
//打开文件
FILE* pFile = fopen(szFilePath, "wb");
if (!pFile)
{
printf("MemToFile fopen Fail \r\n");
return;
}
//输出文件
fwrite(pFileBuffer, dwFileSize, 1, pFile);
fclose(pFile);
}
定位并输出导入表数据
VOID PrintImportInfo()
{
//读取文件二进制数据
DWORD dwFileSize = 0;
PCHAR pFileBuffer = FileToMem(FILE_PATH_IN, &dwFileSize);
if (!pFileBuffer)
{
return;
}
//定位结构
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pFileBuffer;
PIMAGE_NT_HEADERS pNth = (PIMAGE_NT_HEADERS)(pFileBuffer + pDos->e_lfanew);
PIMAGE_FILE_HEADER pFil = (PIMAGE_FILE_HEADER)((PUCHAR)pNth + 4);
PIMAGE_OPTIONAL_HEADER32 pOpo = (PIMAGE_OPTIONAL_HEADER32)((PUCHAR)pFil + IMAGE_SIZEOF_FILE_HEADER);
//判断该PE文件是否有重定位表
if (!pOpo->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress)
{
printf("该PE文件不存在导入表 \r\n");
return;
}
PIMAGE_IMPORT_DESCRIPTOR pImp = (PIMAGE_IMPORT_DESCRIPTOR)(pFileBuffer + RvaToFoa(pFileBuffer, pOpo->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress));
//循环遍历
while (pImp->OriginalFirstThunk && pImp->FirstThunk)
{
printf("-----------------------------------------------------------\r\n");
PUCHAR pDllName = pFileBuffer + RvaToFoa(pFileBuffer, pImp->Name);
printf("ModuleName -> [%s] \r\n", pDllName);
//遍历INT
printf("*****************INT*****************\r\n");
PIMAGE_THUNK_DATA pInt = (PIMAGE_THUNK_DATA)(pFileBuffer + RvaToFoa(pFileBuffer, pImp->OriginalFirstThunk));
do
{
//判断最高位
if (*(PDWORD)pInt & IMAGE_ORDINAL_FLAG32)//IMAGE_ORDINAL_FLAG32 0x80000000
{
printf("ExportByOriginal -> [0x%08x] \r\n", *(PDWORD)pInt & 0x7FFFFFFF);
}
else
{
PIMAGE_IMPORT_BY_NAME pTemp = (PIMAGE_IMPORT_BY_NAME)(pFileBuffer + RvaToFoa(pFileBuffer, *(PDWORD)pInt));
printf("ExportByName -> HINT[0x%04x] NAME[%s] \r\n", pTemp->Hint, pTemp->Name);
}
} while (*(PDWORD)(++pInt));
//遍历IAT
printf("*****************IAT*****************\r\n");
PIMAGE_THUNK_DATA pIat = (PIMAGE_THUNK_DATA)(pFileBuffer + RvaToFoa(pFileBuffer, pImp->FirstThunk));
//判断是否为绑定导入
if (pImp->TimeDateStamp == 0)
{
do
{
//判断最高位
if (*(PDWORD)pIat & IMAGE_ORDINAL_FLAG32)//IMAGE_ORDINAL_FLAG32 0x80000000
{
printf("ExportByOriginal -> [0x%08x] \r\n", *(PDWORD)pIat & 0x7FFFFFFF);
}
else
{
PIMAGE_IMPORT_BY_NAME pTemp = (PIMAGE_IMPORT_BY_NAME)(pFileBuffer + RvaToFoa(pFileBuffer, *(PDWORD)pIat));
printf("ExportByName -> HINT[0x%04x] NAME[%s] \r\n", pTemp->Hint, pTemp->Name);
}
} while (*(PDWORD)(++pIat));
}
else
{
do
{
printf("FunAddr -> [0x%08x] \r\n", *(PDWORD)pIat);
} while (*(PDWORD)(++pIat));
}
//指向下一个导入表结构
pImp++;
}
}