PE文件:(3)导入表

导入表的概念

  可执行文件在进行加载时,需要导入一些动态库,即Dll文件。通过导入这些动态库,我们可以使用文件中提供的函数和功能,来为我们的可执行文件服务。这些Dll中的函数就称为导入函数。为了记录需要的动态库和导入函数,可执行文件在可选头的数据目录中维护了一张表,这张表记录了文件需要的所有动态库以及导入函数的信息,方便Windows加载器在加载PE文件时使用。这张表就是所谓的导入表,它被存储在数据目录的第二项。
  可执行文件在使用动态库中的函数和代码时,一般有两种方式。一种是隐式链接,这完全通过windows加载器来完成;另一种方式是显式链接,即我们可以根据需要手动载入一个动态库文件,并使用其中的函数,这一过程需要使用LoadLibrary和GetProcAddress。

导入表的结构

  导入表的结构定义如下:

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
  _ANONYMOUS_UNION union {
    ULONG Characteristics;
    ULONG OriginalFirstThunk;
  } DUMMYUNIONNAME;
  ULONG TimeDateStamp;
  ULONG ForwarderChain;
  ULONG Name;			//动态库的名字
  ULONG FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;

  看到这个数据结构的时候,我们可能会一脸懵- - ! 这OriginalFirstThunk,FirstThunk都是什么啊?别急,下面我用一张图来为大家解释一下:
在这里插入图片描述
  前面的文章中我们讲到过,PE文件分为未加载和加载后两种状态。在未加载时,我们的PE文件还不知道自己即将被加载到进程空间的何处(Dll文件会出现地址重定位的问题),因此,动态库文件在真正被加载前,是无法确定其中的函数地址的。所以,如图所示,OriginalFirstThunk,FirstThunk两成员在PE文件被加载前,都指向一个数组,这个数组中的每一成员都是一个地址,每个地址指向一个字符串,这些字符串就是每个导入函数的名称。换言之,在PE文件被加载前,OriginalFirstThunk,FirstThunk指向两个数组的内容是完全相同的,都是保存指向导入函数名称的地址的一个数组。
在这里插入图片描述

  那么,在Dll文件被加载到进程空间后,我们的可执行文件需要使用导入函数,这个时候,就需要确认每个导入函数的地址了。Windows加载器会根据OriginalFirstThunk中每个函数的名称,查找该函数的真正地址,也就是保存实现函数功能的代码地址,并将这些函数的真正地址填写到FirstThunk指向的数组中,这样,我们的可执行文件就可以通过函数地址调用具体的函数了。这一过程也被称为导入表的修正。

  还有一个成员Name,它指向导入的动态库的名称,如"kernel32.dll"、"User32.dll"等。可执行文件中通常需要不止一个动态库文件,所以导入表中会存储若干个IMAGE_IMPORT_DESCRIPTOR结构,每个结构指向一个具体的动态库文件,我们可以通过查看结构中的Name来确定每个结构对应的Dll文件名。

  使用LordPE工具查看一个具体的PE文件,查看导入表,可以查看到QQMusic.exe需要加载的动态库和导入函数。
在这里插入图片描述

手动加载导入表

  前面我们说到,Dll文件有两种链接方式。显式链接需要我们使用LoadLibrary和GetProcAddress函数,这两个函数可以帮助我们将一个存在的Dll文件加载到进程空间内并获得导入函数的地址。这种显式链接方式也被称为注入,因为如果我们在想要攻击的目标进程中加载了我们自定义的Dll文件,就可以让目标进程调用我们Dll文件中的函数,从而实现修改进程中数据和代码的目的。
  显式链接还有一种更复杂的方式,那就是手动加载PE文件。大概步骤为:我们将一个PE文件的数据读入到目标进程的地址空间内,然后按内存对齐的方式进行存储,同时按照Windows加载器的要求对导入表、重定位表进行修正。手动修正导入表的过程如下:
  ①定位到PE文件中数据目录的导入表项
  ②查看每一个IMAGE_IMPORT_DIRECTORY结构中的OriginalFirstThunk数组,根据数组中存储的函数名,调用GetProcAddress函数获得函数的地址,并手动填写到FirstThunk指向的数组中。
  ③如果是使用索引导入的方式,那么需要到指定的Dll文件中,查找该索引在Dll导出表对应的函数地址,然后填写到FirstThunk数组中(关于导出表的内容后续会讲到)。

