首页
社区
课程
招聘
[原创]Win PE系列之重定位表解析与模块注入技术
2021-10-11 19:22 6668

[原创]Win PE系列之重定位表解析与模块注入技术

2021-10-11 19:22
6668

一.重定位表的作用

考虑下面这段代码

	char szHello[] = { "Hello 1900" };
00111038  mov         eax,dword ptr ds:[001881C0h]  
0011103D  mov         dword ptr [ebp-10h],eax  
00111040  mov         ecx,dword ptr ds:[001881C4h]  
00111046  mov         dword ptr [ebp-0Ch],ecx  
00111049  mov         dx,word ptr ds:[001881C8h]  
00111050  mov         word ptr [ebp-8],dx  
00111054  mov         al,byte ptr ds:[001881CAh]  
00111059  mov         byte ptr [ebp-6],al

为了给数组szHello赋值,程序从0x01881C0地址处开始依次取出相应的数据赋给数组szHello,而这个地址中,保存的就是要赋值给数组的字符串

但问题是,由于程序装载到内存中的基地址,也就是ImageBase是不一样的(dll文件和开了ASLR的exe文件),这就导致了这个字符串的地址会发生改变。重新编译上面的程序,会得到如下的结果

	char szHello[] = { "Hello 1900" };
003A1038  mov         eax,dword ptr ds:[004181C0h]  
003A103D  mov         dword ptr [ebp-10h],eax  
003A1040  mov         ecx,dword ptr ds:[004181C4h]  
003A1046  mov         dword ptr [ebp-0Ch],ecx  
003A1049  mov         dx,word ptr ds:[004181C8h]  
003A1050  mov         word ptr [ebp-8],dx  
003A1054  mov         al,byte ptr ds:[004181CAh]  
003A1059  mov         byte ptr [ebp-6],al

可以看到,此时的字符串的地址变成了0x004181C0。所以为了让程序运行的时候找到正确的字符串的地址,这就需要一块内存来保存所有引用了这个字符串地址的地址。当程序加载的时候,对这个地址进行重定位来找到正确的字符串的地址,这块内存就是重定位表。

二.重定位表的解析

重定位表位于数据目录的第6项,在文档中的定义如下

#define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table

所指的地址指向了IMAGE_BASE_RELOCATION结构体,结构体的定义如下

