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
| Name | Type | Dir | Description |
|---|---|---|---|
| ServiceClass | VDMSERVICECLASS | in | Enum zur Auswahl der VDM-Operation (VdmStartExecution, VdmQueueInterrupt, VdmDelayInterrupt, VdmInitialize, …). |
| ServiceData | PVOID | in/out | Service-spezifischer Ein-/Ausgabepuffer. Layout hängt vollständig von ServiceClass ab; für Nicht-NTVDM-Aufrufer opak. |
Syscall-IDs pro Windows-Version
| Windows-Version | Syscall-ID | Build |
|---|---|---|
| Win10 1507 | 0x1B2 | win10-1507 |
| Win10 1607 | 0x1BB | win10-1607 |
| Win10 1703 | 0x1C1 | win10-1703 |
| Win10 1709 | 0x1C5 | win10-1709 |
| Win10 1803 | 0x1C7 | win10-1803 |
| Win10 1809 | 0x1C8 | win10-1809 |
| Win10 1903 | 0x1C9 | win10-1903 |
| Win10 1909 | 0x1C9 | win10-1909 |
| Win10 2004 | 0x1CF | win10-2004 |
| Win10 20H2 | 0x1CF | win10-20h2 |
| Win10 21H1 | 0x1CF | win10-21h1 |
| Win10 21H2 | 0x1D1 | win10-21h2 |
| Win10 22H2 | 0x1D1 | win10-22h2 |
| Win11 21H2 | 0x1DB | win11-21h2 |
| Win11 22H2 | 0x1DF | win11-22h2 |
| Win11 23H2 | 0x1DF | win11-23h2 |
| Win11 24H2 | 0x1E2 | win11-24h2 |
| Server 2016 | 0x1BB | winserver-2016 |
| Server 2019 | 0x1C8 | winserver-2019 |
| Server 2022 | 0x1D7 | winserver-2022 |
| Server 2025 | 0x1E2 | winserver-2025 |
Kernel-Modul
Verwandte APIs
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.
Erkennungsmö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 ENDPcHistorical 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 x64rustProbe 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