> Windows Syscalls
ntoskrnl.exeT1083T1518.001T1106

NtOpenDirectoryObject

Öffnet ein vorhandenes Directory-Objekt im Namensraum des Windows Object Managers.

Prototyp

NTSTATUS NtOpenDirectoryObject(
  PHANDLE            DirectoryHandle,
  ACCESS_MASK        DesiredAccess,
  POBJECT_ATTRIBUTES ObjectAttributes
);

Argumente

NameTypeDirDescription
DirectoryHandlePHANDLEoutEmpfängt das Handle auf das geöffnete Directory-Objekt.
DesiredAccessACCESS_MASKinDIRECTORY_QUERY für Enumeration, DIRECTORY_TRAVERSE für Pfadauflösung usw.
ObjectAttributesPOBJECT_ATTRIBUTESinName und optionales RootDirectory zur Identifikation des Zielverzeichnisses (z. B. L"\\BaseNamedObjects").

Syscall-IDs pro Windows-Version

Windows-VersionSyscall-IDBuild
Win10 15070x58win10-1507
Win10 16070x58win10-1607
Win10 17030x58win10-1703
Win10 17090x58win10-1709
Win10 18030x58win10-1803
Win10 18090x58win10-1809
Win10 19030x58win10-1903
Win10 19090x58win10-1909
Win10 20040x58win10-2004
Win10 20H20x58win10-20h2
Win10 21H10x58win10-21h1
Win10 21H20x58win10-21h2
Win10 22H20x58win10-22h2
Win11 21H20x58win11-21h2
Win11 22H20x58win11-22h2
Win11 23H20x58win11-23h2
Win11 24H20x58win11-24h2
Server 20160x58winserver-2016
Server 20190x58winserver-2019
Server 20220x58winserver-2022
Server 20250x58winserver-2025

Kernel-Modul

ntoskrnl.exeNtOpenDirectoryObject

Verwandte APIs

NtCreateDirectoryObjectNtQueryDirectoryObjectNtOpenSymbolicLinkObjectOpenPrivateNamespace

Syscall-Stub

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

Undokumentierte Hinweise

SSN `0x58` ist von 1507 bis 24H2 konstant geblieben — ungewöhnlich für eine Object-Manager-Routine — Nebeneffekt davon, dass die Open-Syscalls in der vorderen Tabellenhälfte liegen, wo Microsoft stabiler ist. Das Öffnen eines Directory-Objekts ist der erste Schritt jeder Namensraum-Erkundung: mit `DIRECTORY_QUERY` lässt sich NtQueryDirectoryObject zur Enumeration aufrufen; mit `DIRECTORY_TRAVERSE` kann das Handle als `RootDirectory` in nachfolgenden OBJECT_ATTRIBUTES dienen, um die Namensauflösung abzukürzen. Standardpräfixe: `\\` (globaler Root), `\\??\\` (DOS-Device-Link-Verzeichnis der aktuellen Session — `\\GLOBAL??` ist die systemweite Variante), `\\BaseNamedObjects`, `\\Sessions\\<n>\\BaseNamedObjects`, `\\Device`, `\\Driver`, `\\KnownDlls`, `\\KernelObjects`.

Häufige Malware-Nutzung

Meist als Vorbereitungsschritt für eine **Namensraum-Enumeration** — Implants öffnen `\\Sessions\\<n>\\BaseNamedObjects` mit DIRECTORY_QUERY und rufen dann NtQueryDirectoryObject auf, um bekannte Mutex/Event-Namen von AVs und EDRs zu suchen (Defenders `Global\\SQMMUTEX_TIMER`, mehrere benannte Symantec/CrowdStrike-Events, Sandbox-Mutants von Cuckoo und Triage), Named Pipes konkurrierender Banking-Malware und vorhandene C2-Implant-Rendezvous-Verzeichnisse für Survivor-Checks. Auch als **RootDirectory-Shortcut**, wenn viele Objekte unter einem bekannten Parent geöffnet werden — den Parent einmal öffnen und wiederverwenden vermeidet erneutes Pfad-Parsing und ist etwas sauberer in Spuren. **Sandbox/Container-Inventur**: ein AppContainer-isolierter Prozess kann seinen eigenen BNO-Root öffnen, um aufzulisten, was sein Broker exponiert.

