> Windows Syscalls
ntoskrnl.exeT1068T1106

NtVdmControl

Steuerungs-Einstiegspunkt der NT Virtual DOS Machine — 16-Bit-DOS/Windows-Unterstützung, auf x64 außer Betrieb.

Prototyp

NTSTATUS NtVdmControl(
  VDMSERVICECLASS ServiceClass,
  PVOID           ServiceData
);

Argumente

NameTypeDirDescription
ServiceClassVDMSERVICECLASSinEnum zur Auswahl der VDM-Operation (VdmStartExecution, VdmQueueInterrupt, VdmDelayInterrupt, VdmInitialize, …).
ServiceDataPVOIDin/outService-spezifischer Ein-/Ausgabepuffer. Layout hängt vollständig von ServiceClass ab; für Nicht-NTVDM-Aufrufer opak.

Syscall-IDs pro Windows-Version

Windows-VersionSyscall-IDBuild
Win10 15070x1B2win10-1507
Win10 16070x1BBwin10-1607
Win10 17030x1C1win10-1703
Win10 17090x1C5win10-1709
Win10 18030x1C7win10-1803
Win10 18090x1C8win10-1809
Win10 19030x1C9win10-1903
Win10 19090x1C9win10-1909
Win10 20040x1CFwin10-2004
Win10 20H20x1CFwin10-20h2
Win10 21H10x1CFwin10-21h1
Win10 21H20x1D1win10-21h2
Win10 22H20x1D1win10-22h2
Win11 21H20x1DBwin11-21h2
Win11 22H20x1DFwin11-22h2
Win11 23H20x1DFwin11-23h2
Win11 24H20x1E2win11-24h2
Server 20160x1BBwinserver-2016
Server 20190x1C8winserver-2019
Server 20220x1D7winserver-2022
Server 20250x1E2winserver-2025

Kernel-Modul

ntoskrnl.exeNtVdmControl

Verwandte APIs

NtCreateProcessNtQueueApcThreadNtRaiseExceptionWow64GetThreadContext

Syscall-Stub

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

NtVdmControl ist das Kernel-Backend der NT Virtual DOS Machine (NTVDM) — des Windows-NT-Subsystems, das 16-Bit-MS-DOS- und Win16-Binärdateien auf 32-Bit-Windows via ntvdm.exe ausführte. NTVDM wurde *vollständig entfernt* in 64-Bit-Editionen von Windows, weil Long-Mode-CPUs den von NTVDM benötigten v8086-Mode nicht ausführen können. Der Syscall-Stub bleibt dennoch in 64-Bit-ntoskrnl.exe erhalten und dispatcht in einen Stub, der für jede ServiceClass auf x64 unbedingt STATUS_NOT_IMPLEMENTED zurückgibt. Auf 32-Bit-Windows 10/11 (und auf WoW64-gehostetem ntvdm.exe bis hin zu 32-Bit-Editionen) bedient der Aufruf Interrupt-Queueing, BIOS-Data-Area-Wartung und v8086-Zustandsübergänge für den abgefangenen DOS-Prozess. Es gibt keinen öffentlichen Win32-Wrapper; alles, was ihn nutzt, ist intern in ntvdm.exe und im WoW16/NTVDM-Subsystem.

Häufige Malware-Nutzung

