> Windows Syscalls
ntoskrnl.exeT1083T1518.001T1106

NtOpenDirectoryObject

Ouvre un objet directory existant dans l'espace de noms de l'object manager Windows.

Prototype

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

Arguments

NameTypeDirDescription
DirectoryHandlePHANDLEoutReçoit le handle vers l'objet directory ouvert.
DesiredAccessACCESS_MASKinDIRECTORY_QUERY pour énumération, DIRECTORY_TRAVERSE pour résolution de chemin, etc.
ObjectAttributesPOBJECT_ATTRIBUTESinNom et RootDirectory optionnel identifiant le directory cible (ex. L"\\BaseNamedObjects").

IDs de syscalls par version de Windows

Version de WindowsID de syscallBuild
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

Module noyau

ntoskrnl.exeNtOpenDirectoryObject

APIs liées

NtCreateDirectoryObjectNtQueryDirectoryObjectNtOpenSymbolicLinkObjectOpenPrivateNamespace

Stub du syscall

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

Notes non documentées

Le SSN `0x58` est resté constant de 1507 à 24H2, ce qui est rare pour une routine d'object manager — effet du fait que les syscalls d'ouverture vivent dans la première moitié de la table, là où Microsoft fait plus attention à la stabilité. Ouvrir un Directory est la première étape de toute exploration de namespace : avec `DIRECTORY_QUERY` on peut appeler NtQueryDirectoryObject pour énumérer les enfants ; avec `DIRECTORY_TRAVERSE` le handle peut servir de `RootDirectory` dans des OBJECT_ATTRIBUTES ultérieurs pour court-circuiter la résolution de nom. Préfixes standards : `\\` (racine globale), `\\??\\` (répertoire des liens DOS pour la session courante — `\\GLOBAL??` est la variante système), `\\BaseNamedObjects`, `\\Sessions\\<n>\\BaseNamedObjects`, `\\Device`, `\\Driver`, `\\KnownDlls`, `\\KernelObjects`.

Usage courant par les malwares

Le plus souvent comme étape de préparation à une **énumération du namespace** — les implants ouvrent `\\Sessions\\<n>\\BaseNamedObjects` avec DIRECTORY_QUERY puis appellent NtQueryDirectoryObject pour chercher des noms de mutex/event connus appartenant aux AV et EDR (le `Global\\SQMMUTEX_TIMER` de Defender, plusieurs events nommés Symantec/CrowdStrike, mutants sandbox de Cuckoo et Triage), des named pipes de banking malware concurrents, et des répertoires de rendez-vous d'implants C2 existants pour vérifier les survivants. Aussi comme **raccourci RootDirectory** quand on ouvre de nombreux objets sous un parent connu — ouvrir le parent une fois et le réutiliser évite de re-parser le chemin et reste un peu plus propre dans les traces. **Inventaire sandbox/container** : un processus enfermé en AppContainer peut ouvrir son propre BNO racine pour inventorier ce que son broker expose.

Opportunités de détection

Quasi aucun code légitime hors csrss.exe, services.exe et Process Explorer n'énumère le namespace de l'object manager à grande échelle, mais **les ouvertures uniques de directories communs** (`\\BaseNamedObjects`, `\\KnownDlls`) sont bruyantes et présentes dans beaucoup d'apps légitimes. Pivot à haute valeur : **NtOpenDirectoryObject suivi d'une boucle serrée de NtQueryDirectoryObject** — empreinte canonique de l'énumération. La détection vit dans ObRegisterCallbacks côté noyau sur `IoDirectoryObjectType`, ETW Microsoft-Windows-Kernel-Audit-API-Calls, et — étonnamment — Microsoft Defender for Endpoint émet une alerte synthétique sur les processus non système ouvrant `\\GLOBAL??` avec DIRECTORY_QUERY. Les syscalls directs contournent les hooks ntdll ; les callbacks noyau se déclenchent quand même.

Exemples de syscalls directs

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

Mappings MITRE ATT&CK

Last verified: 2026-05-20