> Windows Syscalls
ntoskrnl.exeT1003.001T1555.003T1005

NtReadFile

Lee bytes desde un archivo, dispositivo, canal con nombre o sección mapeada hacia un búfer de usuario — primitiva del kernel detrás de ReadFile.

Prototipo

NTSTATUS NtReadFile(
  HANDLE           FileHandle,
  HANDLE           Event,
  PIO_APC_ROUTINE  ApcRoutine,
  PVOID            ApcContext,
  PIO_STATUS_BLOCK IoStatusBlock,
  PVOID            Buffer,
  ULONG            Length,
  PLARGE_INTEGER   ByteOffset,
  PULONG           Key
);

Argumentos

NameTypeDirDescription
FileHandleHANDLEinHandle obtenido de NtCreateFile/NtOpenFile con acceso FILE_READ_DATA o GENERIC_READ.
EventHANDLEinHandle de evento opcional señalado al completar I/O asíncrona. NULL para I/O síncrona.
ApcRoutinePIO_APC_ROUTINEinRutina APC en modo usuario opcional encolada al completar. NULL para llamadas síncronas.
ApcContextPVOIDinPuntero de contexto pasado a ApcRoutine. Normalmente NULL.
IoStatusBlockPIO_STATUS_BLOCKoutRecibe el NTSTATUS final y el campo Information con los bytes leídos.
BufferPVOIDoutBúfer en modo usuario que recibe los datos. Debe tener al menos Length bytes.
LengthULONGinNúmero de bytes a leer. Lecturas cortas son normales en EOF o en pipes.
ByteOffsetPLARGE_INTEGERinDesplazamiento opcional en el archivo. NULL usa el puntero actual (archivo síncrono).
KeyPULONGinClave de bloqueo opcional. Casi siempre NULL fuera de bloqueos por rango.

IDs de syscalls por versión de Windows

Versión de WindowsID de syscallBuild
Win10 15070x6win10-1507
Win10 16070x6win10-1607
Win10 17030x6win10-1703
Win10 17090x6win10-1709
Win10 18030x6win10-1803
Win10 18090x6win10-1809
Win10 19030x6win10-1903
Win10 19090x6win10-1909
Win10 20040x6win10-2004
Win10 20H20x6win10-20h2
Win10 21H10x6win10-21h1
Win10 21H20x6win10-21h2
Win10 22H20x6win10-22h2
Win11 21H20x6win11-21h2
Win11 22H20x6win11-22h2
Win11 23H20x6win11-23h2
Win11 24H20x6win11-24h2
Server 20160x6winserver-2016
Server 20190x6winserver-2019
Server 20220x6winserver-2022
Server 20250x6winserver-2025

Módulo del kernel

ntoskrnl.exeNtReadFile

APIs relacionadas

ReadFileReadFileExNtWriteFileNtCreateFileNtOpenFileNtQueryInformationFileZwReadFile

Stub del syscall

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

NtReadFile sigue la forma clásica NT de 9 argumentos (par Event/APC, IO_STATUS_BLOCK, offset, lock key) reutilizada por NtWriteFile, NtDeviceIoControlFile y NtFsControlFile — saber invocar uno equivale a saber invocar los cuatro. El SSN `0x6` está fijo desde Windows 7 al estar en la base de la tabla. Detalle clave del lado del invocador: para un archivo abierto *sin* FILE_SYNCHRONOUS_IO_NONALERT, hay que pasar un ByteOffset válido o suministrar un Event y esperarlo — `ReadFile` en modo usuario oculta esto emulando comportamiento síncrono cuando no se entrega FILE_FLAG_OVERLAPPED ni OVERLAPPED.

Uso común por malware