typedef struct _IMAGE_BASE_RELOCATION {
    DWORD   VirtualAddress;
    DWORD   SizeOfBlock;
//  WORD    TypeOffset[1];
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;

这些需要重定位的地址都是按块保存的,每一个块都是一个IMAGE_BASE_RELOCATION后面跟上若干两字节的偏移地址。当找个一个块,它的VirtualAdreess和SizeOfBlock都是0的时候,说明已经没有重定位块要解析了。

而偏移地址有多少,就需要根据IMAGE_BASE_RELOCATION中的SizeOfBlock来决定,这个字段说明的是整个重定位块的大小,减去VirtualAddress和SizeOfBlock的占的8字节,在除以2就可以得到偏移地址的个数。

每个偏移地址的高4位如果是3的话,那么它的低12位就代表了偏移地址,这个偏移地址加上这个块的IMAGE_BASE_RELOCATION中的VirtualAddress就得到了需要进行重定位的地址。

所以可以用下面的代码来解析重定位表

void PEParse::PrintRelocationTable()
{
	PIMAGE_BASE_RELOCATION pRelocationTable = this->pRelocationTable;
	DWORD dwSizeOfBlock = 0, i = 0, dwVirtualAddress = 0, dwNum = 0;
	PWORD pAddr = NULL;

	if (pRelocationTable == NULL)
	{
		printf("The file does not have relocation table.\n");
		goto exit;
	}

	printf("==================The relocation table information===================\n");
	while (pRelocationTable->VirtualAddress != 0 || pRelocationTable->SizeOfBlock != 0)
	{
		dwVirtualAddress = pRelocationTable->VirtualAddress;
		dwSizeOfBlock = pRelocationTable->SizeOfBlock;
		dwNum = (dwSizeOfBlock - 2 * sizeof(DWORD)) / 2;
		pAddr = (PWORD)((DWORD)pRelocationTable + 8);
		for (i = 0; i < dwNum; i++)
		{
			if (pAddr[i] & 0x3000)
			{
				printf("The address of need to be relocation: 0x%X\n", this->RVAToFOA(dwVirtualAddress + (pAddr[i] & 0x0FFF)));
			}
		}
		pRelocationTable = (PIMAGE_BASE_RELOCATION)((DWORD)pRelocationTable + dwSizeOfBlock);
	}
	printf("==================The relocation table information===================\n");
exit:;
}

部分输出结果如下


三.模块注入

实现模块注入的相应过程如下

  1. 将要注入的模块在内存中的PE状态读入到内存中

  2. 将被注入的程序启动,并在它的进程空间中申请可以容纳注入的模块大小的空间

  3. 根据申请到的地址,去重定位读入到内存中的注入的模块的重定位表

  4. 将重定位以后的内存中的内容写入到申请到的地址

  5. 通过创建远程线程的方式启动要运行的模块

  6. 运行的模块中有修复IAT表的操作

其中的第三步,修复重定位的操作是,获取要重定位的数据的地址。计算第二步申请到的地址与注入的模块的基地址相减得到的结果与重定位数据进行相加。

而第6步中的修复IAT表,就是遍历INT和IAT的同时,使用LoadLibrary和GetProcAddress来获得正确的函数地址,在填入IAT表中。

具体代码如下

#include <cstdio>
#include <Windows.h>

#define FILE_NAME "demo.exe"	//要启动的被注入的进程名

void ShowError(char *msg);	//输出错误信息
void *GetModuleBuffer();	//得到本程序的PE文件在内存中的数据
void *GetMemoryFromTarget();	//启动要注入的进程并申请足够的内存空间
void RepairRelocation(void *pOrgAddr, DWORD dwSize);	//修复要注入的模块的重定位表
void Execute(void *pBaseAddr);	//注入的模块以后要执行的函数

DWORD g_dwSizeOfImage;
DWORD g_dwImageBase;	//保存本进程的这两个数据用来使用
HANDLE g_hProcess;	//被写入的进程的句柄

int main()
{
	void *pOrgAddr = NULL;
	void *pDestAddr = NULL;

	pOrgAddr = GetModuleBuffer();
	if (!pOrgAddr) goto exit;

	pDestAddr = GetMemoryFromTarget();
	if (!pDestAddr) goto exit;

	RepairRelocation(pOrgAddr, (DWORD)pDestAddr - (DWORD)GetModuleHandle(NULL));

	if (!WriteProcessMemory(g_hProcess, pDestAddr, pOrgAddr, g_dwSizeOfImage, NULL))
	{
		ShowError("WriteProcessMemory");
		goto exit;
	}

	if (!CreateRemoteThread(g_hProcess, NULL, 0, 
							(LPTHREAD_START_ROUTINE)((DWORD)pDestAddr + (DWORD)Execute - (DWORD)GetModuleHandle(NULL)),
							pDestAddr, 0, NULL))
	{
		ShowError("CreateRemoteThread");
		goto exit;
	}
exit:
	return 0;
}

void Execute(void *pBaseAddr)
{
	PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)pBaseAddr;
	PIMAGE_OPTIONAL_HEADER32 pOptionHead = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pBaseAddr + pDosHead->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER);
	PIMAGE_IMPORT_DESCRIPTOR pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pBaseAddr + pOptionHead->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
	PCHAR pDllName = NULL;
	HMODULE hModule = NULL;
	PDWORD pINT = NULL, pIAT = NULL;
	DWORD dwFunAddr = 0;	//正确的函数地址
	PIMAGE_IMPORT_BY_NAME pFunName = NULL;

	while (pImportTable->FirstThunk != 0 || pImportTable->OriginalFirstThunk != 0)
	{
		pDllName = (PCHAR)pBaseAddr + pImportTable->Name;
		hModule = LoadLibrary(pDllName);

		if (!hModule)
		{
			return;
		}

		pINT = (PDWORD)((DWORD)pBaseAddr + pImportTable->OriginalFirstThunk);
		pIAT = (PDWORD)((DWORD)pBaseAddr + pImportTable->FirstThunk);

		while (*pINT)
		{
			if (IMAGE_SNAP_BY_ORDINAL32(*pINT))	//以序号导入的获取函数地址
			{
				dwFunAddr = (DWORD)GetProcAddress(hModule, MAKEINTRESOURCE(*pINT & ~IMAGE_ORDINAL_FLAG32));
			}
			else
			{
				pFunName = (PIMAGE_IMPORT_BY_NAME)((DWORD)pBaseAddr + *pINT);
				dwFunAddr = (DWORD)GetProcAddress(hModule, (char *)pFunName->Name);		//以名字的方式获取函数地址
			}

			*pIAT = dwFunAddr; //修正IAT的地址
			pINT++;
			pIAT++;
		}

		pImportTable++;
	}

	MessageBox(NULL, TEXT("Success Inject"), TEXT("code by 1900~"), MB_OK);
}

