> Windows Syscalls
ntoskrnl.exeT1083T1518.001T1106

NtQueryDirectoryObject

Enumerates the entries (name + type) inside an object-manager directory.

Prototype

NTSTATUS NtQueryDirectoryObject(
  HANDLE   DirectoryHandle,
  PVOID    Buffer,
  ULONG    Length,
  BOOLEAN  ReturnSingleEntry,
  BOOLEAN  RestartScan,
  PULONG   Context,
  PULONG   ReturnLength
);

Arguments

NameTypeDirDescription
DirectoryHandleHANDLEinHandle from NtOpenDirectoryObject opened with DIRECTORY_QUERY access.
BufferPVOIDoutReceives one or more OBJECT_DIRECTORY_INFORMATION entries followed by their UNICODE_STRING bodies.
LengthULONGinSize of Buffer in bytes.
ReturnSingleEntryBOOLEANinTRUE returns one entry per call; FALSE packs as many as fit.
RestartScanBOOLEANinTRUE resets enumeration; FALSE resumes using Context.
ContextPULONGin/outIn/out enumeration cursor maintained by the kernel.
ReturnLengthPULONGoutReceives bytes actually written to Buffer.

Syscall IDs by Windows version

Windows versionSyscall IDBuild
Win10 15070x12Bwin10-1507
Win10 16070x131win10-1607
Win10 17030x136win10-1703
Win10 17090x139win10-1709
Win10 18030x13Bwin10-1803
Win10 18090x13Cwin10-1809
Win10 19030x13Dwin10-1903
Win10 19090x13Dwin10-1909
Win10 20040x143win10-2004
Win10 20H20x143win10-20h2
Win10 21H10x143win10-21h1
Win10 21H20x144win10-21h2
Win10 22H20x144win10-22h2
Win11 21H20x14Awin11-21h2
Win11 22H20x14Cwin11-22h2
Win11 23H20x14Cwin11-23h2
Win11 24H20x14Ewin11-24h2
Server 20160x131winserver-2016
Server 20190x13Cwinserver-2019
Server 20220x149winserver-2022
Server 20250x14Ewinserver-2025

Kernel module

ntoskrnl.exeNtQueryDirectoryObject

Related APIs

NtOpenDirectoryObjectNtCreateDirectoryObjectFindFirstFileNtQuerySystemInformation

Syscall stub

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

NtQueryDirectoryObject is one of the more SSN-volatile syscalls (`0x12B` → `0x14E`) because it sits late in the table and Microsoft adds new entries before it almost every release. The output buffer is a packed array of `OBJECT_DIRECTORY_INFORMATION { UNICODE_STRING Name; UNICODE_STRING TypeName; }` with the UNICODE_STRING `Buffer` fields pointing back into the same buffer; a NULL Name terminates the list. Each entry tells you both **what** is named (e.g. `Cor_SxSPublic_IPCBlock_Perfmon_v4.0.30319_Global_Mutex`) and **what type** it is (`Event`, `Mutant`, `Section`, `Directory`, `SymbolicLink`, `ALPC Port`, `Job`, `Semaphore`).

Common malware usage

Three primary uses. **Security-software discovery** — walk `\\Sessions\\<n>\\BaseNamedObjects` and `\\BaseNamedObjects`, match names against a curated list of AV/EDR mutants and named events (Defender, Sophos, Bitdefender, CrowdStrike Falcon, SentinelOne, Carbon Black). This is one of the cheapest sandbox-discovery techniques because it requires no API import beyond ntdll and produces no filesystem or registry traffic. **C2/implant rendezvous discovery** — same primitive, looking for the attacker's own per-session marker subdirectory to coordinate multi-implant operations. **Filesystem/device discovery from inside a sandbox** — walk `\\??` and `\\Device` to learn which Mount Manager devices and DOS drive letters exist in the current job/AppContainer view; necessary intel for sandbox escape via symbolic-link planting (CVE-2019-0808 lineage). `\\Driver` enumeration also reveals which third-party kernel drivers (especially EDR minifilters) are loaded — exactly the kind of vulnerable-driver intel BYOVD attacks need.

Detection opportunities