参考代码

	// 导入表项
	ImageDataDirectory = (ULONG_PTR)&((PIMAGE_NT_HEADERS)ImageNtHeaders)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];

	// 导入表起始地址
	ImageImportDescriptor = (VirtualAddress + ((PIMAGE_DATA_DIRECTORY)ImageDataDirectory)->VirtualAddress);

	// 从起始地址开始,加载每一个Dll和Dll中的导入函数
	while (((PIMAGE_IMPORT_DESCRIPTOR)ImageImportDescriptor)->Name)
	{
		// Dll基地址
		ModuleBase = (ULONG_PTR)LoadLibraryA(
			(LPCSTR)(VirtualAddress + ((PIMAGE_IMPORT_DESCRIPTOR)ImageImportDescriptor)->Name));

		// 函数名数组的基地址
		OriginalFirstThunk = (VirtualAddress + ((PIMAGE_IMPORT_DESCRIPTOR)ImageImportDescriptor)->OriginalFirstThunk);

		// 需要修正的导入函数地址表
		FirstThunk = (VirtualAddress + ((PIMAGE_IMPORT_DESCRIPTOR)ImageImportDescriptor)->FirstThunk);

	
		while (DEREFERENCE(FirstThunk))
		{
			// 索引导入
			if (OriginalFirstThunk && ((PIMAGE_THUNK_DATA)OriginalFirstThunk)->u1.Ordinal & IMAGE_ORDINAL_FLAG)
			{
				// 按索引号搜索Dll中对应的导出表中的导出函数
				ImageNtHeaders = ModuleBase + ((PIMAGE_DOS_HEADER)ModuleBase)->e_lfanew;
				// 需要修正某个Dll的导入表,查看该Dll的导出函数
				ImageDataDirectory = (ULONG_PTR)&((PIMAGE_NT_HEADERS)ImageNtHeaders)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
				
				ImageExportDirectory = (ModuleBase + ((PIMAGE_DATA_DIRECTORY)ImageDataDirectory)->VirtualAddress);
				
				AddressOfFunctions = (ModuleBase + ((PIMAGE_EXPORT_DIRECTORY)ImageExportDirectory)->AddressOfFunctions);

				//导入函数地址 = (导入表索引 - Base) * 4 + 起始地址
				AddressOfFunctions += 
					((IMAGE_ORDINAL(((PIMAGE_THUNK_DATA)OriginalFirstThunk)->u1.Ordinal) -
					((PIMAGE_EXPORT_DIRECTORY)ImageExportDirectory)->Base) * sizeof(DWORD));

				//将FirstThunk中的内容修改为真正函数地址
				DEREFERENCE(FirstThunk) = (ModuleBase + DEREFERENCE_32(AddressOfFunctions));
			}
			else
			{
				//修正名称导入的函数地址
				ImageImportByName = (VirtualAddress + DEREFERENCE(OriginalFirstThunk));
				//将函数名导入的函数地址填入到IAT中
				DEREFERENCE(FirstThunk) = (ULONG_PTR)GetProcAddress((HMODULE)ModuleBase, 
					(LPCSTR)((PIMAGE_IMPORT_BY_NAME)ImageImportByName)->Name);
			}
			
			FirstThunk += sizeof(ULONG_PTR);
			if (OriginalFirstThunk)
				OriginalFirstThunk += sizeof(ULONG_PTR);
		}
		// 查找下一个导入表项
		ImageImportDescriptor += sizeof(IMAGE_IMPORT_DESCRIPTOR);
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值