Windows Ring3层注入——修改PE输入表(导入表)注入(一)

谁说我不能喝 提交于 2020-01-17 03:59:29

PE文件简单介绍

PE(Portable Execute)文件是Windows下可执行文件的总称,常见的有DLL,EXE,OCX,SYS等,事实上,一个文件是否是PE文件与其扩展名无关,PE文件可以是任何扩展名
PE文件的结构一般来说如下图所示:从起始位置开始依次是DOS头,PE文件头,块表(节表)以及具体的块(节)

在这里插入图片描述

PE文件相关名词解释

  • 虚拟空间:PE文件被系统加载器映射到内存中,每个程序都有自己的虚拟空间。
  • 虚拟地址(VA):虚拟空间的内存地址
  • 相对虚拟地址(RVA):内存中相对于PE文件载入地址的偏移相对地址(也称偏移量)。
  • 模块(Module):PE文件映射到内存中的版本被称为模块(Module)。(GetModuleHandle)
  • 模块句柄:映射文件的起始地址
  • 基地址(ImageBase):初始内存起始地址,跟模块句柄一样(Windows CE不成立)。
  • 区块间隙:PE文件头中,FileAlignment定义了磁盘区块对齐值。每一个区块从对齐值的倍数的偏移位置开始,而区块的实际代码或数据的大小不一定刚好是这么多,所以不足的地方一般以00h来填充,就会出现区块间隙。(PE文件的典型对齐值为200h)。
  • 内存对齐:PE文件头中,SectionAlignment定义了内存中区块的对齐值。当PE文件被映射到内存中,区块总是从一个页边界处开始。(x86内存页4KB(1000h)排列,x64内存页8KB(2000h)排列)。

File Offset=RVA-△k
File Offset=VA-ImageBase-△k

PE文件磁盘与内存映像结构图

在这里插入图片描述
更多PE文件的介绍可以参照《加密与解密》第11章节。

输入表介绍

输入是可执行程序(EXE)被加载到地址空间后,使用来自于其他 Dll 的代码或数据的动作。
输入表Windows PE文件中的一组数据结构,输入表RVA在上图PE文件结构的PE文件头中的数据目录表中。
输入表就相当于 EXE文件DLL文件 沟通的钥匙,形象的可以比喻成两个城市之间交流的高速公路,所有的导入函数信息都会写入输入表中,在 PE 文件映射到内存后,Windows 将相应的 DLL文件装入,EXE 文件通过**“输入表”找到相应的 DLL 中的导入函数**,从而完成程序的正常运行,这一动态连接的过程都是由**“输入表”**参与的。

输入表结构

输入表是以一个IMAGE_IMPORT_DESCRIPTOR(IID) 数组 开始的,每一个被PE文件隐式的 链接进来的DLL 都有一个 IIDIID数组的最后一个单元用NULL表示。

typedef struct _IMAGE_IMPORT_DESCRIPTOR 
{  
    _ANONYMOUS_UNION union {            
        DWORD Characteristics;           //
        DWORD OriginalFirstThunk;        //指向输入名称表(INT)的RVA(相对虚拟地址),INT是一个IMAGE_THUNCK_DATA结构的数组,数组中的每个元素指向一个IMAGE_IMPORT_BY_NAME结构,INT以元素0结束。
    } DUMMYUNIONNAME;  
    DWORD TimeDateStamp;                 //   时间戳,可以忽略
    DWORD ForwarderChain;                //   如果没有向前引用(Forwarders)的话就是-1
    DWORD Name;                          //   被导入DLL的名字指针,是一个RVA
    DWORD FirstThunk;                    //   指向输入地址表(IAT)的RVA。IAT是一个IMAGE_THUNK_DATA结构的数组
} IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR; 
  • IMAGE_THUNK_DATA 结构:
typedef struct _IMAGE_THUNK_DATA32 
{  
    union 
    {  
        DWORD ForwarderString;  //指向一个转向者字符串的RVA;
        DWORD Function;         //被输入的函数的内存地址
        DWORD Ordinal;          //被输入的API的序数值
        DWORD AddressOfData;    //指向IMAGE_IMPORT_BY_NAME
    } u1;  
} IMAGE_THUNK_DATA32,*PIMAGE_THUNK_DATA32;
  • MAGE_IMPORT_BY_NAME结构:
