12.PE文件之导入表(IMAGE_IMPORT_DESCRIPTOR)

目录

导入表定位以及解析(手动FileBuffer)

导入表定位以及解析(手动ImageBuffer)

代码定位导入表并打印其数据


导入表是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++;
	}

}
  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值