Overview
The allocator::memory policy allocates memory using NtAllocateVirtualMemory, initially with PAGE_READWRITE permissions. After copying the syscall stubs, it transitions the protection to PAGE_EXECUTE_READ using NtProtectVirtualMemory.
Method Signatures
Allocates virtual memory and sets it up for syscall stub execution static bool allocate (
size_t uRegionSize ,
const std :: span < const uint8_t > vecBuffer ,
void*& pOutRegion ,
HANDLE & unused
)
Size of the region to allocate in bytes
vecBuffer
const std::span<const uint8_t>
required
Buffer containing the syscall stubs to copy into memory
Output parameter receiving the base address of the allocated region
Returns true on successful allocation and protection change, false on failure
Frees the virtual memory region static void release ( void* pRegion , HANDLE /*heapHandle*/ )
Base address of the region to free
Implementation Details
The allocation process follows a W^X (Write XOR Execute) pattern:
Allocate memory with PAGE_READWRITE (writable, not executable)
Copy the syscall stubs into the writable memory
Change protection to PAGE_EXECUTE_READ (executable, not writable)
Return the allocation address
This approach follows the W^X security principle: memory is never simultaneously writable and executable.
Source Code
From syscall.hpp:211-259:
struct memory
{
static bool allocate ( size_t uRegionSize , const std :: span < const uint8_t > vecBuffer ,
void*& pOutRegion , HANDLE & /*unused*/ )
{
HMODULE hNtDll = native :: getModuleBase ( hashing :: calculateHash ( "ntdll.dll" ));
auto fNtAllocate = reinterpret_cast < native :: NtAllocateVirtualMemory_t > (
native :: getExportAddress (hNtDll, SYSCALL_ID ( "NtAllocateVirtualMemory" )));
auto fNtProtect = reinterpret_cast < native :: NtProtectVirtualMemory_t > (
native :: getExportAddress (hNtDll, SYSCALL_ID ( "NtProtectVirtualMemory" )));
if ( ! fNtAllocate || ! fNtProtect)
return false ;
pOutRegion = nullptr ;
SIZE_T uSize = uRegionSize;
NTSTATUS status = fNtAllocate (
native :: getCurrentProcess (), & pOutRegion, 0 , & uSize,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE
);
if ( ! NT_SUCCESS (status) || ! pOutRegion)
return false ;
// Copy stubs while writable
std :: copy_n ( vecBuffer . data (), uRegionSize, static_cast < uint8_t *> (pOutRegion));
// Transition to executable
ULONG oldProtection = 0 ;
uSize = uRegionSize;
status = fNtProtect (
native :: getCurrentProcess (), & pOutRegion, & uSize,
PAGE_EXECUTE_READ, & oldProtection
);
if ( ! NT_SUCCESS (status)) {
// Cleanup on protection failure
uSize = 0 ;
fNtAllocate ( native :: getCurrentProcess (), & pOutRegion, 0 ,
& uSize, MEM_RELEASE, 0 );
pOutRegion = nullptr ;
return false ;
}
return true ;
}
static void release ( void* pRegion , HANDLE /*heapHandle*/ )
{
if (pRegion) {
HMODULE hNtDll = native :: getModuleBase ( hashing :: calculateHash ( "ntdll.dll" ));
auto fNtFree = reinterpret_cast < native :: NtFreeVirtualMemory_t > (
native :: getExportAddress (hNtDll, SYSCALL_ID ( "NtFreeVirtualMemory" )));
if (fNtFree) {
SIZE_T uSize = 0 ;
fNtFree ( native :: getCurrentProcess (), & pRegion, & uSize, MEM_RELEASE);
}
}
}
};
Usage Example
#include <syscalls-cpp/syscall.hpp>
// Compose manually with memory allocator
using MemoryDirectManager = syscall ::Manager <
syscall :: policies :: allocator ::memory,
syscall :: policies :: generator ::direct
> ;
MemoryDirectManager manager;
if ( ! manager . initialize ()) {
std ::cerr << "Failed to initialize with memory allocator \n " ;
return 1 ;
}
// Invoke syscalls as usual
PVOID pAddress = nullptr ;
SIZE_T uSize = 0x 1000 ;
manager . invoke < NTSTATUS > (
SYSCALL_ID ( "NtAllocateVirtualMemory" ),
NtCurrentProcess (),
& pAddress, 0 , & uSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE
);
Security Properties
W^X Principle Memory is never simultaneously writable and executable, following modern security best practices.
Standard Allocation Uses the standard virtual memory allocator, making it compatible and well-understood.
Trade-offs
Aspect Description Security Medium—follows W^X but memory can be made writable again Performance Fast (two syscalls: allocate + protect) Complexity Simple, well-documented Windows API Compatibility Windows NT 3.1+ (most compatible) Flexibility Protection can be changed after allocation
While this policy follows W^X during initialization, the memory protection can still be changed post-allocation using VirtualProtect or similar APIs. For immutable stubs, use allocator::section.
See Also
allocator::section Immutable section-based allocation
allocator::heap Heap-based allocation
Policy Composition Learn how to combine policies