NtCallEnclave
Überführt die Ausführung vom VTL0-Host-Code in eine Routine innerhalb einer initialisierten Enklave.
Prototyp
NTSTATUS NtCallEnclave( PVOID Routine, PVOID Parameter, BOOLEAN WaitForThread, PVOID *ReturnValue );
Argumente
| Name | Type | Dir | Description |
|---|---|---|---|
| Routine | PVOID | in | VTL1-Adresse der Enklaven-Einstiegsfunktion. Muss im Bereich der initialisierten Enklave liegen und über die Enclave-Config exportiert sein. |
| Parameter | PVOID | in | Einzelner opaker Parameter, der der Enklaven-Routine in RCX übergeben wird. Üblicherweise ein Zeiger auf einen vom Host allokierten Request-Puffer. |
| WaitForThread | BOOLEAN | in | TRUE blockiert, bis ein freier Enklaven-Thread verfügbar ist; FALSE liefert STATUS_ENCLAVE_NOT_TERMINATED, wenn alle reservierten Threads belegt sind. |
| ReturnValue | PVOID* | out | Empfängt den PVOID-Rückgabewert der Enklaven-Routine (deren RAX beim Rückkehren nach VTL0). |
Syscall-IDs pro Windows-Version
| Windows-Version | Syscall-ID | 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 |
Kernel-Modul
Verwandte APIs
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
Undokumentierte Hinweise
`NtCallEnclave` ist der **World-Switch**-Call. Intern führt er ein `EENTER` auf SGX bzw. einen `VMCALL` in den Secure Kernel für VBS aus — beides überträgt schließlich die logische CPU des Host-Threads in VTL1 mit gespeichertem Stack-/Register-Kontext. Die Enklaven-Routine läuft mit einem *frischen* Stack innerhalb des reservierten Enklaven-Bereichs; sie kann den VTL0-Host-Speicher niemals direkt anfassen. Datenaustausch: die Enklave liest `Parameter` (eine VTL0-Adresse, die der Secure Kernel beim Eintritt marshallt) und liefert einen Wert per `*ReturnValue`. Wiedereintritt ist durch `ThreadCount` aus `NtInitializeEnclave` begrenzt. Der User-Mode-Wrapper `CallEnclave` ist ein dünner Shim um diesen Syscall — er holt `Routine` aus der Export-Tabelle der Enklave.
Häufige Malware-Nutzung
Dies ist der Call, der **Angreifer-Code in VTL1 ausführt**, aber erst nachdem die Create/Load/Initialize-Kette die Signaturprüfungen bestanden hat. Realistische Missbrauchswege: (1) eine *Microsoft-signierte* Enklave mit präparierten Parametern aufrufen, die eine bekannte Schwachstelle triggern — die Enklave führt dann Angreifer-Logik mit VTL0-EDR-Blindheit aus; (2) eine *Research-Loader*-Enklave (z. B. der Yuste / Soriano-Salvador-Stub) aufrufen, die in `Parameter` absichtlich einen beliebigen Shellcode-Pointer akzeptiert und in VTL1 dorthin springt. Einmal drin, kann die Enklave `NtCallEnclave` mit `Routine = Adresse außerhalb der Enklave` nutzen, um zurück in den Host zu rufen (der `CallEnclave`-Mechanismus ist bidirektional); diesen Weg nutzt Recon-Class-Research, um normale Syscalls (z. B. `NtAllocateVirtualMemory`) aus der Enklave abzusetzen. EDR sieht den Host-Syscall, kann ihn aber nicht dem Enklaven-Thread zuordnen.
Erkennungsmöglichkeiten
Direkte Telemetrie aus dem Enklaven-Inneren ist von VTL0 aus unmöglich — by design. Stattdessen pivotieren auf (a) die *Frequenz* von `NtCallEnclave` pro Prozess (legitime Trustlets rufen es in stabiler Kadenz; ein Angreifer-Tool zeigt oft einen Burst zum Angriffszeitpunkt), (b) das *Callout*-Muster: eine Enklave, die sofort zurück in VTL0 ruft, um `NtAllocateVirtualMemory`/`NtWriteVirtualMemory` aufzurufen, ist verdächtig. Der ETW-Provider `Microsoft-Windows-Kernel-Memory` (Event 5) traced Enklaven-Entries/Exits, ist aber nicht standardmäßig aktiv — auf wertvollen Endpoints aktivieren. Aus einem Kernel-Driver heraus existiert `KeRegisterEnclaveCallback` *nicht*; den Enklaven-Call können Sie aus einem normalen Driver nicht abfangen. Das Signal muss aus der Host-Syscall-Sequenz kommen.
Direkte Syscall-Beispiele
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)
}MITRE ATT&CK-Mappings
Last verified: 2026-05-20