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
| Name | Type | Dir | Description |
|---|---|---|---|
| FileHandle | HANDLE | in | Handle vers un répertoire ouvert avec l'accès FILE_LIST_DIRECTORY. |
| Event | HANDLE | in | Événement optionnel signalé à l'achèvement. NULL si un APC ou une attente alertable est utilisé. |
| ApcRoutine | PIO_APC_ROUTINE | in | APC usermode optionnel délivré à l'achèvement dans un thread alertable. |
| ApcContext | PVOID | in | Contexte défini par l'appelant transmis à ApcRoutine. |
| IoStatusBlock | PIO_STATUS_BLOCK | out | Reçoit le statut final et le nombre d'octets écrits dans Buffer. |
| Buffer | PVOID | out | Buffer aligné DWORD qui reçoit des enregistrements de la classe choisie par DirectoryNotifyInformationClass. |
| Length | ULONG | in | Taille de Buffer en octets. |
| CompletionFilter | ULONG | in | Masque de bits FILE_NOTIFY_CHANGE_* identique à NtNotifyChangeDirectoryFile. |
| WatchTree | BOOLEAN | in | TRUE pour surveiller récursivement tous les sous-répertoires ; FALSE pour le répertoire immédiat uniquement. |
| DirectoryNotifyInformationClass | DIRECTORY_NOTIFY_INFORMATION_CLASS | in | Classe d'enregistrement : DirectoryNotifyInformation (legacy) ou DirectoryNotifyExtendedInformation (ajoute FileId, ParentFileId, timestamps). |
IDs de syscalls par version de Windows
| Version de Windows | ID de syscall | Build |
|---|---|---|
| Win10 1709 | 0x10F | win10-1709 |
| Win10 1803 | 0x111 | win10-1803 |
| Win10 1809 | 0x112 | win10-1809 |
| Win10 1903 | 0x113 | win10-1903 |
| Win10 1909 | 0x113 | win10-1909 |
| Win10 2004 | 0x118 | win10-2004 |
| Win10 20H2 | 0x118 | win10-20h2 |
| Win10 21H1 | 0x118 | win10-21h1 |
| Win10 21H2 | 0x119 | win10-21h2 |
| Win10 22H2 | 0x119 | win10-22h2 |
| Win11 21H2 | 0x11F | win11-21h2 |
| Win11 22H2 | 0x120 | win11-22h2 |
| Win11 23H2 | 0x120 | win11-23h2 |
| Win11 24H2 | 0x122 | win11-24h2 |
| Server 2019 | 0x112 | winserver-2019 |
| Server 2022 | 0x11E | winserver-2022 |
| Server 2025 | 0x122 | winserver-2025 |
Module noyau
APIs liées
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 ENDPcExtended 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