void RepairRelocation(void *pOrgAddr, DWORD dwSize)
{
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pOrgAddr;
	PIMAGE_OPTIONAL_HEADER pOptionHeader = (PIMAGE_OPTIONAL_HEADER)((DWORD)pOrgAddr + pDosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER);
	PIMAGE_BASE_RELOCATION pRelocation = (PIMAGE_BASE_RELOCATION)((DWORD)pOrgAddr + pOptionHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
	DWORD i = 0, dwNum = 0;	//dwNum记录每一个块要修正多少数据
	PDWORD pTargetAddr = NULL;	//得到具体的地址
	PWORD pAddr = NULL;	//遍历偏移数据

	while (pRelocation->VirtualAddress != 0 || pRelocation->SizeOfBlock != 0)
	{
		dwNum = (pRelocation->SizeOfBlock - 2 * sizeof(DWORD)) / 2;
		pAddr = (PWORD)((DWORD)pRelocation + 2 * sizeof(DWORD));
		for (i = 0; i < dwNum; i++)
		{
			if (pAddr[i] & 0x3000)
			{
				pTargetAddr = (PDWORD)((DWORD)pOrgAddr + pRelocation->VirtualAddress + (pAddr[i] & ~0x3000));
				*pTargetAddr += dwSize;
			}
		}
		pRelocation = (PIMAGE_BASE_RELOCATION)((DWORD)pRelocation + pRelocation->SizeOfBlock);
	}
}

void *GetMemoryFromTarget()
{
	void *pTargetAddr = NULL;
	STARTUPINFO si = { 0 };
	PROCESS_INFORMATION pi = { 0 };

	si.cb = sizeof(si);
	if (!CreateProcess(FILE_NAME, NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
	{
		ShowError("CreateProcess");
		goto exit;
	}

	g_hProcess = pi.hProcess;
	pTargetAddr = VirtualAllocEx(g_hProcess, NULL, g_dwSizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

	if (!pTargetAddr)
	{
		ShowError("VirtualAllocEx");
		goto exit;
	}

exit:
	return pTargetAddr;
}

void *GetModuleBuffer()
{
	PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)GetModuleHandle(NULL);
	PIMAGE_OPTIONAL_HEADER32 pOptionHead = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pDosHead + pDosHead->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER);
	void *pMemBuffer = NULL;

	g_dwImageBase = pOptionHead->ImageBase;
	g_dwSizeOfImage = pOptionHead->SizeOfImage;
	pMemBuffer = VirtualAlloc(NULL, g_dwSizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

	if (!pMemBuffer)
	{
		ShowError("VirtualAlloc");
		goto exit;
	}

	memcpy(pMemBuffer, (void *)pDosHead, g_dwSizeOfImage);
exit:
	return pMemBuffer;
}

void ShowError(char *msg)
{
	printf("%s Error %d\n", msg, GetLastError());
}

最终的结果如下,可以看到程序将模块注入到demo.exe中并启动了成功调用了模块中的函数


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

最后于 2021-10-11 19:55 被1900编辑 ,原因:
收藏
点赞4
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回