Skip to main content

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

allocate
static bool
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
)
return
bool
Returns true on successful allocation and protection change, false on failure
release
static void
Frees the virtual memory region
static void release(void* pRegion, HANDLE /*heapHandle*/)

Implementation Details

The allocation process follows a W^X (Write XOR Execute) pattern:
  1. Allocate memory with PAGE_READWRITE (writable, not executable)
  2. Copy the syscall stubs into the writable memory
  3. Change protection to PAGE_EXECUTE_READ (executable, not writable)
  4. 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 = 0x1000;

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

AspectDescription
SecurityMedium—follows W^X but memory can be made writable again
PerformanceFast (two syscalls: allocate + protect)
ComplexitySimple, well-documented Windows API
CompatibilityWindows NT 3.1+ (most compatible)
FlexibilityProtection 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

Build docs developers (and LLMs) love