> Windows Syscalls
ntoskrnl.exeT1068T1562.001T1014

NtDeviceIoControlFile

Envoie un IOCTL à un driver noyau — porte d'entrée user-mode de tout abus de primitive BYOVD.

Prototype

NTSTATUS NtDeviceIoControlFile(
  HANDLE           FileHandle,
  HANDLE           Event,
  PIO_APC_ROUTINE  ApcRoutine,
  PVOID            ApcContext,
  PIO_STATUS_BLOCK IoStatusBlock,
  ULONG            IoControlCode,
  PVOID            InputBuffer,
  ULONG            InputBufferLength,
  PVOID            OutputBuffer,
  ULONG            OutputBufferLength
);

Arguments

NameTypeDirDescription
FileHandleHANDLEinHandle vers l'objet device (ex. ouvert via \Device\RTCore64 ou \\.\Mimidrv).
EventHANDLEinÉvènement optionnel signalé à la complétion asynchrone. NULL pour synchrone.
ApcRoutinePIO_APC_ROUTINEinRoutine APC user-mode optionnelle mise en file à la complétion. Habituellement NULL.
ApcContextPVOIDinValeur de contexte passée à ApcRoutine. NULL si pas d'APC.
IoStatusBlockPIO_STATUS_BLOCKoutReçoit le NTSTATUS final et Information = octets écrits dans OutputBuffer.
IoControlCodeULONGinIdentifiant IOCTL encodé par CTL_CODE. Spécifique au driver (ex. 0x80002048 dans RTCore64).
InputBufferPVOIDinTampon d'entrée. Sa structure est entièrement dictée par le driver cible.
InputBufferLengthULONGinTaille en octets d'InputBuffer.
OutputBufferPVOIDoutTampon de sortie recevant les données retournées par le driver. Peut être NULL.
OutputBufferLengthULONGinTaille en octets d'OutputBuffer. Zéro si OutputBuffer est NULL.

IDs de syscalls par version de Windows

Version de WindowsID de syscallBuild
Win10 15070x7win10-1507
Win10 16070x7win10-1607
Win10 17030x7win10-1703
Win10 17090x7win10-1709
Win10 18030x7win10-1803
Win10 18090x7win10-1809
Win10 19030x7win10-1903
Win10 19090x7win10-1909
Win10 20040x7win10-2004
Win10 20H20x7win10-20h2
Win10 21H10x7win10-21h1
Win10 21H20x7win10-21h2
Win10 22H20x7win10-22h2
Win11 21H20x7win11-21h2
Win11 22H20x7win11-22h2
Win11 23H20x7win11-23h2
Win11 24H20x7win11-24h2
Server 20160x7winserver-2016
Server 20190x7winserver-2019
Server 20220x7winserver-2022
Server 20250x7winserver-2025

Module noyau

ntoskrnl.exeNtDeviceIoControlFile

APIs liées

DeviceIoControlNtFsControlFileNtCreateFileNtLoadDriverStartServiceCreateServiceW

Stub du syscall

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

Notes non documentées