Tres patrones ofensivos principales. (1) **Robo de token / lectura de LSASS**: abrir un handle a una sección respaldada por la memoria de lsass.exe o a un minidump en disco, luego NtReadFile para extraer credenciales sin tocar MiniDumpWriteDump ni NtReadVirtualMemory. (2) **Exfil de configuraciones / portapapeles**: abrir `\Device\KsecDD`, `\??\C:\Users\<u>\AppData\Roaming\Mozilla\Firefox\Profiles\*\logins.json`, SQLite de cookies de navegador, PST/OST de Outlook, y leer en bloques. (3) **Relectura del propio archivo backing del loader reflectivo** para entregar la segunda etapa sin las heurísticas que vigilan NtMapViewOfSection de imágenes ejecutables. Junto a NtCreateFile sobre rutas NT (`\??\GLOBALROOT\Device\...`), la lectura esquiva minifilters anclados a rutas DOS.

Oportunidades de detección

NtReadFile no es anómalo por sí solo — miles de millones de llamadas por host al día. La señal está en (a) *qué archivo*, (b) *quién lo abrió*, (c) la cadena IRP. ETW Microsoft-Windows-Kernel-File (`{EDD08927-9CC4-4E65-B970-C2560FB5C289}`) emite eventos Read con offset y longitud. Los callbacks minifilter de EDR (IRP_MJ_READ pre/post) ven cada lectura sin importar hooks de usuario, así que las syscalls directas no ocultan lecturas. Casos de caza valiosos: cualquier proceso no-MsMpEng/SearchProtocolHost leyendo `*\Mozilla\*\logins.json`, `*\Chromium\User Data\*\Login Data`, `*\Microsoft\Credentials\*`, `*\Microsoft\Protect\*` (masterkeys DPAPI) o `\Device\HarddiskVolumeShadowCopy*` (extracción de NTDS.dit por VSC).

Ejemplos de syscalls directos

cRead browser logins file synchronously

// Assumes hFile was opened with FILE_SYNCHRONOUS_IO_NONALERT and FILE_READ_DATA.
IO_STATUS_BLOCK iosb = {0};
BYTE  buffer[0x4000];
LARGE_INTEGER off = {0};                  // start of file

NTSTATUS s = NtReadFile(
    hFile,
    NULL,                                 // Event
    NULL, NULL,                           // APC routine + context
    &iosb,
    buffer,
    sizeof(buffer),
    &off,
    NULL);                                // Key

if (NT_SUCCESS(s)) {
    SIZE_T got = iosb.Information;
    // got bytes copied into buffer
}

asmDirect stub (SSN 0x6) — stack args 5..9

; NtReadFile has 9 args. RCX,RDX,R8,R9 hold the first four; the rest
; sit on the stack after 32-byte home space — caller is responsible.
NtReadFile PROC
    mov  r10, rcx
    mov  eax, 06h
    syscall
    ret
NtReadFile ENDP

rustAsync read with event wait

// Cargo: ntapi = "0.4", winapi = { version = "0.3", features = ["synchapi", "ntdef"] }
use ntapi::ntioapi::{NtReadFile, IO_STATUS_BLOCK};
use winapi::um::synchapi::WaitForSingleObject;
use winapi::shared::ntdef::{HANDLE, LARGE_INTEGER};

pub unsafe fn read_async(h_file: HANDLE, h_event: HANDLE, buf: &mut [u8], off: i64) -> i32 {
    let mut iosb: IO_STATUS_BLOCK = core::mem::zeroed();
    let mut li: LARGE_INTEGER = core::mem::zeroed();
    *li.QuadPart_mut() = off;
    let s = NtReadFile(
        h_file, h_event,
        None, core::ptr::null_mut(),
        &mut iosb,
        buf.as_mut_ptr() as *mut _,
        buf.len() as u32,
        &mut li,
        core::ptr::null_mut(),
    );
    if s == 0x103 /* STATUS_PENDING */ {
        WaitForSingleObject(h_event, u32::MAX);
    }
    s
}

Mapeos MITRE ATT&CK

Last verified: 2026-05-20