> Windows Syscalls
ntoskrnl.exeT1083T1486T1106

NtNotifyChangeDirectoryFileEx

Notification étendue de changement de répertoire permettant de choisir la classe FILE_NOTIFY_INFORMATION retournée dans le buffer.

Prototype

NTSTATUS NtNotifyChangeDirectoryFileEx(
  HANDLE                          FileHandle,
  HANDLE                          Event,
  PIO_APC_ROUTINE                 ApcRoutine,
  PVOID                           ApcContext,
  PIO_STATUS_BLOCK                IoStatusBlock,
  PVOID                           Buffer,
  ULONG                           Length,
  ULONG                           CompletionFilter,
  BOOLEAN                         WatchTree,
  DIRECTORY_NOTIFY_INFORMATION_CLASS DirectoryNotifyInformationClass
);

Arguments

NameTypeDirDescription
FileHandleHANDLEinHandle vers un répertoire ouvert avec l'accès FILE_LIST_DIRECTORY.
EventHANDLEinÉvénement optionnel signalé à l'achèvement. NULL si un APC ou une attente alertable est utilisé.
ApcRoutinePIO_APC_ROUTINEinAPC usermode optionnel délivré à l'achèvement dans un thread alertable.
ApcContextPVOIDinContexte défini par l'appelant transmis à ApcRoutine.
IoStatusBlockPIO_STATUS_BLOCKoutReçoit le statut final et le nombre d'octets écrits dans Buffer.
BufferPVOIDoutBuffer aligné DWORD qui reçoit des enregistrements de la classe choisie par DirectoryNotifyInformationClass.
LengthULONGinTaille de Buffer en octets.
CompletionFilterULONGinMasque de bits FILE_NOTIFY_CHANGE_* identique à NtNotifyChangeDirectoryFile.
WatchTreeBOOLEANinTRUE pour surveiller récursivement tous les sous-répertoires ; FALSE pour le répertoire immédiat uniquement.
DirectoryNotifyInformationClassDIRECTORY_NOTIFY_INFORMATION_CLASSinClasse d'enregistrement : DirectoryNotifyInformation (legacy) ou DirectoryNotifyExtendedInformation (ajoute FileId, ParentFileId, timestamps).

IDs de syscalls par version de Windows

Version de WindowsID de syscallBuild
Win10 17090x10Fwin10-1709
Win10 18030x111win10-1803
Win10 18090x112win10-1809
Win10 19030x113win10-1903
Win10 19090x113win10-1909
Win10 20040x118win10-2004
Win10 20H20x118win10-20h2
Win10 21H10x118win10-21h1
Win10 21H20x119win10-21h2
Win10 22H20x119win10-22h2
Win11 21H20x11Fwin11-21h2
Win11 22H20x120win11-22h2
Win11 23H20x120win11-23h2
Win11 24H20x122win11-24h2
Server 20190x112winserver-2019
Server 20220x11Ewinserver-2022
Server 20250x122winserver-2025

Module noyau

ntoskrnl.exeNtNotifyChangeDirectoryFileEx

APIs liées

ReadDirectoryChangesExWReadDirectoryChangesWNtNotifyChangeDirectoryFileFindFirstChangeNotificationWNtQueryDirectoryFileNtQueryInformationFile

Stub du syscall

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

Ajouté dans Windows 10 1709 pour supporter la nouvelle API Win32 `ReadDirectoryChangesExW`. L'unique ajout par rapport à NtNotifyChangeDirectoryFile est l'énumération `DirectoryNotifyInformationClass` : passer `DirectoryNotifyExtendedInformation` produit des enregistrements `FILE_NOTIFY_EXTENDED_INFORMATION` qui incluent `FileId`, `ParentFileId`, `CreationTime`, `LastModificationTime`, `LastChangeTime`, `LastAccessTime`, `AllocatedLength`, `FileSize` et `FileAttributes` — c'est-à-dire tout ce que l'appelant devrait autrement stater pour chaque fichier. C'est un gain significatif sur de gros répertoires : un seul syscall renvoie ce qui aurait nécessité une notification suivie d'un NtQueryInformationFile par entrée changée. À cause du minimum 1709, du code ciblant des builds plus anciens doit conserver un fallback sur NtNotifyChangeDirectoryFile.

Usage courant par les malwares

