NtCreateEnclave
Allocates a new enclave (SGX or VBS/VTL1) inside a target process's address space.
Prototype
NTSTATUS NtCreateEnclave( HANDLE ProcessHandle, PVOID *BaseAddress, ULONG_PTR ZeroBits, SIZE_T Size, SIZE_T InitialCommitment, ULONG EnclaveType, PVOID EnclaveInformation, ULONG EnclaveInformationLength, PSIZE_T EnclaveError );
Arguments
| Name | Type | Dir | Description |
|---|---|---|---|
| ProcessHandle | HANDLE | in | Handle to the host process. NtCurrentProcess() ((HANDLE)-1) is the only practical value. |
| BaseAddress | PVOID* | in/out | Pointer to the requested base. NULL lets the kernel pick a slot. Receives the chosen base on success. |
| ZeroBits | ULONG_PTR | in | Number of high-order zero bits in BaseAddress. Typically 0. |
| Size | SIZE_T | in | Total enclave address-range size in bytes. Must be a power of two on SGX; page-aligned on VBS. |
| InitialCommitment | SIZE_T | in | Bytes initially committed; remainder is reserved. Use 0 to reserve only. |
| EnclaveType | ULONG | in | ENCLAVE_TYPE_SGX (1), ENCLAVE_TYPE_SGX2 (2) or ENCLAVE_TYPE_VBS (0x10). SGX is deprecated post-2024. |
| EnclaveInformation | PVOID | in | Type-specific creation struct: ENCLAVE_CREATE_INFO_SGX or ENCLAVE_CREATE_INFO_VBS (owner ID + flags). |
| EnclaveInformationLength | ULONG | in | Size in bytes of EnclaveInformation. |
| EnclaveError | PSIZE_T | out | Receives the hardware/securekernel-specific error code when NTSTATUS is STATUS_ENCLAVE_FAILURE. |
Syscall IDs by Windows version
| Windows version | Syscall ID | Build |
|---|---|---|
| Win10 1607 | 0x9E | win10-1607 |
| Win10 1703 | 0xA1 | win10-1703 |
| Win10 1709 | 0xA2 | win10-1709 |
| Win10 1803 | 0xA3 | win10-1803 |
| Win10 1809 | 0xA3 | win10-1809 |
| Win10 1903 | 0xA4 | win10-1903 |
| Win10 1909 | 0xA4 | win10-1909 |
| Win10 2004 | 0xA8 | win10-2004 |
| Win10 20H2 | 0xA8 | win10-20h2 |
| Win10 21H1 | 0xA8 | win10-21h1 |
| Win10 21H2 | 0xA9 | win10-21h2 |
| Win10 22H2 | 0xA9 | win10-22h2 |
| Win11 21H2 | 0xAB | win11-21h2 |
| Win11 22H2 | 0xAC | win11-22h2 |
| Win11 23H2 | 0xAC | win11-23h2 |
| Win11 24H2 | 0xAE | win11-24h2 |
| Server 2016 | 0x9E | winserver-2016 |
| Server 2019 | 0xA3 | winserver-2019 |
| Server 2022 | 0xAB | winserver-2022 |
| Server 2025 | 0xAE | winserver-2025 |
Kernel module
Related APIs
Syscall stub
4C 8B D1 mov r10, rcx B8 AE 00 00 00 mov eax, 0xAE F6 04 25 08 03 FE 7F 01 test byte ptr [0x7FFE0308], 1 75 03 jne short +3 0F 05 syscall C3 ret CD 2E int 2Eh C3 ret
Undocumented notes
`NtCreateEnclave` is the entry point into Windows' two enclave technologies: **Intel SGX** (`ENCLAVE_TYPE_SGX`/`SGX2`) and **Virtualization-Based Security enclaves** (`ENCLAVE_TYPE_VBS` = 0x10). SGX is effectively dead — Intel removed client SGX in 12th-gen Core, and Microsoft has been deprecating the API surface since 2024. VBS enclaves are the strategic primitive: a chunk of address space in a user-mode process whose code and data execute in **VTL1** (Virtual Trust Level 1), a more-privileged trust level managed by the **Secure Kernel** (securekernel.exe) on top of Hyper-V. From the host process' perspective the enclave is a normal range of memory; from the *kernel's* perspective it is opaque — VTL0 (where ntoskrnl runs) cannot read or write VTL1 pages. Creation requires the `SeEnableVirtualizationPrivilege` (held only by signed processes that opted in via `IMAGE_ENCLAVE_CONFIG`) and the eventual enclave image must be Authenticode-signed with a Microsoft-issued enclave-signing cert (or be a debug enclave on a test-signed machine). The host links against `vertdll.dll` which marshals the create/init/call/load sequence into the LSASS-adjacent `LsaIso.exe` trustlet model. Production users today: **Credential Guard**, **vTPM**, **Win32k mitigation policy attestation**, and some Microsoft research deployments.
Common malware usage
Enclaves are a **research-stage offensive primitive**, not yet a fixture of commodity malware. The appeal is structural: VTL0 kernel-mode EDR drivers (every Microsoft, CrowdStrike, SentinelOne, Defender driver) **cannot read VTL1 memory** — Hyper-V refuses the mapping. So shellcode hosted in an enclave is invisible to kernel-mode AV scans, kernel callbacks fire after the fact only on the *side effects* of enclave execution. Notable public work: **Schenk & Park (Black Hat USA 2022) — "A Dirty Little History: Bypassing Spectre Hardware Mitigations"** touched enclave isolation; **Yuste / Soriano-Salvador (Recon 2023)** demonstrated a VBS-enclave loader for arbitrary unsigned shellcode by abusing a signed loader stub; **TrustedSec** and **MDSec** have public PoCs. The practical barrier is the *signing requirement* — abuse currently requires either (a) a stolen Microsoft enclave-signing key, (b) a code-signing-cert-private trustlet vulnerability, or (c) a Microsoft-signed enclave loader with a memory-corruption bug (the most realistic path). No widely-attributed in-the-wild family uses enclaves yet.
Detection opportunities
Enclave creation is **near-zero on a normal endpoint** — that is the entire detection strategy. ETW provider `Microsoft-Windows-Kernel-Memory` emits events on VBS enclave creation; `Microsoft-Windows-Hyper-V-Worker` shows VTL1 activity. On Win11 24H2, `Microsoft-Windows-Security-Mitigations/KernelMode` includes enclave lifecycle. Practical hunt: list processes that hold the `SeEnableVirtualizationPrivilege` access right, cross-reference against `IMAGE_ENCLAVE_CONFIG` directory presence in their loaded modules, and alert on anything that is **not** `lsass.exe`, `LsaIso.exe`, `securekernel.exe` peers, or known Microsoft trustlets. EDR vendors do *not* see enclave-internal code execution, but they do see the host process' `NtCreateEnclave` / `NtLoadEnclaveData` / `NtInitializeEnclave` syscall sequence — hook those (or scrape kernel-syscall ETW) and treat the chain as a high-fidelity signal outside of `lsass`. Defender uses the Hyper-V code-integrity layer (HVCI) to refuse unsigned enclave images; never disable HVCI in a managed environment.
Direct syscall examples
cVBS enclave bring-up (step 1/3 — create)
// Minimal VBS enclave creation skeleton. Requires:
// * Host binary compiled with /ENCLAVE flag and an IMAGE_ENCLAVE_CONFIG directory
// * Enclave DLL Authenticode-signed with a Microsoft-issued enclave cert
// * SeEnableVirtualizationPrivilege available (granted to host by signing policy)
// See NtInitializeEnclave + NtCallEnclave for the rest of the lifecycle.
#include <windows.h>
#include <ntsecapi.h>
typedef struct _ENCLAVE_CREATE_INFO_VBS {
ULONG Flags; // 0 or ENCLAVE_VBS_FLAG_DEBUG
UCHAR OwnerID[32]; // 32-byte owner identity
} ENCLAVE_CREATE_INFO_VBS;
PVOID CreateVbsEnclave(SIZE_T size) {
ENCLAVE_CREATE_INFO_VBS info = {0};
info.Flags = ENCLAVE_VBS_FLAG_DEBUG; // omit in production
// OwnerID can be left zero for a single-owner test enclave
PVOID base = CreateEnclave(
GetCurrentProcess(),
NULL, // let kernel choose base
size,
0, // initial commit = 0
ENCLAVE_TYPE_VBS,
&info, sizeof(info),
NULL); // enclaveError
if (!base) {
DWORD ec = GetLastError(); // NTSTATUS-mapped
return NULL;
}
return base;
}asmx64 direct stub (Win11 24H2)
; Direct syscall stub for NtCreateEnclave (SSN 0xAE on Win11 24H2 / Server 2025)
; Note: enclave SSNs drift across builds — resolve dynamically for portable code.
NtCreateEnclave PROC
mov r10, rcx ; ProcessHandle
mov eax, 0AEh ; SSN
syscall
ret
NtCreateEnclave ENDPrustwindows-sys CreateEnclave wrapper
// Cargo: windows-sys = { version = "0.59", features = [
// "Win32_System_Threading", "Win32_Foundation", "Win32_System_Memory"
// ] }
use std::ptr::{null, null_mut};
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::System::Threading::GetCurrentProcess;
#[repr(C)]
struct EnclaveCreateInfoVbs {
flags: u32,
owner_id: [u8; 32],
}
extern "system" {
fn CreateEnclave(
process: HANDLE, base: *const u8, size: usize, initial_commit: usize,
ty: u32, info: *const core::ffi::c_void, info_len: u32, err: *mut u32,
) -> *mut core::ffi::c_void;
}
const ENCLAVE_TYPE_VBS: u32 = 0x10;
const ENCLAVE_VBS_FLAG_DEBUG: u32 = 1;
pub unsafe fn create_vbs_enclave(size: usize) -> Option<*mut core::ffi::c_void> {
let info = EnclaveCreateInfoVbs { flags: ENCLAVE_VBS_FLAG_DEBUG, owner_id: [0u8; 32] };
let mut enclave_err: u32 = 0;
let base = CreateEnclave(
GetCurrentProcess(), null(), size, 0,
ENCLAVE_TYPE_VBS,
&info as *const _ as *const _, core::mem::size_of::<EnclaveCreateInfoVbs>() as u32,
&mut enclave_err,
);
if base.is_null() { None } else { Some(base) }
}MITRE ATT&CK mappings
Last verified: 2026-05-20