struct _IMAGE_IMPORT_BY_NAME {
    WORD    Hint;             //   指出函数在所在的dll的输出表中的序号 
    BYTE    Name[1];          //   指出要输入的函数的函数名
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
  • 要两个 IMAGE_THUNK_DATA 数组的原因:
    当程序加载时,IAT 会被PE加载器重写,PE加载器先搜索 INT ,PE加载器迭代搜索 INT数组 中的每个指针,找出 INT 所指向的 IMAGE_IMPORT_BY_NAME 结构中的函数在内存中的真正的地址,并把它替代原来 IAT 中的值。当完成后, INT 就没有用了,程序只需要 IAT 就可以正常运行了。看下面图:

这个是 可执行程序在磁盘中 的时候:
在这里插入图片描述
这个是 当程序被加载 的时候:
在这里插入图片描述

输入表注入原理

首先让我们换一个角度观察一下模块的导入关系。假设有一个可执行程序A.exe,运行之后,其进程空间模块有hid.dll、msvcrt.dll、advapi32.dll,技术模块有ntdll.dll、kernel32.dll,代表其他模块的是other.dll,他们的导入关系如图所示:
在这里插入图片描述

  • 因为一个进程空间对于同一个dll模块只需要加载一个,所以简化一下模块的导入依赖关系可以看到如图所示。

简化后的模块导入依赖关系图看上去比较清晰和简洁,很像一棵树,树枝代表上面输入表逻辑结构中的IMAGE_IMPORT_DESCRIPTIOR结构。Windows的PE加载器就是根据这种依赖关系来加载模块的。
注入是为了让自己的模块可以加载到目标进程空间中,所以根据上述原理,我们可不可以在上面的导入关系中添加一个是我们自己模块的节点,就可以完成注入。
一般情况,输入表的加载都是通过修改进程输入依赖树上某个模块的输入表来实现的(树上增加节点),这个修改动作是在进程加载该模块之前完成的(即修改模块文件的输入表)。等到待注入模块被加载到进程控件后,就可以将被修改的模块文件改回来,防止文件修改操作被发现。

输入表加载的原理就是在原有的IMAGE_IMPORT_DESCRIPTION(IDD)数组中增加一条IID记录,所以我们必须考虑原来存放IID的位置是否有空间容纳这个新的IID。为了避免这种意外发生,有两种方法可以采用:
一是在
所有的节中寻找一块能容纳心IID数组的文件空隙

二是新增一个足够大的节来专门存放新的IID数组

在这里插入图片描述

PE输入表注入的两种方法

静态修改PE文件法:

  • 1.备份原IID结构
  • 2.在原IID区域构造新的OriginalFirstThunk、Name和FirstThunk结构
  • 3.填充新输入表项的IID结构
  • 4.修正PE文件头的信息

进程创建期修改PE输入表法:

