> Windows Syscalls
ntoskrnl.exeT1620T1574T1106

NtCallEnclave

Transitions execution from VTL0 host code into a routine inside an initialised enclave.

Prototype

NTSTATUS NtCallEnclave(
  PVOID    Routine,
  PVOID    Parameter,
  BOOLEAN  WaitForThread,
  PVOID   *ReturnValue
);

Arguments

NameTypeDirDescription
RoutinePVOIDinVTL1 address of the enclave entry function. Must lie inside the initialised enclave's range and be exported via the enclave config.
ParameterPVOIDinSingle opaque parameter passed to the enclave routine in RCX. Typically a pointer to a host-allocated request buffer.
WaitForThreadBOOLEANinTRUE blocks until a free enclave thread is available; FALSE returns STATUS_ENCLAVE_NOT_TERMINATED if all reserved threads are busy.
ReturnValuePVOID*outReceives the PVOID value returned from the enclave routine (its RAX on return into VTL0).

Syscall IDs by Windows version

Windows versionSyscall IDBuild
Win10 17090x8Ewin10-1709
Win10 18030x8Fwin10-1803
Win10 18090x8Fwin10-1809
Win10 19030x8Fwin10-1903
Win10 19090x8Fwin10-1909
Win10 20040x91win10-2004
Win10 20H20x91win10-20h2
Win10 21H10x91win10-21h1
Win10 21H20x91win10-21h2
Win10 22H20x91win10-22h2
Win11 21H20x91win11-21h2
Win11 22H20x91win11-22h2
Win11 23H20x91win11-23h2
Win11 24H20x93win11-24h2
Server 20190x8Fwinserver-2019
Server 20220x91winserver-2022
Server 20250x93winserver-2025

Kernel module

ntoskrnl.exeNtCallEnclave

Related APIs

CallEnclaveNtCreateEnclaveNtInitializeEnclaveNtLoadEnclaveDataNtTerminateEnclaveGetProcAddressForCaller

Syscall stub

4C 8B D1            mov r10, rcx
B8 93 00 00 00      mov eax, 0x93
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

`NtCallEnclave` is the **world-switch** call. Internally it performs an `EENTER` on SGX or a `VMCALL` into the Secure Kernel for VBS — both ultimately transfer the host thread's logical CPU into VTL1 with a saved-stack-and-registers context. The enclave routine runs with a *fresh* stack inside the enclave's reserved range; it can never touch host-VTL0 memory directly. To get data in and out, the enclave reads `Parameter` (a VTL0 address that the Secure Kernel marshals on entry) and returns a value via `*ReturnValue`. Reentrance is bounded by `ThreadCount` from `NtInitializeEnclave`. The user-mode wrapper `CallEnclave` is a thin shim around this syscall — it picks up `Routine` from the enclave's export table.

Common malware usage

This is the call that **executes attacker code in VTL1**, but only after the create/load/initialize chain has passed signature checks. Realistic abuse paths: (1) call into a *Microsoft-signed* enclave with crafted parameters that trigger a known vulnerability — the enclave then runs attacker logic with VTL0-EDR blindness; (2) call into a *research-loader* enclave (e.g. the Yuste / Soriano-Salvador stub) that intentionally accepts an arbitrary shellcode pointer in `Parameter` and jumps to it inside VTL1. Once inside, the enclave can use `NtCallEnclave` with `Routine = 0x... outside enclave` to call *back* into the host (it's bidirectional via the `CallEnclave` mechanism); this is the path that Recon-class research uses to issue normal syscalls (e.g. `NtAllocateVirtualMemory`) from within the enclave. EDR sees the host syscall but cannot attribute it to the enclave thread.

Detection opportunities

Direct telemetry from inside the enclave is impossible from VTL0 — that is by design. Pivot instead on (a) the *frequency* of `NtCallEnclave` from a process (legitimate trustlets call it on a steady cadence; an attacker tool often shows a burst at attack time), (b) the *callout* pattern: an enclave that immediately turns around and calls back into VTL0 to invoke `NtAllocateVirtualMemory`/`NtWriteVirtualMemory` is suspicious. The ETW provider `Microsoft-Windows-Kernel-Memory` (event 5) traces enclave entries/exits but is not enabled by default — turn it on for high-value endpoints. From a kernel driver perspective, `KeRegisterEnclaveCallback` does *not* exist; you cannot intercept the enclave call from a normal driver. The signal must come from the host syscall sequence.

Direct syscall examples

cVBS enclave bring-up (step 4 — call into VTL1)

// Final step of a minimal enclave session: invoke a named export inside the enclave.
// 'enclaveBase' came from CreateEnclave; 'EnclaveEntry' is exported by the signed enclave DLL.
#include <windows.h>

typedef int (*PENCLAVE_ENTRY)(void *param);

int InvokeEnclave(PVOID enclaveBase, PVOID hostRequest) {
    PENCLAVE_ENTRY entry = (PENCLAVE_ENTRY)GetProcAddressForCaller(
        enclaveBase, "EnclaveEntry");
    PVOID retVal = NULL;
    if (!CallEnclave(entry, hostRequest, TRUE /*WaitForThread*/, &retVal)) {
        return -1;
    }
    return (int)(INT_PTR)retVal;
}

asmx64 direct stub (Win11 24H2)

; Direct syscall stub for NtCallEnclave (SSN 0x93 on Win11 24H2 / Server 2025)
; SSN was 0x91 from Win10 2004 through Win11 23H2 — stable for ~5 years.
NtCallEnclave PROC
    mov  r10, rcx          ; Routine
    mov  eax, 93h          ; SSN
    syscall
    ret
NtCallEnclave ENDP

rustwindows-sys CallEnclave

// Cargo: windows-sys = { version = "0.59", features = ["Win32_System_Threading"] }
use std::ptr::null_mut;
use windows_sys::core::BOOL;

extern "system" {
    fn CallEnclave(
        routine: *const core::ffi::c_void,
        param: *mut core::ffi::c_void,
        wait_for_thread: BOOL,
        ret: *mut *mut core::ffi::c_void,
    ) -> BOOL;
}

pub unsafe fn call_enclave_entry(
    routine: *const core::ffi::c_void,
    request: *mut core::ffi::c_void,
) -> Option<*mut core::ffi::c_void> {
    let mut ret: *mut core::ffi::c_void = null_mut();
    if CallEnclave(routine, request, 1 /*TRUE*/, &mut ret) == 0 {
        return None;
    }
    Some(ret)
}

MITRE ATT&CK mappings

Last verified: 2026-05-20