Mêmes patterns opérationnels que NtNotifyChangeDirectoryFile (implants watchdog, C2 déclenché, détection de nouveaux volumes en cours de chiffrement), avec une amélioration spécifique : `DirectoryNotifyExtendedInformation` renvoie le **FileId** pour chaque changement sans nécessiter un second roundtrip. Les ransomwares s'en servent pour dédupliquer le travail sur les hardlinks et jonctions lors d'un chiffrement multi-threadé — le worker ignore un événement de changement si le FileId est déjà dans le set 'chiffré' en mémoire, évitant un double passage sur le même fichier NTFS. Les champs timestamp ajoutés servent aussi à certains infostealers pour filtrer les changements 'plus récents que mon dernier beacon', produisant des exfils différentiels sans passage d'énumération séparé.

Opportunités de détection

Surface de télémétrie identique à NtNotifyChangeDirectoryFile — Sysmon FileCreate (Event ID 11), le provider ETW Microsoft-Windows-Kernel-File et la télémétrie des handles EDR. Un seul différenciateur exploitable côté défense : les requêtes avec `DirectoryNotifyExtendedInformation` restent relativement rares dans le vieux logiciel tiers (la plupart est restée sur `ReadDirectoryChangesW` pour la compat ABI), donc un processus non-Explorer, non-Indexer, non-OneDrive qui demande spécifiquement la classe étendue sur un chemin sensible est un signal légèrement plus fort que la variante legacy. Utile pour calibrer des détecteurs d'anomalies ML qui consomment déjà des vecteurs de features par syscall.

Exemples de syscalls directs

asmx64 direct stub (Win11 24H2)

; Direct syscall stub for NtNotifyChangeDirectoryFileEx (SSN 0x122 on Win11 24H2)
NtNotifyChangeDirectoryFileEx PROC
    mov  r10, rcx          ; syscall convention
    mov  eax, 122h         ; SSN — varies per build
    syscall
    ret
NtNotifyChangeDirectoryFileEx ENDP

cExtended class — FileId without a second query

// Use ReadDirectoryChangesExW to receive FILE_NOTIFY_EXTENDED_INFORMATION.
// One syscall returns FileId + timestamps + size in addition to the name.
#include <windows.h>

typedef struct _FILE_NOTIFY_EXTENDED_INFORMATION {
    ULONG         NextEntryOffset;
    ULONG         Action;
    LARGE_INTEGER CreationTime;
    LARGE_INTEGER LastModificationTime;
    LARGE_INTEGER LastChangeTime;
    LARGE_INTEGER LastAccessTime;
    LARGE_INTEGER AllocatedLength;
    LARGE_INTEGER FileSize;
    ULONG         FileAttributes;
    ULONG         ReparsePointTag;
    LARGE_INTEGER FileId;
    LARGE_INTEGER ParentFileId;
    ULONG         FileNameLength;
    WCHAR         FileName[1];
} FILE_NOTIFY_EXTENDED_INFORMATION;

VOID WatchExtended(HANDLE hDir, BYTE* buf, DWORD cb) {
    DWORD got = 0;
    while (ReadDirectoryChangesExW(hDir, buf, cb, TRUE,
               FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,
               &got, NULL, NULL,
               ReadDirectoryNotifyExtendedInformation)) {
        FILE_NOTIFY_EXTENDED_INFORMATION* p =
            (FILE_NOTIFY_EXTENDED_INFORMATION*)buf;
        for (;;) {
            // p->FileId is unique per NTFS volume — dedupe key
            HandleChange(p);
            if (!p->NextEntryOffset) break;
            p = (FILE_NOTIFY_EXTENDED_INFORMATION*)((BYTE*)p + p->NextEntryOffset);
        }
    }
}

rustDirect-syscall wrapper with class selector

// Cargo: ntapi = "0.4"
use std::ptr::null_mut;
use ntapi::ntioapi::{NtNotifyChangeDirectoryFileEx, IO_STATUS_BLOCK};

#[repr(C)]
#[allow(non_camel_case_types, dead_code)]
enum DirectoryNotifyInformationClass {
    DirectoryNotifyInformation = 1,
    DirectoryNotifyExtendedInformation = 2,
}

pub unsafe fn watch_extended(h_dir: isize, buf: &mut [u8]) -> i32 {
    let mut iosb: IO_STATUS_BLOCK = std::mem::zeroed();
    NtNotifyChangeDirectoryFileEx(
        h_dir as _, null_mut(), None, null_mut(),
        &mut iosb,
        buf.as_mut_ptr() as _, buf.len() as u32,
        0x1F, // all FILE_NOTIFY_CHANGE_* basics
        1,    // WatchTree = TRUE
        DirectoryNotifyInformationClass::DirectoryNotifyExtendedInformation as u32,
    )
}

Mappings MITRE ATT&CK

Last verified: 2026-05-20