Même forme I/O à 9 arguments que NtReadFile/NtWriteFile/NtFsControlFile — seule la signification du slot médian change (code IOCTL au lieu d'un offset). Dispatché côté noyau vers `IopXxxControlFile`, qui bâtit un IRP de code majeur `IRP_MJ_DEVICE_CONTROL` et le route vers la dispatch table du device. La valeur IOCTL encode elle-même le type de device, la fonction, la méthode de transfert (METHOD_BUFFERED, METHOD_IN_DIRECT, METHOD_OUT_DIRECT, METHOD_NEITHER) et l'accès requis — les drivers METHOD_NEITHER font fréquemment confiance aux pointeurs user sans probing, ce qui est précisément la classe de bug rendant le BYOVD viable.

Usage courant par les malwares

La primitive de communication driver noyau exploitée par le **BYOVD (Bring Your Own Vulnerable Driver)**. Le schéma est invariant : déposer un driver vulnérable mais signé Microsoft (RTCore64.sys / Micro-Star MSI Afterburner, gdrv.sys / Gigabyte, mhyprot2.sys / Genshin Impact, dbutil_2_3.sys / Dell, Procexp152.sys), appeler `OpenSCManager`+`CreateService`+`StartService` (ou `NtLoadDriver`), ouvrir `\\.\<DeviceName>`, puis émettre des IOCTL qui exposent du R/W noyau arbitraire (ex. RTCore64 0x80002048 = primitive MmMapIoSpace, dbutil_2_3 0x9B0C1EC8 = R/W physique arbitraire). Avec cette primitive, le malware patche `_EPROCESS.Token` pour SYSTEM, vide les tableaux de callbacks EDR (`PsSetCreateProcessNotifyRoutineEx`, `CmRegisterCallbackEx`, `ObRegisterCallbacks`) ou déhooke ntoskrnl. Sert aussi de signal **kill switch AMSI/ETW** dans le HVNC et les outils type EDRSilencer, et de voie IOCTL vers le `mimidrv.sys` embarqué de Mimikatz pour la manipulation de tokens.

Opportunités de détection

Les fournisseurs ETW Microsoft-Windows-Kernel-PnP et Microsoft-Windows-Kernel-Audit-API-Calls journalisent les chargements de driver — coupler à Sysmon Event ID 6 (Driver loaded) qui expose signature, signataire, hash et chemin image. La Vulnerable Driver Blocklist Microsoft (`HVCI`/Code Integrity `SiPolicy.p7b`, activée par défaut depuis Win11 22H2) bloque par hash les familles BYOVD publiées — uniquement si activée. Les capteurs EDR enregistrent `PsSetLoadImageNotifyRoutine` pour les images de driver et `IoRegisterFsRegistrationChange` pour les filtres FS ; les deux se déclenchent pré-exécution. Côté IOCTL, l'ETW noyau est pauvre ; les signaux à plus forte fidélité sont (1) image-load d'un hash de driver connu malveillant, (2) ouverture de handle vers un device name de driver non utilisé par un produit légitime sur l'hôte, (3) jetons de processus dont le pointeur `Token` égale soudainement celui de lsass.exe (détection de token swap). LOLDrivers.io est la liste de hashes de référence.

Exemples de syscalls directs

cBYOVD: send arbitrary-R/W IOCTL to RTCore64

// Pattern: \\.\RTCore64 opened, send the IOCTL that maps physical memory.
#define RTCORE64_MEMORY_READ_IOCTL  0x80002048

typedef struct _RTCORE_PAYLOAD {
    UINT64 dst;
    UINT64 src;
    UINT32 read_size;
    // ... driver-specific layout
} RTCORE_PAYLOAD;

RTCORE_PAYLOAD p = {0};
p.src       = 0xFFFFF80000000000ULL;       // target kernel addr
p.read_size = sizeof(UINT64);

IO_STATUS_BLOCK iosb = {0};
UINT64 out = 0;
NTSTATUS s = NtDeviceIoControlFile(
    hRtcore, NULL, NULL, NULL, &iosb,
    RTCORE64_MEMORY_READ_IOCTL,
    &p,  sizeof(p),
    &out, sizeof(out));

asmDirect stub (SSN 0x7) — 10 args, stack heavy

; NtDeviceIoControlFile takes 10 args. Caller must reserve 32-byte home space
; PLUS room for args 5..10 on the stack before calling this stub.
NtDeviceIoControlFile PROC
    mov  r10, rcx
    mov  eax, 07h
    syscall
    ret
NtDeviceIoControlFile ENDP

rustOpen device + IOCTL helper

// Cargo: ntapi = "0.4", winapi = { version = "0.3", features = ["ntdef"] }
use ntapi::ntioapi::{NtDeviceIoControlFile, IO_STATUS_BLOCK};
use winapi::shared::ntdef::HANDLE;

pub unsafe fn ioctl(
    h_dev: HANDLE,
    code: u32,
    inp: &[u8],
    out: &mut [u8],
) -> (i32, usize) {
    let mut iosb: IO_STATUS_BLOCK = core::mem::zeroed();
    let s = NtDeviceIoControlFile(
        h_dev, core::ptr::null_mut(),
        None, core::ptr::null_mut(),
        &mut iosb,
        code,
        inp.as_ptr() as *mut _, inp.len() as u32,
        out.as_mut_ptr() as *mut _, out.len() as u32,
    );
    (s, *iosb.u.Status_mut() as usize)
}

Mappings MITRE ATT&CK

Last verified: 2026-05-20