The fingerprint is **a process opening an object-manager directory (NtOpenDirectoryObject with DIRECTORY_QUERY) and immediately calling NtQueryDirectoryObject in a loop**. Almost no legitimate user-mode software does this — Process Explorer, WinObj, Process Hacker, and a few EDR self-checks. Detection via kernel-mode ObRegisterCallbacks on `IoDirectoryObjectType` cannot directly see the enumeration, but EDR hooks on NtQueryDirectoryObject in ntdll catch it; ETW Microsoft-Windows-Kernel-Audit-API-Calls also surfaces it. Defender ASR rule 'Block process creations originating from PSExec and WMI commands' has historically also alerted on bursts of NtQueryDirectoryObject from Office children.

Direct syscall examples

asmx64 direct stub (Win11 24H2)

; Direct syscall stub for NtQueryDirectoryObject (SSN 0x14E, Win11 24H2)
NtQueryDirectoryObject PROC
    mov  r10, rcx          ; syscall convention
    mov  eax, 14Eh         ; SSN (BUILD-SPECIFIC, resolve dynamically)
    syscall
    ret
NtQueryDirectoryObject ENDP

cBNO mutex sweep for AV/EDR fingerprints

// Walk \Sessions\<n>\BaseNamedObjects matching entries against a small
// AV/EDR mutex list. Returns the first hit's UNICODE name or NULL.
#include <windows.h>
#include <winternl.h>

typedef struct _OBJECT_DIRECTORY_INFORMATION {
    UNICODE_STRING Name;
    UNICODE_STRING TypeName;
} OBJECT_DIRECTORY_INFORMATION;

typedef NTSTATUS (NTAPI *pNtQueryDirectoryObject)(
    HANDLE, PVOID, ULONG, BOOLEAN, BOOLEAN, PULONG, PULONG);

static const WCHAR* kHits[] = {
    L"Global\\SQMMUTEX_TIMER",         // Defender
    L"CrowdStrikeFalconRules",          // Falcon
    L"SentinelOne_Mutex",               // S1
};

BOOL ScanBNO(HANDLE hDir) {
    pNtQueryDirectoryObject NtQueryDirectoryObject = (pNtQueryDirectoryObject)
        GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryDirectoryObject");
    BYTE buf[8192]; ULONG ctx = 0, ret = 0; BOOLEAN restart = TRUE;
    while (NT_SUCCESS(NtQueryDirectoryObject(hDir, buf, sizeof(buf),
                                             FALSE, restart, &ctx, &ret))) {
        restart = FALSE;
        OBJECT_DIRECTORY_INFORMATION* p = (OBJECT_DIRECTORY_INFORMATION*)buf;
        while (p->Name.Length) {
            for (int i = 0; i < _countof(kHits); ++i) {
                if (wcsstr(p->Name.Buffer, kHits[i])) return TRUE;
            }
            p++;
        }
    }
    return FALSE;
}

rustEnumerate \Driver for BYOVD intel

// Cargo: ntapi = "0.4"
// Dump loaded kernel driver object names — exactly what BYOVD attacks need to
// know about EDR minifilters and exploitable third-party drivers on the host.
use ntapi::ntobapi::NtQueryDirectoryObject;
use winapi::shared::ntdef::UNICODE_STRING;

#[repr(C)]
struct ObjectDirectoryInformation {
    name: UNICODE_STRING,
    type_name: UNICODE_STRING,
}

pub unsafe fn list_drivers(driver_dir: isize) -> Vec<String> {
    let mut out = Vec::new();
    let mut buf = vec![0u8; 16 * 1024];
    let mut ctx: u32 = 0; let mut ret: u32 = 0; let mut restart = 1u8;
    loop {
        let s = NtQueryDirectoryObject(driver_dir as _,
            buf.as_mut_ptr() as _, buf.len() as u32, 0, restart,
            &mut ctx, &mut ret);
        if s < 0 { break; }
        restart = 0;
        let mut p = buf.as_ptr() as *const ObjectDirectoryInformation;
        while (*p).name.Length != 0 {
            let n = &(*p).name;
            let slice = std::slice::from_raw_parts(n.Buffer, (n.Length / 2) as usize);
            out.push(String::from_utf16_lossy(slice));
            p = p.add(1);
        }
        if s == 0 { break; }
    }
    out
}

MITRE ATT&CK mappings

Last verified: 2026-05-20