  • 1.以挂起方式创建目标进程
  • 2.获取目标进程中的PE结构信息
  • 3.获取原IID大小,增加一项,搜索可用的节空隙
  • 4.构造心的IID及其相关的OriginalFirstThunk、Name和FirstThunk结构
  • 5.修正PE映像头
  • 6.更新目标进程的内存。
  • 7.继续运行主线程

输入表注入核心代码示例

根据上述原理介绍,对代码的流程可以如此梳理:
1.写一个我们自己的输入表IID结构
1.新增一个节(块section)来保存旧的输入表IID结构数组和我们新加的输入表IID结构
2.因为新加了节,我们就需要增加一个节表指向我们新加的节
3.因为新加了节和节表就需要更改PE文件头的信息

//新增输入表
BOOL AddNewImportDescriptor(const char * szPEFilePath,char * szInjectDllName, char *szImportFuncName)
//新增节(块)
BOOL AddNewSection(LPCTSTR lpStrModulePath, DWORD dwNewSectionSize)

BOOL AddNewImportDescriptor(const char * szPEFilePath,char * szInjectDllName, char *szImportFuncName)
{
	BOOL bSuccess = FALSE;
	LPVOID lpMemModule = NULL;
	LPBYTE lpData = NULL;
	DWORD  dwNewSecFileSize, dwNewSecMemSize;
	HANDLE hFile = INVALID_HANDLE_VALUE, hFileMapping = INVALID_HANDLE_VALUE;
	PIMAGE_NT_HEADERS pNtHeader = NULL;
	PIMAGE_IMPORT_DESCRIPTOR pstImportTable = NULL;
	PIMAGE_SECTION_HEADER    pstSectionHeader = NULL;
	__try
	{
			//pe文件映射到内存
		hFile = CreateFile(
			     szPEFilePath,
				 GENERIC_READ | GENERIC_WRITE,
				 FILE_SHARE_READ | FILE_SHARE_WRITE,
				 NULL,
				 OPEN_EXISTING,
				 FILE_ATTRIBUTE_NORMAL,
				 NULL
				 );
		if ( INVALID_HANDLE_VALUE == hFile )
		{
			OutputDebugString("[-] AddNewImportDescriptor CreateFile fail!\n");
			goto _EXIT_;
		}

		DWORD dwFileSize = GetFileSize(hFile, NULL);
		hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READWRITE/* | SEC_IMAGE*/, 0, dwFileSize, "WINSUN_MAPPING_FILE");
		if ( NULL == hFileMapping )
		{
			OutputDebugString("[-] AddNewImportDescriptor CreateFileMapping fail!\n");
			goto _EXIT_;
		}

		lpMemModule = MapViewOfFile(hFileMapping, FILE_MAP_ALL_ACCESS, 0, 0, dwFileSize);
		if ( NULL == lpMemModule )
		{
			OutputDebugString("[-] AddNewImportDescriptor MapViewOfFile fail!\n");
			goto _EXIT_;
		}
		lpData = (LPBYTE)lpMemModule;
		//判断是否是PE
        if (((PIMAGE_DOS_HEADER)lpData)->e_magic != IMAGE_DOS_SIGNATURE )
        {
			OutputDebugString("[-] AddNewImportDescriptor PE Header MZ error!\n");
			goto _EXIT_;
        }
		pNtHeader = (PIMAGE_NT_HEADERS)(lpData + ((PIMAGE_DOS_HEADER)(lpData))->e_lfanew);
		if ( pNtHeader->Signature != IMAGE_NT_SIGNATURE )
		{
			OutputDebugString("[-] AddNewImportDescriptor PE Header PE error!\n");
			goto _EXIT_;
		}
		pstImportTable = (PIMAGE_IMPORT_DESCRIPTOR)(lpData + RVA2Offset(pNtHeader,pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress));
		BOOL bBoundImport = FALSE;
		if (pstImportTable->Characteristics == 0 && pstImportTable->FirstThunk != 0)
		{
			bBoundImport = TRUE;
			pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].Size = 0;
			pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].VirtualAddress = 0;
		}
		
		pstSectionHeader = (PIMAGE_SECTION_HEADER)(pNtHeader+1)+pNtHeader->FileHeader.NumberOfSections-1;
		PBYTE pbNewSection = pstSectionHeader->PointerToRawData + lpData;
		int i = 0;
		while(pstImportTable->FirstThunk != 0)
		{
			memcpy(pbNewSection, pstImportTable, sizeof(IMAGE_IMPORT_DESCRIPTOR));
			pstImportTable++;
			pbNewSection += sizeof(IMAGE_IMPORT_DESCRIPTOR);
			i++;
		}
		memcpy(pbNewSection, (pbNewSection-sizeof(IMAGE_IMPORT_DESCRIPTOR)), sizeof(IMAGE_IMPORT_DESCRIPTOR));
		DWORD dwDelt = pstSectionHeader->VirtualAddress - pstSectionHeader->PointerToRawData;
		//avoid import not need table
		PIMAGE_THUNK_DATA pImgThunkData = (PIMAGE_THUNK_DATA)(pbNewSection + sizeof(IMAGE_IMPORT_DESCRIPTOR)*2);
	
		//import dll name
		PBYTE pszDllNamePosition = (PBYTE)(pImgThunkData + 2);
		memcpy(pszDllNamePosition, szInjectDllName, strlen(szInjectDllName));
		pszDllNamePosition[strlen(szInjectDllName)] = 0;

		//确定IMAGE_IMPORT_BY_NAM的位置
		PIMAGE_IMPORT_BY_NAME pImgImportByName = (PIMAGE_IMPORT_BY_NAME)(pszDllNamePosition + strlen(szInjectDllName) + 1);

		//init IMAGE_THUNK_DATA
		pImgThunkData->u1.Ordinal = dwDelt + (DWORD)pImgImportByName - (DWORD)lpData ;


		//init IMAGE_IMPORT_BY_NAME
		pImgImportByName->Hint = 1;
		memcpy(pImgImportByName->Name, szImportFuncName, strlen(szImportFuncName)); //== dwDelt + (DWORD)pszFuncNamePosition - (DWORD)lpData ;
		pImgImportByName->Name[strlen(szImportFuncName)] = 0;
		//init OriginalFirstThunk
		if (bBoundImport)
		{
			((PIMAGE_IMPORT_DESCRIPTOR)pbNewSection)->OriginalFirstThunk = 0;
		}
		else
			((PIMAGE_IMPORT_DESCRIPTOR)pbNewSection)->OriginalFirstThunk = dwDelt + (DWORD)pImgThunkData - (DWORD)lpData;
		//init FirstThunk
		((PIMAGE_IMPORT_DESCRIPTOR)pbNewSection)->FirstThunk = dwDelt + (DWORD)pImgThunkData - (DWORD)lpData;
		//init Name
		((PIMAGE_IMPORT_DESCRIPTOR)pbNewSection)->Name = dwDelt + (DWORD)pszDllNamePosition-(DWORD)lpData;

		//改变导入表指向顺序
		pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress =  pstSectionHeader->VirtualAddress; 
		pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size			 =  (i+1)*sizeof(IMAGE_IMPORT_DESCRIPTOR);
		
		
	}
	__except(EXCEPTION_EXECUTE_HANDLER)
	{
        OutputDebugString("[-] AddNewImportDescriptor  Exception!\n");
		return false;
	}

	_EXIT_:

	if ( hFile )
	{
         CloseHandle(hFile);
	}

	if ( lpMemModule)
	{
		UnmapViewOfFile(lpMemModule);
	}

	if ( hFileMapping )
	{
		CloseHandle(hFileMapping);
	}
	return true;
}


