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

NtFsControlFile

Sends FSCTL codes to a filesystem — used to plant reparse points, access ADS, and abuse junction traversal.

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 to a file, directory or volume. Access required depends on the FSCTL.
EventHANDLEinOptional event for async completion. NULL for synchronous.
ApcRoutinePIO_APC_ROUTINEinOptional APC routine on completion. Usually NULL.
ApcContextPVOIDinContext for ApcRoutine. NULL when unused.
IoStatusBlockPIO_STATUS_BLOCKoutReceives status and bytes returned in OutputBuffer.
FsControlCodeULONGinFSCTL_* code, e.g. FSCTL_SET_REPARSE_POINT (0x900A4), FSCTL_GET_REPARSE_POINT (0x900A8), FSCTL_SET_SPARSE.
InputBufferPVOIDinFSCTL-specific input payload (e.g. REPARSE_DATA_BUFFER for set-reparse).
InputBufferLengthULONGinSize of InputBuffer in bytes.
OutputBufferPVOIDoutOptional output buffer for FSCTLs that return data.
OutputBufferLengthULONGinSize of OutputBuffer in bytes. 0 when OutputBuffer is NULL.

Syscall IDs by Windows version

Windows versionSyscall IDBuild
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

Kernel module

ntoskrnl.exeNtFsControlFile

Related APIs

DeviceIoControlNtDeviceIoControlFileCreateSymbolicLinkWFindFirstStreamWBackupReadNtCreateFile

Syscall stub

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

Undocumented notes

Same 10-argument shape as NtDeviceIoControlFile; the kernel even shares dispatch code (`IopXxxControlFile` with a `FsControl` flag). The difference matters semantically: an FSCTL is sent to a *filesystem driver* (NTFS, ReFS, FAT, CDFS) or a network redirector (RDBSS, mrxsmb) rather than a generic device, and many FSCTLs require **SeManageVolumePrivilege** or **SeRestorePrivilege**. SSN `0x39` is stable Win7 → Win11 24H2. The catalog of FSCTL codes is large (≈ 250); the offensive-relevant subset is small but high-impact: reparse-point set/get, sparse-file marking, USN journal read, and volume-bitmap retrieval for raw NTFS dumping.

Common malware usage

Three offensive families. (1) **Reparse-point / junction abuse (T1564.004 + T1574.x)**: `FSCTL_SET_REPARSE_POINT` writes a NTFS reparse tag on a directory, turning it into a junction or mount point. UAC-bypass and EoP exploits use this to make a writeable folder resolve to a SYSTEM-only one (RedirectionGuard mitigated this only in Win11), and ransomware uses it to redirect log-file paths into volumes it has wiped. (2) **Alternate Data Streams (ADS) discovery (T1564.004)**: `FSCTL_QUERY_FILE_LAYOUT` and `FSCTL_GET_NTFS_FILE_RECORD` expose every $DATA stream on a file, letting droppers stash payloads (PowerShell scripts, scheduled task XMLs, secondary EXEs) into streams that `dir` does not display by default. (3) **Raw-NTFS extraction**: `FSCTL_GET_VOLUME_BITMAP` + `FSCTL_GET_RETRIEVAL_POINTERS` (cluster runs of `$MFT`) is the recipe behind `RawCopy`, `EXTRACT-DiT`, and most NTDS.dit dumpers — they read cluster runs directly from `\\.\PhysicalDriveN`, completely bypassing the locked file open.

Detection opportunities

Microsoft-Windows-Ntfs ETW (`{3FF37A1C-A68D-4D6E-8C9B-F79E8B16C482}`) and the related Storage subsystem providers emit events for reparse-point sets and FSCTL traffic. Sysmon Event ID 11 fires on the file-create side of a reparse plant. Minifilter pre-callbacks on `IRP_MJ_FILE_SYSTEM_CONTROL` (FSCTL is its minor) see every code with the input buffer — EDRs typically register here. High-value hunts: `FSCTL_SET_REPARSE_POINT` from non-installer processes, `FSCTL_GET_VOLUME_BITMAP` from user-mode binaries other than `chkdsk`/`defrag`/AV, and any process opening `\\.\PhysicalDriveN` followed by sequential `FSCTL_GET_RETRIEVAL_POINTERS`. PowerShell `Get-Item -Force` plus `:Zone.Identifier` enumeration surfaces obvious ADS plants; `Sysinternals streams.exe` is the manual tool. Microsoft's symbolic-link protections (`fsutil behavior set SymlinkEvaluation`) blunt some of the reparse abuse classes.

Direct syscall examples

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 }
}

MITRE ATT&CK mappings

Last verified: 2026-05-20