Reines historisches Interesse auf modernem x64. Auf 32-Bit-XP und 32-Bit-Windows 7 war NTVDM ein wiederkehrender Trojaner-Vektor — 16-Bit-DOS-Komponenten von Mass-Mailer-Familien (Bagle-, Sober-, Bugbear-Varianten) lieferten einen kleinen DOS-Stub, der beim Start ntvdm.exe startete, das wiederum NtVdmControl in seiner Initialisierung aufrief. Der eigentliche Missbrauchsvektor war der 16-Bit-Code, nicht der Syscall selbst, aber der Syscall ist der Engpass. NTVDM war außerdem Quelle mehrerer Privilegieneskalationen: CVE-2010-0232 (Tavis Ormandys #GP-Handler-Bug, ausgenutzt auf 32-Bit-XP/Vista/7) und CVE-2018-8897/8453 (Kernel-Bugs erreichbar über Legacy-Pfade). Auf 64-Bit-Windows ist der Syscall als sinnvolle Primitive unerreichbar und es gibt keine In-the-Wild-Missbrauchsmeldungen.

Erkennungs­möglichkeiten

Auf 64-Bit-Windows ist *jeder* Aufruf von NtVdmControl anomal — der legitime Aufrufer (ntvdm.exe) existiert nicht und der Syscall liefert unbedingt STATUS_NOT_IMPLEMENTED. Der ETW-Provider Microsoft-Windows-Kernel-Audit-API-Calls und EDR-Syscall-Tracing-Hooks können Invocations direkt markieren; die False-Positive-Rate auf einem modernen Windows-11-Endpoint ist praktisch null. Auf 32-Bit-Hosts Detektion auf Prozesse außerhalb von %SystemRoot%\System32\ntvdm.exe begrenzen und ntvdm.exe-Parent-Chains beobachten, die nicht aus einem Benutzer-Doppelklick auf eine .com/.exe-MZ-Only-Binärdatei stammen. Sysmon Event ID 1 (Prozesserzeugung) mit Parent ntvdm.exe + Child einer ungewöhnlichen DOS-Binärdatei ist die praktische Jagd auf Legacy-Systemen.

Direkte Syscall-Beispiele

asmx64 direct stub (returns STATUS_NOT_IMPLEMENTED)

; Direct syscall stub for NtVdmControl (SSN 0x1E2, Win11 24H2)
; On x64 always returns 0xC0000002 STATUS_NOT_IMPLEMENTED.
NtVdmControl PROC
    mov  r10, rcx          ; syscall convention
    mov  eax, 1E2h         ; SSN
    syscall
    ret
NtVdmControl ENDP

cHistorical 32-bit XP-era 16-bit Trojan launch (defunct)

// Historical sketch — does not work on any 64-bit Windows.
// 16-bit MZ-only DOS components of early-2000s mass-mailers (Bagle, Sober)
// relied on the Windows shell auto-launching ntvdm.exe when the user
// double-clicked the 16-bit dropper. Inside, ntvdm.exe issued a long
// sequence of NtVdmControl calls to set up interrupt queues and the
// BIOS data area for the trapped DOS task.
//
// On modern x64 Windows the shell refuses to run 16-bit MZ binaries
// ("unsupported 16-bit application") and this whole vector is dead.
//
// Reference: VX-Underground archives, Symantec write-ups on Bagle.A
// and Sober.D from 2003-2004.
typedef NTSTATUS (NTAPI *PFN_NtVdmControl)(ULONG ServiceClass, PVOID ServiceData);
HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
PFN_NtVdmControl p = (PFN_NtVdmControl)GetProcAddress(hNtdll, "NtVdmControl");
NTSTATUS s = p(/*VdmInitialize*/ 0, NULL); // STATUS_NOT_IMPLEMENTED on x64

rustProbe to confirm NTVDM absence on x64

// Detection helper: on any x64 Windows this returns STATUS_NOT_IMPLEMENTED.
// Useful inside an EDR self-test or a defensive sanity check.
use std::ffi::CString;
use std::ptr;

type NtVdmControlFn = unsafe extern "system" fn(u32, *mut core::ffi::c_void) -> i32;

fn ntvdm_is_dead() -> bool {
    unsafe {
        let ntdll = libloading::Library::new("ntdll.dll").unwrap();
        let f: libloading::Symbol<NtVdmControlFn> =
            ntdll.get(b"NtVdmControl").unwrap();
        let status = f(0, ptr::null_mut());
        status == 0xC0000002u32 as i32 // STATUS_NOT_IMPLEMENTED
    }
}

MITRE ATT&CK-Mappings

Last verified: 2026-05-20