> Windows Syscalls
ntoskrnl.exeT1068T1562.001T1014

NtDeviceIoControlFile

Envía un IOCTL a un driver del kernel — entrada en modo usuario para todo abuso de primitivas BYOVD.

Prototipo

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
);

Argumentos

NameTypeDirDescription
FileHandleHANDLEinHandle al objeto device (p. ej. abierto vía \Device\RTCore64 o \\.\Mimidrv).
EventHANDLEinEvento opcional señalado al completar asincrónicamente. NULL para síncrono.
ApcRoutinePIO_APC_ROUTINEinRutina APC en modo usuario opcional encolada al completar. Normalmente NULL.
ApcContextPVOIDinValor de contexto pasado a ApcRoutine. NULL si no se usa APC.
IoStatusBlockPIO_STATUS_BLOCKoutRecibe el NTSTATUS final e Information = bytes escritos en OutputBuffer.
IoControlCodeULONGinIdentificador IOCTL codificado por CTL_CODE. Específico del driver (p. ej. 0x80002048 en RTCore64).
InputBufferPVOIDinBúfer de entrada. Su estructura la dicta exclusivamente el driver objetivo.
InputBufferLengthULONGinTamaño de InputBuffer en bytes.
OutputBufferPVOIDoutBúfer de salida que recibe datos retornados por el driver. Puede ser NULL.
OutputBufferLengthULONGinTamaño de OutputBuffer en bytes. Cero si OutputBuffer es NULL.

IDs de syscalls por versión de Windows

Versión 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

Módulo del kernel

ntoskrnl.exeNtDeviceIoControlFile

APIs relacionadas

DeviceIoControlNtFsControlFileNtCreateFileNtLoadDriverStartServiceCreateServiceW

Stub del 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

Notas no documentadas

Misma forma I/O de 9 argumentos que NtReadFile/NtWriteFile/NtFsControlFile — sólo cambia el significado del slot intermedio (código IOCTL en lugar de offset). Despachada en el kernel a `IopXxxControlFile`, que construye un IRP con código mayor `IRP_MJ_DEVICE_CONTROL` y lo enruta a la dispatch table del device. El propio valor IOCTL codifica tipo de device, función, método de transferencia (METHOD_BUFFERED, METHOD_IN_DIRECT, METHOD_OUT_DIRECT, METHOD_NEITHER) y acceso requerido — los drivers METHOD_NEITHER suelen confiar en punteros de usuario sin sondear, justamente la clase de bug que sostiene el BYOVD.

Uso común por malware

Primitiva de comunicación con drivers del kernel abusada por **BYOVD (Bring Your Own Vulnerable Driver)**. El patrón es invariante: soltar un driver vulnerable pero firmado por Microsoft (RTCore64.sys / Micro-Star MSI Afterburner, gdrv.sys / Gigabyte, mhyprot2.sys / Genshin Impact, dbutil_2_3.sys / Dell, Procexp152.sys), llamar a `OpenSCManager`+`CreateService`+`StartService` (o `NtLoadDriver`), abrir `\\.\<DeviceName>` y emitir IOCTLs que exponen R/W arbitrario al kernel (p. ej. RTCore64 0x80002048 = primitiva MmMapIoSpace, dbutil_2_3 0x9B0C1EC8 = R/W físico arbitrario). Con esa primitiva el malware parchea `_EPROCESS.Token` para SYSTEM, vacía los arrays de callbacks de EDR (`PsSetCreateProcessNotifyRoutineEx`, `CmRegisterCallbackEx`, `ObRegisterCallbacks`) o desengancha ntoskrnl. También se usa como señal **kill switch AMSI/ETW** en HVNC y herramientas tipo EDRSilencer, y como vía IOCTL al `mimidrv.sys` incluido de Mimikatz para manipulación de tokens.

Oportunidades de detección

Los proveedores ETW Microsoft-Windows-Kernel-PnP y Microsoft-Windows-Kernel-Audit-API-Calls registran cargas de drivers — combinar con Sysmon Event ID 6 (Driver loaded) que expone firma, firmante, hash y ruta. La Vulnerable Driver Blocklist de Microsoft (`HVCI`/Code Integrity `SiPolicy.p7b`, activa por defecto desde Win11 22H2) bloquea por hash las familias BYOVD publicadas — sólo si está habilitada. Los sensores EDR registran `PsSetLoadImageNotifyRoutine` para imágenes de driver y `IoRegisterFsRegistrationChange` para filtros FS; ambos disparan pre-ejecución. En el lado IOCTL, el ETW de kernel es pobre; las señales más fiables son (1) image-load de un hash de driver malo conocido, (2) apertura de handle a un device name de driver no usado por producto legítimo en el host, (3) tokens de procesos cuyo puntero `Token` repentinamente iguala al de lsass.exe (detección de token swap). LOLDrivers.io es la lista de hashes de referencia.

Ejemplos de syscalls directos

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)
}

Mapeos MITRE ATT&CK

Last verified: 2026-05-20