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
| Name | Type | Dir | Description |
|---|---|---|---|
| FileHandle | HANDLE | in | Handle obtenu via NtCreateFile/NtOpenFile avec un accès FILE_READ_DATA ou GENERIC_READ. |
| Event | HANDLE | in | Handle d'événement optionnel signalé à la complétion asynchrone. NULL pour de l'I/O synchrone. |
| ApcRoutine | PIO_APC_ROUTINE | in | Routine APC user-mode optionnelle mise en file à la complétion. NULL pour les appels synchrones. |
| ApcContext | PVOID | in | Pointeur de contexte passé à ApcRoutine. Habituellement NULL. |
| IoStatusBlock | PIO_STATUS_BLOCK | out | Reçoit le NTSTATUS final et le champ Information indiquant les octets effectivement lus. |
| Buffer | PVOID | out | Tampon user-mode recevant les données. Doit faire au moins Length octets. |
| Length | ULONG | in | Nombre d'octets à lire. Les lectures partielles sont normales en fin de fichier ou sur les pipes. |
| ByteOffset | PLARGE_INTEGER | in | Offset optionnel dans le fichier. NULL utilise le pointeur courant (fichier synchrone). |
| Key | PULONG | in | Clé de verrou optionnelle. Presque toujours NULL hors verrouillage par plage d'octets. |
IDs de syscalls par version de Windows
| Version de Windows | ID de syscall | Build |
|---|---|---|
| Win10 1507 | 0x6 | win10-1507 |
| Win10 1607 | 0x6 | win10-1607 |
| Win10 1703 | 0x6 | win10-1703 |
| Win10 1709 | 0x6 | win10-1709 |
| Win10 1803 | 0x6 | win10-1803 |
| Win10 1809 | 0x6 | win10-1809 |
| Win10 1903 | 0x6 | win10-1903 |
| Win10 1909 | 0x6 | win10-1909 |
| Win10 2004 | 0x6 | win10-2004 |
| Win10 20H2 | 0x6 | win10-20h2 |
| Win10 21H1 | 0x6 | win10-21h1 |
| Win10 21H2 | 0x6 | win10-21h2 |
| Win10 22H2 | 0x6 | win10-22h2 |
| Win11 21H2 | 0x6 | win11-21h2 |
| Win11 22H2 | 0x6 | win11-22h2 |
| Win11 23H2 | 0x6 | win11-23h2 |
| Win11 24H2 | 0x6 | win11-24h2 |
| Server 2016 | 0x6 | winserver-2016 |
| Server 2019 | 0x6 | winserver-2019 |
| Server 2022 | 0x6 | winserver-2022 |
| Server 2025 | 0x6 | winserver-2025 |
Module noyau
APIs liées
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 ENDPrustAsync 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