NtCallEnclave
Transfiere la ejecución desde el código anfitrión VTL0 a una rutina dentro de una enclave inicializada.
Prototipo
NTSTATUS NtCallEnclave( PVOID Routine, PVOID Parameter, BOOLEAN WaitForThread, PVOID *ReturnValue );
Argumentos
| Name | Type | Dir | Description |
|---|---|---|---|
| Routine | PVOID | in | Dirección VTL1 de la función de entrada de la enclave. Debe estar dentro del rango de la enclave inicializada y exportarse vía la config de enclave. |
| Parameter | PVOID | in | Parámetro opaco único pasado a la rutina de enclave en RCX. Típicamente un puntero a un buffer de petición asignado por el anfitrión. |
| WaitForThread | BOOLEAN | in | TRUE bloquea hasta que haya un hilo de enclave libre; FALSE devuelve STATUS_ENCLAVE_NOT_TERMINATED si todos los hilos reservados están ocupados. |
| ReturnValue | PVOID* | out | Recibe el valor PVOID devuelto por la rutina de enclave (su RAX al retornar a VTL0). |
IDs de syscalls por versión de Windows
| Versión de Windows | ID de syscall | Build |
|---|---|---|
| Win10 1709 | 0x8E | win10-1709 |
| Win10 1803 | 0x8F | win10-1803 |
| Win10 1809 | 0x8F | win10-1809 |
| Win10 1903 | 0x8F | win10-1903 |
| Win10 1909 | 0x8F | win10-1909 |
| Win10 2004 | 0x91 | win10-2004 |
| Win10 20H2 | 0x91 | win10-20h2 |
| Win10 21H1 | 0x91 | win10-21h1 |
| Win10 21H2 | 0x91 | win10-21h2 |
| Win10 22H2 | 0x91 | win10-22h2 |
| Win11 21H2 | 0x91 | win11-21h2 |
| Win11 22H2 | 0x91 | win11-22h2 |
| Win11 23H2 | 0x91 | win11-23h2 |
| Win11 24H2 | 0x93 | win11-24h2 |
| Server 2019 | 0x8F | winserver-2019 |
| Server 2022 | 0x91 | winserver-2022 |
| Server 2025 | 0x93 | winserver-2025 |
Módulo del kernel
APIs relacionadas
Stub del 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
Notas no documentadas
`NtCallEnclave` es la llamada de **conmutación de mundo**. Internamente ejecuta un `EENTER` en SGX o un `VMCALL` al Secure Kernel para VBS — ambos transfieren al final la CPU lógica del hilo anfitrión a VTL1 con un contexto pila/registros guardado. La rutina de enclave corre con una pila *fresca* dentro del rango reservado de la enclave; jamás puede tocar memoria del anfitrión VTL0 directamente. Para mover datos, la enclave lee `Parameter` (una dirección VTL0 que el Secure Kernel marshala en la entrada) y devuelve un valor por `*ReturnValue`. La reentrada está limitada por `ThreadCount` de `NtInitializeEnclave`. El envoltorio en modo usuario `CallEnclave` es un shim fino sobre este syscall — toma `Routine` de la tabla de exports de la enclave.
Uso común por malware
Ésta es la llamada que **ejecuta código atacante en VTL1**, pero sólo después de que la cadena create/load/initialize ha pasado las verificaciones de firma. Rutas realistas: (1) llamar a una enclave *firmada por Microsoft* con parámetros que disparen una vulnerabilidad conocida — la enclave entonces ejecuta la lógica del atacante con ceguera del EDR VTL0; (2) llamar a una enclave *loader de investigación* (p. ej. el stub Yuste / Soriano-Salvador) que acepta intencionalmente un puntero a shellcode arbitrario en `Parameter` y salta a él dentro de VTL1. Una vez dentro, la enclave puede usar `NtCallEnclave` con `Routine = dirección fuera de enclave` para retro-llamar al anfitrión (el mecanismo `CallEnclave` es bidireccional); ésta es la vía que usa la investigación tipo Recon para emitir syscalls normales (p. ej. `NtAllocateVirtualMemory`) desde dentro de la enclave. El EDR ve el syscall del anfitrión pero no puede atribuirlo al hilo de la enclave.
Oportunidades de detección
La telemetría directa desde dentro de la enclave es imposible desde VTL0 — por diseño. Pivotar en (a) la *frecuencia* de `NtCallEnclave` por proceso (los trustlets legítimos lo invocan a cadencia estable; una herramienta atacante suele mostrar un pico en el momento del ataque), (b) el patrón *callout*: una enclave que de inmediato se gira y vuelve a llamar a VTL0 para invocar `NtAllocateVirtualMemory`/`NtWriteVirtualMemory` es sospechosa. El proveedor ETW `Microsoft-Windows-Kernel-Memory` (evento 5) traza entradas/salidas de enclave pero no está activo por defecto — activarlo en endpoints de alto valor. Desde un driver de kernel, `KeRegisterEnclaveCallback` *no* existe; no puede interceptar la llamada de enclave desde un driver normal. La señal debe venir de la secuencia syscall del anfitrión.
Ejemplos de syscalls directos
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 ENDPrustwindows-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)
}Mapeos MITRE ATT&CK
Last verified: 2026-05-20