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
| Name | Type | Dir | Description |
|---|---|---|---|
| DirectoryHandle | HANDLE | in | Handle from NtOpenDirectoryObject opened with DIRECTORY_QUERY access. |
| Buffer | PVOID | out | Receives one or more OBJECT_DIRECTORY_INFORMATION entries followed by their UNICODE_STRING bodies. |
| Length | ULONG | in | Size of Buffer in bytes. |
| ReturnSingleEntry | BOOLEAN | in | TRUE returns one entry per call; FALSE packs as many as fit. |
| RestartScan | BOOLEAN | in | TRUE resets enumeration; FALSE resumes using Context. |
| Context | PULONG | in/out | In/out enumeration cursor maintained by the kernel. |
| ReturnLength | PULONG | out | Receives bytes actually written to Buffer. |
Syscall IDs by Windows version
| Windows version | Syscall ID | Build |
|---|---|---|
| Win10 1507 | 0x12B | win10-1507 |
| Win10 1607 | 0x131 | win10-1607 |
| Win10 1703 | 0x136 | win10-1703 |
| Win10 1709 | 0x139 | win10-1709 |
| Win10 1803 | 0x13B | win10-1803 |
| Win10 1809 | 0x13C | win10-1809 |
| Win10 1903 | 0x13D | win10-1903 |
| Win10 1909 | 0x13D | win10-1909 |
| Win10 2004 | 0x143 | win10-2004 |
| Win10 20H2 | 0x143 | win10-20h2 |
| Win10 21H1 | 0x143 | win10-21h1 |
| Win10 21H2 | 0x144 | win10-21h2 |
| Win10 22H2 | 0x144 | win10-22h2 |
| Win11 21H2 | 0x14A | win11-21h2 |
| Win11 22H2 | 0x14C | win11-22h2 |
| Win11 23H2 | 0x14C | win11-23h2 |
| Win11 24H2 | 0x14E | win11-24h2 |
| Server 2016 | 0x131 | winserver-2016 |
| Server 2019 | 0x13C | winserver-2019 |
| Server 2022 | 0x149 | winserver-2022 |
| Server 2025 | 0x14E | winserver-2025 |
Kernel module
Related APIs
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 ENDPcBNO 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