> Windows Syscalls
ntoskrnl.exeT1564.004T1564T1106

NtQueryEaFile

Reads NTFS extended attributes (EAs) from a file handle, optionally filtered or paged.

Prototype

NTSTATUS NtQueryEaFile(
  HANDLE           FileHandle,
  PIO_STATUS_BLOCK IoStatusBlock,
  PVOID            Buffer,
  ULONG            Length,
  BOOLEAN          ReturnSingleEntry,
  PVOID            EaList,
  ULONG            EaListLength,
  PULONG           EaIndex,
  BOOLEAN          RestartScan
);

Arguments

NameTypeDirDescription
FileHandleHANDLEinHandle to an open file (must include FILE_READ_EA access).
IoStatusBlockPIO_STATUS_BLOCKoutReceives completion status and bytes written into Buffer.
BufferPVOIDoutReceives an array of FILE_FULL_EA_INFORMATION records.
LengthULONGinSize of Buffer in bytes.
ReturnSingleEntryBOOLEANinIf TRUE, returns only the first matching EA, even if more fit in Buffer.
EaListPVOIDinOptional list of FILE_GET_EA_INFORMATION naming specific EAs to fetch (NULL = all).
EaListLengthULONGinSize of EaList in bytes, or 0 if EaList is NULL.
EaIndexPULONGinOptional 1-based index into the file's EA list to start enumeration from.
RestartScanBOOLEANinIf TRUE, restart enumeration from the first EA; FALSE continues from previous call.

Syscall IDs by Windows version

Windows versionSyscall IDBuild
Win10 15070x12Dwin10-1507
Win10 16070x133win10-1607
Win10 17030x138win10-1703
Win10 17090x13Bwin10-1709
Win10 18030x13Dwin10-1803
Win10 18090x13Ewin10-1809
Win10 19030x13Fwin10-1903
Win10 19090x13Fwin10-1909
Win10 20040x145win10-2004
Win10 20H20x145win10-20h2
Win10 21H10x145win10-21h1
Win10 21H20x146win10-21h2
Win10 22H20x146win10-22h2
Win11 21H20x14Cwin11-21h2
Win11 22H20x14Ewin11-22h2
Win11 23H20x14Ewin11-23h2
Win11 24H20x150win11-24h2
Server 20160x133winserver-2016
Server 20190x13Ewinserver-2019
Server 20220x14Bwinserver-2022
Server 20250x150winserver-2025

Kernel module

ntoskrnl.exeNtQueryEaFile

Related APIs

NtSetEaFileZwQueryEaFileGetFileInformationByHandleEx (FileFullEaInformation)BackupRead (BACKUP_EA_DATA stream)FILE_FULL_EA_INFORMATIONFILE_GET_EA_INFORMATION

Syscall stub

4C 8B D1            mov r10, rcx
B8 50 01 00 00      mov eax, 0x150     ; Win11 24H2 SSN
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

Counterpart to `NtSetEaFile`. With 9 arguments, two of them stack-passed on x64, this is one of the chunkier file syscalls — call sites usually wrap it in a small helper that just hands in `NULL/0/NULL/FALSE` for the optional filter/index/restart trio when the caller wants "give me everything". The kernel returns `STATUS_NO_EAS_ON_FILE` for files without an EA stream and `STATUS_NONEXISTENT_EA_ENTRY` for a missing named lookup — both of which are useful negative oracles when probing.

Common malware usage

The read side of the EA-stealth pattern: an implant that previously hid configuration or staged shellcode via `NtSetEaFile` later loads it with `NtQueryEaFile`. Naming the EA explicitly via the `EaList` parameter is preferred over a full enumeration because it leaves a smaller telemetry footprint and avoids triggering scanners that walk every EA on the file. Some loaders also use the EA path as an integrity-check side channel — payload presence and exact length act as a tamper detector before decryption.

Detection opportunities

`NtQueryEaFile` on its own is benign — the OS itself calls it on roaming profile shares and any time WSL/Cygwin permission EAs are read. The signal is the *pattern*: a process opening a non-WSL file with FILE_READ_EA and then immediately decrypting/jumping into the returned blob. ETW Microsoft-Windows-Kernel-File `FileQueryEa` event, paired with a subsequent VirtualProtect→RX on a region freshly written from that buffer, is the high-fidelity correlation. Standalone EA queries from `svchost.exe`, `lsass.exe` or any signed Microsoft binary against a user-writable file are worth surfacing.

Direct syscall examples

cTargeted single-EA read

// Fetch only the 'CONFIG.BIN' EA — avoids a noisy full enumeration.
HANDLE h = CreateFileW(L"C:\\ProgramData\\update.dat", FILE_READ_EA, FILE_SHARE_READ,
                       NULL, OPEN_EXISTING, 0, NULL);

struct {
    ULONG  NextEntryOffset;
    UCHAR  EaNameLength;
    CHAR   EaName[12];   // "CONFIG.BIN\0" + padding
} eaList = { 0, 10, "CONFIG.BIN\0" };

BYTE out[4096];
IO_STATUS_BLOCK iosb;
NTSTATUS st = NtQueryEaFile(h, &iosb, out, sizeof(out),
                            TRUE,                  // single entry
                            &eaList, sizeof(eaList),
                            NULL,                  // no index
                            TRUE);                 // restart scan
if (NT_SUCCESS(st)) {
    PFILE_FULL_EA_INFORMATION ea = (PFILE_FULL_EA_INFORMATION)out;
    decrypt_and_run(ea->EaName + ea->EaNameLength + 1, ea->EaValueLength);
}

asmx64 direct stub

; NtQueryEaFile direct syscall (Win11 24H2 SSN 0x150)
NtQueryEaFile PROC
    mov  r10, rcx
    mov  eax, 150h
    syscall
    ret
NtQueryEaFile ENDP

rustEnumerate every EA on a file

// Cargo: windows-sys = "0.59"
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::System::LibraryLoader::*;

type NtQueryEaFileFn = unsafe extern "system" fn(
    HANDLE, *mut u8, *mut u8, u32, u8, *const u8, u32, *mut u32, u8,
) -> i32;

unsafe fn dump_eas(h: HANDLE) -> Vec<(String, Vec<u8>)> {
    let nt = GetModuleHandleA(b"ntdll.dll\0".as_ptr());
    let f: NtQueryEaFileFn = std::mem::transmute(
        GetProcAddress(nt, b"NtQueryEaFile\0".as_ptr()).unwrap()
    );
    let mut buf = vec![0u8; 0x10000];
    let mut iosb = [0u8; 16];
    let st = f(h, iosb.as_mut_ptr(), buf.as_mut_ptr(), buf.len() as u32,
               0, std::ptr::null(), 0, std::ptr::null_mut(), 1);
    if st < 0 { return vec![]; }
    // Walk NextEntryOffset chain ... (omitted for brevity)
    Vec::new()
}

MITRE ATT&CK mappings

Last verified: 2026-05-20