VTL Secure Calls — NT ↔ Secure Kernel Interface
Last updated: 2026-04-10
Author of primary source: Connor McGarr
Related: Mitigations, Architecture
Tags:vtl,vbs,hvci,hyper-v,secure-kernel,secure-calls,kernel-mode,internals
Summary
“Secure calls” are the mechanism by which the NT kernel (VTL 0) requests services from the Secure Kernel (VTL 1). They underlie key Windows security features: HVCI, Credential Guard, Virtual TPM, Windows Hello. Understanding this interface is essential for:
- HVCI bypass research
- Kernel shadow stack analysis (VslAllocateKernelShadowStack is a secure call)
- Trustlet/IUM process research
- VBS/VSM security model analysis
Key tool: Connor McGarr’s SkBridge — allows issuing arbitrary secure calls from kernel mode; Vtl1Mon — ETW-based VTL 1 entry/exit monitoring.
Architecture Overview
VTL 0 (NT kernel)
nt!VslpEnterIumSecureMode()
→ nt!HvlSwitchToVsmVtl1()
→ vmcall (HvCallVtlCall = 0x11)
→ [VM Exit to Hyper-V]
→ HandleVmCall
→ HvCallVtlCall handler
→ LoadVTL1 VMCS
→ vmresume
→ [VTL 1: securekernel!IumInvokeSecureService]
→ service handled
→ securekernel!SkpPrepareForNormalCall
→ vmcall (HvCallVtlReturn = 0x12)
→ [VM Exit to Hyper-V]
→ Restore VTL 0 VMCS
→ vmresume
→ [VTL 0 resumes after vmcall instruction]
Three components: NT kernel, Hyper-V (VMM), Secure Kernel. Hyper-V brokers the VTL transition — SK does NOT live “in the hypervisor”.
Hypercall Mechanics
A secure call is implemented as vmcall with hypercall code HvCallVtlCall = 0x11:
// NT-side stub: nt!HvlpVsmVtlCallVa
// Issues: vmcall with HvCallVtlCall (0x11) in RCX[15:0]
// Hyper-V VM exit handler dispatches to HvCallVtlCall handler:
// gs:[360h] → current partition
// gs:[368h] → current virtual processor (VP)
// VP+0x3C0 → VTL state array (per-VTL state)
// VTL state+0x14 → current VTL index
On vmcall, Hyper-V:
- Captures VM exit reason =
VMX_EXIT_REASON_EXECUTE_VMCALL - Identifies hypercall code from VMCS guest RAX
- Routes to
HvCallVtlCallhandler - Increments VTL 0 RIP to next instruction (so return doesn’t re-issue vmcall)
- Loads VTL 1 VMCS (
vmptrld VTL1_VMCS_ptr) - Issues
vmresume→ processor starts executing in VTL 1 at VTL 1’s saved RIP
VTL 1’s saved RIP is always securekernel!SkpReturnFromNormalMode — a carefully positioned trampoline that handles any VM entry into VTL 1.
Secure Call Return Path
When the Secure Kernel finishes servicing a secure call:
// securekernel!IumInvokeSecureService →
// → securekernel!SkpPrepareForNormalCall
// → securekernel!SkpPrepareForReturnToNormalMode
// → call securekernel!ShvlpVtlReturn ← pushes return addr onto stack
// Next instruction after call = securekernel!SkpReturnFromNormalMode
// → ShvlpVtlReturn issues vmcall (HvCallVtlReturn = 0x12)
// → VM exit → Hyper-V fixes up VTL 1 RIP to next instruction (= ret)
// → Loads VTL 0 VMCS → vmresume → VTL 0 continues
//
// Next entry into VTL 1: executes 'ret' → pops stack → lands at SkpReturnFromNormalMode
The call (not jump) to ShvlpVtlReturn is intentional — it stages SkpReturnFromNormalMode on the stack so that the VTL 1 “always-ready” trampoline is correctly positioned for the next VTL 1 entry.
NT-Side Interface: VslpEnterIumSecureMode
NTSTATUS
nt!VslpEnterIumSecureMode(
_In_ UINT8 OperationType,
_In_ ULONG64 SecureCallCode,
_In_ ULONG64 OptionalSecureThreadCookie,
_Inout_ SECURE_CALL_ARGS *SecureCallArgs
);
OperationType values:
2= Standard secure service request (most common)3= Flush translation buffers (direct path, may generate ETW event)
typedef struct _SECURE_CALL_ARGS {
SECURE_CALL_RESERVED_FIELD Reserved; // contains OperationType + SecureCallCode + cookie
ULONGLONG Field1; // up to 12 input/output fields
ULONGLONG Field2;
...
ULONGLONG Field12;
} SECURE_CALL_ARGS; // 0x68 bytes total
Valid secure call codes: Enumerated in nt!_SKSERVICE symbol. Providing an invalid code triggers a bugcheck.
Secure Threads and Thread Cookies
Some secure calls are dispatched on a specific secure thread (a thread running in VTL 1, usually owned by a trustlet):
// Find secure thread cookies in current session:
lkd> dx -g @$cursession.Processes
.Where(p => p.Threads.Any(t => t.KernelObject.Tcb.SecureThreadCookie != 0))
.Last().Threads
.Where(t => t.KernelObject.Tcb.SecureThreadCookie != 0)
.Select(t => new { Process, TID, SecureThreadCookie })
// Example: NgcIso.exe (Windows Hello) with cookie 0x15
OptionalSecureThreadCookie routes the secure call to be handled by the thread identified by that cookie in VTL 1.
Parameter Passing: MDL Encapsulation
Many secure calls pass parameters as MDLs (Memory Descriptor Lists):
- VTL 0 allocates parameter data, creates MDL locking it into physical memory
- MDL virtual address + PFN (physical page number) passed to secure call
- VTL 1 creates a second MDL validating the input MDL’s physical pages
- VTL 1 maps the VTL 0 MDL into VTL 1 virtual address space via
MdlMappedSystemVa - VTL 1 processes parameter via mapped VA
- Output written back through same MDL or output fields of
SECURE_CALL_ARGS
Security Implications
Attack Surface on Secure Calls
- Invalid secure call code → bugcheck (no information disclosure, but DoS)
- Malformed MDL → potential VTL 1 kernel bugs (if SK validation is incomplete)
- Secure thread cookies are handles — handle table exhaustion may apply
SkBridgeenables fuzzing the SC interface with arbitrary parameters
HVCI and Secure Calls
The VslAllocateKernelShadowStack secure call (from KiCreateKernelShadowStack) is the mechanism by which the NT kernel gets shadow stack pages. The Secure Kernel allocates these pages in VTL 1-protected memory, then maps them back to VTL 0 as read-only + shadow-stack-writable only. Any HVCI bypass that wants to corrupt shadow stacks must either:
- Compromise the secure call interface itself, OR
- Find a way to write to VTL 1-protected physical pages from VTL 0
Hyper-V Internals Key Offsets (Windows 24H2)
| Location | Content |
|---|---|
gs:[0h] | Self-pointer (Hyper-V per-CPU data) |
gs:[360h] | Current partition |
gs:[368h] | Current virtual processor (VP) |
gs:[2C680h] | Current VMCS virtual address (enlightened VMCS) |
VP + 0x3C0 | VTL state array |
VTL_state + 0x14 | Current VTL index |
Partition + 0x1B0 | Privilege mask (for hypercall authorization check) |
Offsets change between builds — verify with windbgx -z C:\Windows\system32\hvix64.exe.
References
- Connor McGarr — “Windows Internals: Secure Calls — The Bridge Between NT and Secure Kernel” — 2025-09-06
- Connor McGarr — SkBridge tool
- Connor McGarr — Vtl1Mon tool
- Quarkslab — “A Virtual Journey from Hardware Virtualization to Hyper-V’s Virtual Trust Levels”
- Saar Amar — “First Steps in Hyper-V Research” — MSRC blog 2018
- Windows Internals 7th Edition Part 2 — SECURE_CALL_ARGS structure documentation
