> Windows Syscalls
ntoskrnl.exeT1564.004T1574.005T1003.003

NtFsControlFile

Envoie des codes FSCTL à un système de fichiers — utilisé pour planter des reparse points, accéder aux ADS et abuser des jonctions.

Prototype

NTSTATUS NtFsControlFile(
  HANDLE           FileHandle,
  HANDLE           Event,
  PIO_APC_ROUTINE  ApcRoutine,
  PVOID            ApcContext,
  PIO_STATUS_BLOCK IoStatusBlock,
  ULONG            FsControlCode,
  PVOID            InputBuffer,
  ULONG            InputBufferLength,
  PVOID            OutputBuffer,
  ULONG            OutputBufferLength
);

Arguments

NameTypeDirDescription
FileHandleHANDLEinHandle vers un fichier, répertoire ou volume. L'accès requis dépend du FSCTL.
EventHANDLEinÉvènement optionnel pour async. NULL pour synchrone.
ApcRoutinePIO_APC_ROUTINEinRoutine APC optionnelle à la complétion. Habituellement NULL.
ApcContextPVOIDinContexte pour ApcRoutine. NULL si inutilisé.
IoStatusBlockPIO_STATUS_BLOCKoutReçoit le statut et les octets retournés dans OutputBuffer.
FsControlCodeULONGinCode FSCTL_*, ex. FSCTL_SET_REPARSE_POINT (0x900A4), FSCTL_GET_REPARSE_POINT (0x900A8), FSCTL_SET_SPARSE.
InputBufferPVOIDinPayload d'entrée spécifique au FSCTL (ex. REPARSE_DATA_BUFFER pour set-reparse).
InputBufferLengthULONGinTaille en octets d'InputBuffer.
OutputBufferPVOIDoutTampon de sortie optionnel pour les FSCTL qui retournent des données.
OutputBufferLengthULONGinTaille en octets d'OutputBuffer. 0 si OutputBuffer est NULL.

IDs de syscalls par version de Windows

Version de WindowsID de syscallBuild
Win10 15070x39win10-1507
Win10 16070x39win10-1607
Win10 17030x39win10-1703
Win10 17090x39win10-1709
Win10 18030x39win10-1803
Win10 18090x39win10-1809
Win10 19030x39win10-1903
Win10 19090x39win10-1909
Win10 20040x39win10-2004
Win10 20H20x39win10-20h2
Win10 21H10x39win10-21h1
Win10 21H20x39win10-21h2
Win10 22H20x39win10-22h2
Win11 21H20x39win11-21h2
Win11 22H20x39win11-22h2
Win11 23H20x39win11-23h2
Win11 24H20x39win11-24h2
Server 20160x39winserver-2016
Server 20190x39winserver-2019
Server 20220x39winserver-2022
Server 20250x39winserver-2025

Module noyau

ntoskrnl.exeNtFsControlFile

APIs liées

DeviceIoControlNtDeviceIoControlFileCreateSymbolicLinkWFindFirstStreamWBackupReadNtCreateFile

Stub du syscall

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

Même forme à 10 arguments que NtDeviceIoControlFile ; le noyau partage même le code de dispatch (`IopXxxControlFile` avec flag `FsControl`). La différence est sémantique : un FSCTL est destiné à un *driver système de fichiers* (NTFS, ReFS, FAT, CDFS) ou à un redirecteur réseau (RDBSS, mrxsmb), et beaucoup de FSCTL exigent **SeManageVolumePrivilege** ou **SeRestorePrivilege**. SSN `0x39` stable de Win7 à Win11 24H2. Le catalogue de codes FSCTL est large (≈ 250) ; le sous-ensemble offensif est petit mais à fort impact : set/get de reparse point, marquage sparse, lecture du journal USN et récupération de bitmap de volume pour dump NTFS brut.

Usage courant par les malwares

Trois familles offensives. (1) **Abus reparse point / jonction (T1564.004 + T1574.x)** : `FSCTL_SET_REPARSE_POINT` écrit un tag de reparse NTFS sur un répertoire, le transformant en jonction ou point de montage. Exploits de contournement UAC et d'EoP s'en servent pour qu'un dossier inscriptible pointe vers un dossier SYSTEM (RedirectionGuard a atténué ça côté Win11), et les ransomwares l'utilisent pour rediriger les chemins de logs vers des volumes effacés. (2) **Découverte d'ADS (T1564.004)** : `FSCTL_QUERY_FILE_LAYOUT` et `FSCTL_GET_NTFS_FILE_RECORD` exposent chaque flux $DATA d'un fichier, permettant aux droppers de stocker payloads (scripts PowerShell, XML de tâches planifiées, EXE secondaires) dans des flux que `dir` ne montre pas par défaut. (3) **Extraction NTFS brute** : `FSCTL_GET_VOLUME_BITMAP` + `FSCTL_GET_RETRIEVAL_POINTERS` (runs de clusters du `$MFT`) est la recette de `RawCopy`, `EXTRACT-DiT` et la plupart des dumpers de NTDS.dit — ils lisent les runs directement depuis `\\.\PhysicalDriveN`, contournant totalement l'open verrouillé du fichier.

