> Windows Syscalls
ntoskrnl.exeT1620T1574T1106

NtCallEnclave

Transfère l'exécution du code hôte VTL0 vers une routine à l'intérieur d'une enclave initialisée.

Prototype

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

Arguments

NameTypeDirDescription
RoutinePVOIDinAdresse VTL1 de la fonction d'entrée de l'enclave. Doit se trouver dans la plage de l'enclave initialisée et être exportée via la config enclave.
ParameterPVOIDinParamètre opaque unique passé à la routine d'enclave dans RCX. Typiquement un pointeur vers un buffer de requête alloué côté hôte.
WaitForThreadBOOLEANinTRUE bloque jusqu'à ce qu'un thread d'enclave libre soit disponible ; FALSE retourne STATUS_ENCLAVE_NOT_TERMINATED si tous les threads réservés sont occupés.
ReturnValuePVOID*outReçoit la valeur PVOID retournée par la routine d'enclave (son RAX au retour vers VTL0).

IDs de syscalls par version de Windows

Version de WindowsID de syscallBuild
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

Module noyau

ntoskrnl.exeNtCallEnclave

APIs liées

CallEnclaveNtCreateEnclaveNtInitializeEnclaveNtLoadEnclaveDataNtTerminateEnclaveGetProcAddressForCaller

Stub du syscall

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

Notes non documentées

`NtCallEnclave` est l'appel de **bascule de monde**. En interne il exécute un `EENTER` sur SGX ou un `VMCALL` vers le Secure Kernel pour VBS — les deux transfèrent in fine le CPU logique du thread hôte vers VTL1 avec contexte stack/registres sauvegardé. La routine d'enclave s'exécute avec une stack *fraîche* à l'intérieur de la plage réservée de l'enclave ; elle ne peut jamais toucher directement la mémoire hôte VTL0. Pour échanger des données, l'enclave lit `Parameter` (une adresse VTL0 que le Secure Kernel marshale à l'entrée) et retourne une valeur via `*ReturnValue`. La réentrance est bornée par `ThreadCount` issu de `NtInitializeEnclave`. Le wrapper user-mode `CallEnclave` est un fin shim autour de ce syscall — il récupère `Routine` depuis la table d'exports de l'enclave.

Usage courant par les malwares

C'est l'appel qui **exécute du code attaquant en VTL1**, mais seulement après que la chaîne create/load/initialize a passé les vérifications de signature. Voies d'abus réalistes : (1) appeler dans une enclave *signée Microsoft* avec des paramètres ciblant une vulnérabilité connue — l'enclave exécute alors la logique attaquante en aveuglant l'EDR VTL0 ; (2) appeler dans une enclave *loader de recherche* (ex. le stub Yuste / Soriano-Salvador) qui accepte intentionnellement un pointeur de shellcode arbitraire dans `Parameter` et saute dessus dans VTL1. Une fois dedans, l'enclave peut utiliser `NtCallEnclave` avec `Routine = adresse hors enclave` pour rappeler l'hôte (le mécanisme `CallEnclave` est bidirectionnel) ; c'est la voie utilisée par la recherche Recon pour émettre des syscalls normaux (ex. `NtAllocateVirtualMemory`) depuis l'enclave. L'EDR voit le syscall hôte mais ne peut l'attribuer au thread enclave.

Opportunités de détection

La télémétrie directe depuis l'intérieur de l'enclave est impossible depuis VTL0 — c'est par conception. Pivoter plutôt sur (a) la *fréquence* de `NtCallEnclave` par processus (les trustlets légitimes l'appellent à cadence stable ; un outil attaquant montre souvent un pic au moment de l'attaque), (b) le pattern *callout* : une enclave qui se retourne immédiatement pour rappeler VTL0 et invoquer `NtAllocateVirtualMemory`/`NtWriteVirtualMemory` est suspecte. Le provider ETW `Microsoft-Windows-Kernel-Memory` (event 5) trace les entrées/sorties d'enclave mais n'est pas activé par défaut — l'activer sur les endpoints sensibles. Côté driver noyau, `KeRegisterEnclaveCallback` n'existe *pas* ; vous ne pouvez pas intercepter l'appel enclave depuis un driver classique. Le signal doit venir de la séquence syscall hôte.

Exemples de syscalls directs

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)
}

Mappings MITRE ATT&CK

Last verified: 2026-05-20