BOOL AddNewSection(LPCTSTR lpStrModulePath, DWORD dwNewSectionSize)
{
	bool   bSuccess = false;
	LPVOID lpMemModule = NULL;
	LPBYTE lpData = NULL;
	DWORD  dwNewSecFileSize, dwNewSecMemSize;
	HANDLE hFile = INVALID_HANDLE_VALUE, hFileMapping = INVALID_HANDLE_VALUE;
	PIMAGE_NT_HEADERS pNtHeader = NULL;
	PIMAGE_SECTION_HEADER pNewSection = NULL, pLastSection = NULL;

	OutputDebugString("[!] AddNewSection Enter!\n");

	//TODO:可能还涉及关闭windows文件保护
	__try
	{
		//pe文件映射到内存
		hFile = CreateFile(
			     lpStrModulePath,
				 GENERIC_READ | GENERIC_WRITE,
				 FILE_SHARE_READ | FILE_SHARE_WRITE,
				 NULL,
				 OPEN_EXISTING,
				 FILE_ATTRIBUTE_NORMAL,
				 NULL
				 );
		if ( INVALID_HANDLE_VALUE == hFile )
		{
			OutputDebugString("[-] AddNewSection CreateFile fail!\n");
			goto _EXIT_;
		}

		DWORD dwFileSize = GetFileSize(hFile, NULL);
		hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READWRITE/* | SEC_IMAGE*/, 0, dwFileSize, "WINSUN_MAPPING_FILE");
		if ( NULL == hFileMapping )
		{
			
			OutputDebugString("[-] AddNewSection CreateFileMapping fail!\n");
			goto _EXIT_;

		}

		lpMemModule = MapViewOfFile(hFileMapping, FILE_MAP_ALL_ACCESS, 0, 0, dwFileSize);
		if ( NULL == lpMemModule )
		{
			OutputDebugString("[-] AddNewSection MapViewOfFile fail!\n");
			goto _EXIT_;
		}

		lpData = (LPBYTE)lpMemModule;
		//判断是否是PE文件
        if (((PIMAGE_DOS_HEADER)lpData)->e_magic != IMAGE_DOS_SIGNATURE )
        {
			OutputDebugString("[-] AddNewSection PE Header MZ error!\n");
			goto _EXIT_;
        }

		pNtHeader = (PIMAGE_NT_HEADERS)(lpData + ((PIMAGE_DOS_HEADER)(lpData))->e_lfanew);
		if ( pNtHeader->Signature != IMAGE_NT_SIGNATURE )
		{
			OutputDebugString("[-] AddNewSection PE Header PE error!\n");
			goto _EXIT_;
		}
		
		//判断是否可以增加一个新节
		if ( ((pNtHeader->FileHeader.NumberOfSections + 1) * sizeof(IMAGE_SECTION_HEADER)) > (pNtHeader->OptionalHeader.SizeOfHeaders) )
		{
			OutputDebugString("[-] AddNewSection cannot add a new section!\n");
			goto _EXIT_;
		}

        pNewSection  = (PIMAGE_SECTION_HEADER)(pNtHeader+1) + pNtHeader->FileHeader.NumberOfSections;
		pLastSection = pNewSection - 1;

		
		DWORD rsize,vsize,roffset,voffset;
		//对齐偏移和RVA
		rsize=PEAlign(dwNewSectionSize,
				pNtHeader->OptionalHeader.FileAlignment);

		roffset=PEAlign(pLastSection->PointerToRawData+pLastSection->SizeOfRawData,
				pNtHeader->OptionalHeader.FileAlignment);
		
		vsize=PEAlign(dwNewSectionSize,
				pNtHeader->OptionalHeader.SectionAlignment);

		voffset=PEAlign(pLastSection->VirtualAddress+pLastSection->Misc.VirtualSize,
				pNtHeader->OptionalHeader.SectionAlignment);

		//填充新节表
		memcpy(pNewSection->Name, "WINSUN", strlen("WINSUN"));
		pNewSection->VirtualAddress = voffset;
		pNewSection->PointerToRawData = roffset;
		pNewSection->Misc.VirtualSize = vsize;
		pNewSection->SizeOfRawData = rsize;
		pNewSection->Characteristics = IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE;

		//修改IMAGE_NT_HEADERS,增加新节表
		pNtHeader->FileHeader.NumberOfSections++;
		pNtHeader->OptionalHeader.SizeOfImage += vsize;
		pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].Size = 0;
		pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].VirtualAddress = 0;

		//增加新节到文件尾部
		DWORD dwWriteBytes;
		SetFilePointer(hFile,0,0,FILE_END);
		PBYTE pbNewSectionContent = new BYTE[rsize];
		ZeroMemory(pbNewSectionContent, rsize);
		bSuccess = WriteFile(hFile, pbNewSectionContent, rsize, &dwWriteBytes, NULL);
		if (!bSuccess)
		{
			MessageBox(NULL,"新增节失败","error",MB_OK);
			goto _EXIT_;
		}
		
	}
	__except(EXCEPTION_EXECUTE_HANDLER)
	{
        OutputDebugString("[-] AddImportTableItem  Exception!\n");
		return false;
	}
  OutputDebugString("[!] AddNewSection Exit!\n");
	bSuccess = true;
_EXIT_:

	if ( hFile )
	{
         CloseHandle(hFile);
	}

	if ( lpMemModule)
	{
		UnmapViewOfFile(lpMemModule);
	}

	if ( hFileMapping )
	{
		CloseHandle(hFileMapping);
	}
	return true;
}

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!