Opportunités de détection

L'ETW Microsoft-Windows-Ntfs (`{3FF37A1C-A68D-4D6E-8C9B-F79E8B16C482}`) et les fournisseurs du sous-système Storage émettent des évènements pour les sets de reparse point et le trafic FSCTL. Sysmon Event ID 11 se déclenche côté création de fichier lors d'une plantation de reparse. Les callbacks minifilter pre sur `IRP_MJ_FILE_SYSTEM_CONTROL` (FSCTL est son mineur) voient chaque code avec son input buffer — c'est là que la plupart des EDR s'enregistrent. Chasses : `FSCTL_SET_REPARSE_POINT` depuis des processus non-installeurs, `FSCTL_GET_VOLUME_BITMAP` depuis des binaires user-mode autres que `chkdsk`/`defrag`/AV, et tout processus ouvrant `\\.\PhysicalDriveN` puis `FSCTL_GET_RETRIEVAL_POINTERS` en séquence. PowerShell `Get-Item -Force` + énumération `:Zone.Identifier` expose les plantations ADS évidentes ; `Sysinternals streams.exe` est l'outil manuel. Les protections de liens symboliques Microsoft (`fsutil behavior set SymlinkEvaluation`) atténuent certaines classes.

Exemples de syscalls directs

cPlant a junction with FSCTL_SET_REPARSE_POINT

// REPARSE_DATA_BUFFER for IO_REPARSE_TAG_MOUNT_POINT, redirecting
// C:\Users\victim\Downloads\evil  ->  \??\C:\Windows\System32.
#define IO_REPARSE_TAG_MOUNT_POINT 0xA0000003
#define FSCTL_SET_REPARSE_POINT    0x900A4

typedef struct _REPARSE_MOUNT_POINT {
    ULONG  ReparseTag;          // 0xA0000003
    USHORT ReparseDataLength;
    USHORT Reserved;
    USHORT SubstituteNameOffset;
    USHORT SubstituteNameLength;
    USHORT PrintNameOffset;
    USHORT PrintNameLength;
    WCHAR  PathBuffer[1];
} REPARSE_MOUNT_POINT, *PREPARSE_MOUNT_POINT;

// hDir = handle to empty directory opened with GENERIC_WRITE | DELETE
IO_STATUS_BLOCK iosb = {0};
NTSTATUS s = NtFsControlFile(
    hDir, NULL, NULL, NULL, &iosb,
    FSCTL_SET_REPARSE_POINT,
    rd, REPARSE_DATA_BUFFER_HEADER_SIZE + rd->ReparseDataLength,
    NULL, 0);

asmDirect stub (SSN 0x39) — 10 args

; Identical 4-instr stub form; ten parameters from the caller, six on stack.
NtFsControlFile PROC
    mov  r10, rcx
    mov  eax, 39h
    syscall
    ret
NtFsControlFile ENDP

rustQuery reparse point on a directory

// Cargo: ntapi = "0.4", winapi = { version = "0.3", features = ["ntdef"] }
use ntapi::ntioapi::{NtFsControlFile, IO_STATUS_BLOCK};
use winapi::shared::ntdef::HANDLE;

const FSCTL_GET_REPARSE_POINT: u32 = 0x900A8;

pub unsafe fn read_reparse(h_dir: HANDLE) -> Option<Vec<u8>> {
    let mut buf = vec![0u8; 0x4000];
    let mut iosb: IO_STATUS_BLOCK = core::mem::zeroed();
    let s = NtFsControlFile(
        h_dir, core::ptr::null_mut(),
        None, core::ptr::null_mut(),
        &mut iosb,
        FSCTL_GET_REPARSE_POINT,
        core::ptr::null_mut(), 0,
        buf.as_mut_ptr() as *mut _, buf.len() as u32,
    );
    if s == 0 {
        let n = *iosb.u.Status_mut() as usize;
        buf.truncate(n);
        Some(buf)
    } else { None }
}

Mappings MITRE ATT&CK

Last verified: 2026-05-20