Erkennungs­möglichkeiten

Fast kein legitimer Code außer csrss.exe, services.exe und Process Explorer enumeriert den Object-Manager-Namensraum in großem Stil, aber **einzelne Opens auf häufige Directories** (`\\BaseNamedObjects`, `\\KnownDlls`) sind rauschig und in vielen legitimen Apps zu sehen. Hochwertiger Pivot: **NtOpenDirectoryObject gefolgt von einer engen NtQueryDirectoryObject-Schleife** — kanonischer Enumerations-Fingerabdruck. Detektion via kernelseitigem ObRegisterCallbacks auf `IoDirectoryObjectType`, ETW Microsoft-Windows-Kernel-Audit-API-Calls, und — überraschend — Microsoft Defender for Endpoint emittiert eine synthetische Warnung, wenn Nicht-System-Prozesse `\\GLOBAL??` mit DIRECTORY_QUERY öffnen. Direkte Syscalls umgehen ntdll-Hooks; Kernel-Callbacks feuern dennoch.

Direkte Syscall-Beispiele

asmx64 direct stub

; Direct syscall stub for NtOpenDirectoryObject (SSN 0x58, all builds)
NtOpenDirectoryObject PROC
    mov  r10, rcx          ; syscall convention
    mov  eax, 58h          ; SSN
    syscall
    ret
NtOpenDirectoryObject ENDP

cOpen per-session BNO for AV/EDR mutex sweep

// Open the current session's BaseNamedObjects directory with QUERY access
// so we can later NtQueryDirectoryObject() it and look for known
// AV/EDR mutex names (Defender, CrowdStrike, Cuckoo, Triage, ...).
#include <windows.h>
#include <winternl.h>

#define DIRECTORY_QUERY      0x0001
#define DIRECTORY_TRAVERSE   0x0002
#define OBJ_CASE_INSENSITIVE 0x00000040

typedef NTSTATUS (NTAPI *pNtOpenDirectoryObject)(
    PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES);

HANDLE OpenSessionBNO(ULONG sessionId) {
    WCHAR path[64];
    swprintf_s(path, 64, L"\\Sessions\\%lu\\BaseNamedObjects", sessionId);
    UNICODE_STRING us; RtlInitUnicodeString(&us, path);
    OBJECT_ATTRIBUTES oa;
    InitializeObjectAttributes(&oa, &us, OBJ_CASE_INSENSITIVE, NULL, NULL);

    pNtOpenDirectoryObject NtOpenDirectoryObject = (pNtOpenDirectoryObject)
        GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtOpenDirectoryObject");
    HANDLE h = NULL;
    if (NT_SUCCESS(NtOpenDirectoryObject(&h, DIRECTORY_QUERY | DIRECTORY_TRAVERSE, &oa)))
        return h;
    return NULL;
}

rustOpen \GLOBAL?? as DOS-device root

// Cargo: ntapi = "0.4", widestring = "1"
// Open the system-wide DOS device-link directory; subsequent
// NtCreateSymbolicLinkObject calls with this as RootDirectory plant
// links visible to every session (requires SeCreateSymbolicLinkPrivilege).
use ntapi::ntobapi::NtOpenDirectoryObject;
use ntapi::ntrtl::RtlInitUnicodeString;
use ntapi::ntobapi::OBJ_CASE_INSENSITIVE;
use winapi::shared::ntdef::{OBJECT_ATTRIBUTES, UNICODE_STRING};
use widestring::U16CString;

pub unsafe fn open_global_dosdev() -> Option<isize> {
    let w = U16CString::from_str("\\GLOBAL??").ok()?;
    let mut us: UNICODE_STRING = std::mem::zeroed();
    RtlInitUnicodeString(&mut us, w.as_ptr());
    let mut oa: OBJECT_ATTRIBUTES = std::mem::zeroed();
    oa.Length = std::mem::size_of::<OBJECT_ATTRIBUTES>() as u32;
    oa.ObjectName = &mut us;
    oa.Attributes = OBJ_CASE_INSENSITIVE;
    let mut h: isize = 0;
    let s = NtOpenDirectoryObject(&mut h as *mut _ as _, 0x3, &mut oa);
    if s >= 0 { Some(h) } else { None }
}

MITRE ATT&CK-Mappings

Last verified: 2026-05-20