Skip to main content
This guide will walk you through creating your first program using syscalls-cpp. You’ll learn how to initialize a syscall manager and invoke system calls directly.

Prerequisites

Ensure you have completed the installation steps and have a C++20-compatible compiler configured.

Basic Usage

1

Include the header

Start by including the syscalls-cpp header in your source file:
#include <iostream>
#include <syscalls-cpp/syscall.hpp>
This single header provides access to all syscall management functionality.
2

Create a syscall manager

Instantiate a syscall::Manager with your chosen policies:
int main() {
    // Create a manager with section-based allocation and direct syscalls
    syscall::Manager<
        syscall::policies::allocator::section,
        syscall::policies::generator::direct
    > syscallManager;
The Manager template takes two required parameters:
  1. Allocation Policy: How memory for stubs is allocated
  2. Stub Generation Policy: How syscall instructions are generated
An optional third parameter specifies parsing policies (defaults to directory with signature fallback).
3

Initialize the manager

Before invoking syscalls, initialize the manager to parse system call numbers:
    if (!syscallManager.initialize()) {
        std::cerr << "initialization failed!\n";
        return 1;
    }
By default, initialize() parses ntdll.dll. You can parse additional modules:
// Parse multiple modules
syscallManager.initialize({
    SYSCALL_ID("ntdll.dll"),
    SYSCALL_ID("win32u.dll")
});
4

Invoke a syscall

Use the invoke() method to call a system call by name:
    PVOID pBaseAddress = nullptr;
    SIZE_T uSize = 0x1000;
    
    std::ignore = syscallManager.invoke<NTSTATUS>(
        SYSCALL_ID("NtAllocateVirtualMemory"),
        syscall::native::getCurrentProcess(),
        &pBaseAddress,
        0, &uSize,
        MEM_COMMIT | MEM_RESERVE,
        PAGE_READWRITE
    );
  • The first template parameter (NTSTATUS) specifies the return type
  • SYSCALL_ID() computes a compile-time hash of the syscall name
  • Arguments match the native Windows API signature
5

Verify the result

Check that the syscall succeeded:
    if (pBaseAddress) {
        std::cout << "allocation successful at 0x" << pBaseAddress << std::endl;
    }
    
    return 0;
}

Complete Example

Here’s the full working example:
basic-usage.cpp
#include <iostream>
#include <syscalls-cpp/syscall.hpp>

int main() 
{
    syscall::Manager<
        syscall::policies::allocator::section,
        syscall::policies::generator::direct
    > syscallManager;
    
    if (!syscallManager.initialize())
    {
        std::cerr << "initialization failed!\n";
        return 1;
    }

    PVOID pBaseAddress = nullptr;
    SIZE_T uSize = 0x1000;

    std::ignore = syscallManager.invoke<NTSTATUS>(
        SYSCALL_ID("NtAllocateVirtualMemory"),
        syscall::native::getCurrentProcess(),
        &pBaseAddress,
        0, &uSize,
        MEM_COMMIT | MEM_RESERVE,
        PAGE_READWRITE
    );

    if (pBaseAddress)
        std::cout << "allocation successful at 0x" << pBaseAddress << std::endl;

    return 0;
}

Key Concepts

Type Aliases

For convenience, syscalls-cpp provides pre-configured type aliases:
// Section + Gadget (x64 only)
SyscallSectionGadget manager;

// Heap + Gadget (x64 only)
SyscallHeapGadget manager;

The SYSCALL_ID Macro

The SYSCALL_ID() macro performs compile-time hashing of function names for efficient lookups:
// Compile-time hash
auto result = manager.invoke<NTSTATUS>(
    SYSCALL_ID("NtAllocateVirtualMemory"),
    // ...
);
For runtime strings, use SYSCALL_ID_RT():
const char* dynamicName = getSyscallName();
auto result = manager.invoke<NTSTATUS>(
    SYSCALL_ID_RT(dynamicName),
    // ...
);

NULL vs nullptr on x64

Always use nullptr instead of NULL when invoking syscalls on x64.The NULL macro is often defined as 0 (a 32-bit int). Passing it to a syscall expecting a 64-bit pointer can corrupt the stack, causing argument misalignment and crashes.nullptr is type-safe and guarantees the correct 64-bit null pointer representation.
// ❌ Bad - may crash on x64
manager.invoke<NTSTATUS>(
    SYSCALL_ID("NtClose"),
    NULL  // Dangerous!
);

// ✅ Good - type-safe
manager.invoke<NTSTATUS>(
    SYSCALL_ID("NtClose"),
    nullptr  // Safe
);

Advanced Configuration

Custom Policy Combinations

You can mix and match policies based on your security requirements:
// Maximum stealth: section allocation + gadget-based syscalls
syscall::Manager<
    syscall::policies::allocator::section,
    syscall::policies::generator::gadget
> stealthManager;

// Exception-based: triggers via VEH
syscall::Manager<
    syscall::policies::allocator::heap,
    syscall::policies::generator::exception
> exceptionManager;

// Memory-based: standard allocation
syscall::Manager<
    syscall::policies::allocator::memory,
    syscall::policies::generator::direct
> standardManager;

Custom Type Aliases

Create your own type aliases for commonly used configurations:
// Define once
using MySyscallManager = syscall::Manager<
    syscall::policies::allocator::heap,
    syscall::policies::generator::direct
>;

// Use anywhere
MySyscallManager manager;
manager.initialize();

Next Steps

Now that you understand the basics, explore more advanced topics:

Core Concepts

Deep dive into policies and architecture

Custom Policies

Learn to create your own allocation and generation policies

API Reference

Complete API documentation

Examples

More real-world usage examples

Build docs developers (and LLMs) love