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

NtReadFile

Lit des octets depuis un fichier, périphérique, pipe nommé ou section mappée vers un tampon utilisateur — primitive noyau derrière ReadFile.

Prototype

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

Arguments

NameTypeDirDescription
FileHandleHANDLEinHandle obtenu via NtCreateFile/NtOpenFile avec un accès FILE_READ_DATA ou GENERIC_READ.
EventHANDLEinHandle d'événement optionnel signalé à la complétion asynchrone. NULL pour de l'I/O synchrone.
ApcRoutinePIO_APC_ROUTINEinRoutine APC user-mode optionnelle mise en file à la complétion. NULL pour les appels synchrones.
ApcContextPVOIDinPointeur de contexte passé à ApcRoutine. Habituellement NULL.
IoStatusBlockPIO_STATUS_BLOCKoutReçoit le NTSTATUS final et le champ Information indiquant les octets effectivement lus.
BufferPVOIDoutTampon user-mode recevant les données. Doit faire au moins Length octets.
LengthULONGinNombre d'octets à lire. Les lectures partielles sont normales en fin de fichier ou sur les pipes.
ByteOffsetPLARGE_INTEGERinOffset optionnel dans le fichier. NULL utilise le pointeur courant (fichier synchrone).
KeyPULONGinClé de verrou optionnelle. Presque toujours NULL hors verrouillage par plage d'octets.

IDs de syscalls par version de Windows

Version 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

Module noyau

ntoskrnl.exeNtReadFile

APIs liées

ReadFileReadFileExNtWriteFileNtCreateFileNtOpenFileNtQueryInformationFileZwReadFile

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

Notes non documentées

NtReadFile suit la forme NT classique à 9 arguments (paire Event/APC, IO_STATUS_BLOCK, offset, clé de verrou) réutilisée par NtWriteFile, NtDeviceIoControlFile et NtFsControlFile — savoir en appeler un, c'est savoir appeler les quatre. Le SSN `0x6` est figé depuis Windows 7 car l'appel est tout en bas de la table. Le détail critique côté appelant : pour un fichier ouvert *sans* FILE_SYNCHRONOUS_IO_NONALERT, il faut fournir un ByteOffset valide ou un handle Event sur lequel attendre — `ReadFile` user-mode masque ce détail en émulant le synchrone quand ni FILE_FLAG_OVERLAPPED ni OVERLAPPED n'est fourni.

Usage courant par les malwares

Trois patrons offensifs majeurs. (1) **Vol de jeton / lecture LSASS** : ouvrir un handle sur une section adossée à la mémoire de lsass.exe ou sur un minidump capturé, puis NtReadFile pour extraire les credentials sans jamais toucher MiniDumpWriteDump ni NtReadVirtualMemory. (2) **Exfil de configs / presse-papier** : ouvrir `\Device\KsecDD`, `\??\C:\Users\<u>\AppData\Roaming\Mozilla\Firefox\Profiles\*\logins.json`, les SQLite de cookies des navigateurs, les PST/OST Outlook, puis lire par blocs. (3) **Relecture du fichier backing du loader réflectif** pour staging du stage 2 sans déclencher les heuristiques OS qui guettent NtMapViewOfSection d'images exécutables. Combiné à NtCreateFile sur des chemins NT (`\??\GLOBALROOT\Device\...`), la lecture contourne les minifilters indexés sur les chemins DOS.

Opportunités de détection

NtReadFile n'est jamais anormal en soi — des milliards d'appels par hôte et par jour. Le signal vit dans (a) *quel fichier*, (b) *quel processus l'a ouvert*, (c) la chaîne IRP. L'ETW Microsoft-Windows-Kernel-File (`{EDD08927-9CC4-4E65-B970-C2560FB5C289}`) émet des évènements Read avec offset et longueur. Les callbacks minifilter EDR (IRP_MJ_READ pre/post) voient chaque lecture indépendamment du hook user-mode, donc le syscall direct ne cache rien. Chasses à haute valeur : tout processus non-MsMpEng/SearchProtocolHost lisant `*\Mozilla\*\logins.json`, `*\Chromium\User Data\*\Login Data`, `*\Microsoft\Credentials\*`, `*\Microsoft\Protect\*` (masterkeys DPAPI), ou `\Device\HarddiskVolumeShadowCopy*` (extraction NTDS.dit via VSC).

Exemples de syscalls directs

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
}

Mappings MITRE ATT&CK

Last verified: 2026-05-20