Overview
This guide walks you through the basic usage patterns of syscalls-cpp, from initialization to invoking syscalls with proper error handling.
Quick Start Example
Here’s a complete example that allocates memory using NtAllocateVirtualMemory:
#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 = 0x 1000 ;
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 ;
}
Source: examples/basic-usage.cpp:3-28
Step-by-Step Breakdown
#include <syscalls-cpp/syscall.hpp>
This single header provides access to all syscalls-cpp functionality.
2. Create a Manager Instance
syscall ::Manager < syscall :: policies :: allocator ::section,
syscall :: policies :: generator ::direct > syscallManager;
The Manager is a template class that takes two policy parameters:
Allocation Policy : How memory for syscall stubs is allocated (e.g., section, heap, memory)
Stub Generation Policy : How syscall stubs are generated (e.g., direct, gadget, exception)
For most use cases, the combination of allocator::section with generator::direct provides a good balance of security and simplicity.
3. Initialize the Manager
if ( ! syscallManager . initialize ())
{
std ::cerr << "initialization failed! \n " ;
return 1 ;
}
Initialization performs several critical tasks:
Parses ntdll.dll to resolve syscall numbers
Allocates memory for syscall stubs
Generates the actual stub code
Always check the return value of initialize(). If it returns false, the manager cannot be used and syscall invocations will fail.
Parsing Multiple Modules
By default, only ntdll.dll is parsed. You can specify additional modules:
if ( ! syscallManager . initialize ({
SYSCALL_ID ( "ntdll.dll" ),
SYSCALL_ID ( "win32u.dll" )
}))
{
// Handle initialization failure
}
Source: include/syscalls-cpp/syscall.hpp:714
4. Invoke Syscalls
NTSTATUS status = syscallManager . invoke < NTSTATUS > (
SYSCALL_ID ( "NtAllocateVirtualMemory" ),
syscall :: native :: getCurrentProcess (),
& pBaseAddress,
0 , & uSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE
);
The invoke method is a variadic template that:
Takes the syscall name as a hashed identifier
Accepts all syscall arguments
Returns the specified return type
Return Type Specification
The template parameter specifies the return type:
// For syscalls returning NTSTATUS
NTSTATUS status = syscallManager . invoke < NTSTATUS > (...);
// For syscalls returning pointers
PVOID result = syscallManager . invoke < PVOID > (...);
// For syscalls returning integers
ULONG value = syscallManager . invoke < ULONG > (...);
5. Check Results
if (pBaseAddress)
std ::cout << "allocation successful at 0x" << pBaseAddress << std ::endl;
Always verify the results of your syscalls. For NTSTATUS return values, use the NT_SUCCESS() macro:
NTSTATUS status = syscallManager . invoke < NTSTATUS > (...);
if ( NT_SUCCESS (status))
{
// Success
}
else
{
// Handle error
}
Common Use Cases
Memory Allocation
PVOID pBaseAddress = nullptr ;
SIZE_T uSize = 0x 1000 ;
NTSTATUS status = syscallManager . invoke < NTSTATUS > (
SYSCALL_ID ( "NtAllocateVirtualMemory" ),
syscall :: native :: getCurrentProcess (),
& pBaseAddress,
0 ,
& uSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE
);
Process Enumeration
PVOID pBuffer = nullptr ;
ULONG uReturnLength = 0 ;
// First call to get required size
NTSTATUS status = syscallManager . invoke < NTSTATUS > (
SYSCALL_ID ( "NtQuerySystemInformation" ),
SystemProcessInformation,
pBuffer,
0 ,
& uReturnLength
);
if (status == STATUS_INFO_LENGTH_MISMATCH)
{
// Allocate buffer and call again
pBuffer = malloc (uReturnLength);
status = syscallManager . invoke < NTSTATUS > (
SYSCALL_ID ( "NtQuerySystemInformation" ),
SystemProcessInformation,
pBuffer,
uReturnLength,
& uReturnLength
);
}
File Operations
HANDLE hFile = nullptr ;
IO_STATUS_BLOCK ioStatus = {};
OBJECT_ATTRIBUTES objAttr = {};
UNICODE_STRING fileName = {};
// Initialize structures...
NTSTATUS status = syscallManager . invoke < NTSTATUS > (
SYSCALL_ID ( "NtCreateFile" ),
& hFile,
FILE_GENERIC_READ,
& objAttr,
& ioStatus,
nullptr ,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ,
FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT,
nullptr ,
0
);
Error Handling
The library provides automatic error handling in several ways:
Failed Initialization
If the manager fails to initialize, invoke() will automatically attempt reinitialization once:
if ( ! m_bInitialized)
{
if ( ! initialize ())
{
if constexpr ( std ::is_same_v < Ret, NTSTATUS > )
return native ::STATUS_UNSUCCESSFUL;
return Ret{};
}
}
Source: include/syscalls-cpp/syscall.hpp:774-783
Syscall Not Found
If a requested syscall is not found in the parsed modules:
if (it == m_vecParsedSyscalls . end () || it -> m_key != syscallId)
{
if constexpr ( std ::is_same_v < Ret, NTSTATUS > )
return native ::STATUS_PROCEDURE_NOT_FOUND;
return Ret{};
}
Source: include/syscalls-cpp/syscall.hpp:786-792
Type Safety with SYSCALL_ID
The SYSCALL_ID macro ensures type safety and enables compile-time optimizations:
// Compile-time hash (recommended)
syscallManager . invoke < NTSTATUS > (
SYSCALL_ID ( "NtAllocateVirtualMemory" ),
...
);
// Runtime hash (when string is dynamic)
const char * syscallName = getSyscallName ();
syscallManager . invoke < NTSTATUS > (
SYSCALL_ID_RT (syscallName),
...
);
When SYSCALLS_NO_HASH is defined, SYSCALL_ID uses std::string instead of compile-time hashes for easier debugging.
Best Practices
Always Check Initialization Never skip checking the return value of initialize(). A failed initialization means all subsequent invocations will fail.
Use nullptr, Not NULL On x64 platforms, always use nullptr instead of NULL to avoid stack corruption. See Security Considerations .
Handle Return Values Always check NTSTATUS return values using NT_SUCCESS() or compare against specific error codes.
Reuse Managers Create one manager instance and reuse it. Initialization is expensive, but invocation is fast.
Next Steps
Custom Policies Learn how to write custom allocation, generation, and parsing policies
Debugging Techniques for troubleshooting and verifying syscall behavior
Security Considerations Critical security best practices and threat model considerations