NtOpenDirectoryObject
Abre un objeto directory existente en el espacio de nombres del object manager de Windows.
Prototipo
NTSTATUS NtOpenDirectoryObject( PHANDLE DirectoryHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes );
Argumentos
| Name | Type | Dir | Description |
|---|---|---|---|
| DirectoryHandle | PHANDLE | out | Recibe el handle al objeto directory abierto. |
| DesiredAccess | ACCESS_MASK | in | DIRECTORY_QUERY para enumeración, DIRECTORY_TRAVERSE para resolución de ruta, etc. |
| ObjectAttributes | POBJECT_ATTRIBUTES | in | Nombre y RootDirectory opcional que identifican el directory objetivo (p. ej. L"\\BaseNamedObjects"). |
IDs de syscalls por versión de Windows
| Versión de Windows | ID de syscall | Build |
|---|---|---|
| Win10 1507 | 0x58 | win10-1507 |
| Win10 1607 | 0x58 | win10-1607 |
| Win10 1703 | 0x58 | win10-1703 |
| Win10 1709 | 0x58 | win10-1709 |
| Win10 1803 | 0x58 | win10-1803 |
| Win10 1809 | 0x58 | win10-1809 |
| Win10 1903 | 0x58 | win10-1903 |
| Win10 1909 | 0x58 | win10-1909 |
| Win10 2004 | 0x58 | win10-2004 |
| Win10 20H2 | 0x58 | win10-20h2 |
| Win10 21H1 | 0x58 | win10-21h1 |
| Win10 21H2 | 0x58 | win10-21h2 |
| Win10 22H2 | 0x58 | win10-22h2 |
| Win11 21H2 | 0x58 | win11-21h2 |
| Win11 22H2 | 0x58 | win11-22h2 |
| Win11 23H2 | 0x58 | win11-23h2 |
| Win11 24H2 | 0x58 | win11-24h2 |
| Server 2016 | 0x58 | winserver-2016 |
| Server 2019 | 0x58 | winserver-2019 |
| Server 2022 | 0x58 | winserver-2022 |
| Server 2025 | 0x58 | winserver-2025 |
Módulo del kernel
APIs relacionadas
Stub del 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
Notas no documentadas
El SSN `0x58` se ha mantenido constante de 1507 a 24H2, raro para una rutina del object manager — efecto de que las syscalls de apertura viven en la primera mitad de la tabla, donde Microsoft cuida más la estabilidad. Abrir un Directory es el primer paso de cualquier exploración de namespace: con `DIRECTORY_QUERY` se puede llamar a NtQueryDirectoryObject para enumerar hijos; con `DIRECTORY_TRAVERSE` el handle puede usarse como `RootDirectory` en OBJECT_ATTRIBUTES posteriores para acortar la resolución de nombres. Prefijos estándar: `\\` (raíz global), `\\??\\` (directorio de enlaces DOS de la sesión actual — `\\GLOBAL??` es la variante de sistema), `\\BaseNamedObjects`, `\\Sessions\\<n>\\BaseNamedObjects`, `\\Device`, `\\Driver`, `\\KnownDlls`, `\\KernelObjects`.
Uso común por malware
Lo más común es como paso previo a una **enumeración de namespace** — los implantes abren `\\Sessions\\<n>\\BaseNamedObjects` con DIRECTORY_QUERY y luego llaman a NtQueryDirectoryObject buscando nombres de mutex/event conocidos de AVs y EDRs (el `Global\\SQMMUTEX_TIMER` de Defender, varios events nombrados de Symantec/CrowdStrike, mutants de sandbox de Cuckoo y Triage), named pipes de banking malware competidores, y directorios de rendezvous de implantes C2 ya existentes para chequear supervivientes. También como **atajo RootDirectory** al abrir muchos objetos bajo un mismo padre — abrir el padre una vez y reutilizarlo evita re-parsear la ruta y queda algo más limpio en trazas. **Inventario sandbox/container**: un proceso confinado en AppContainer puede abrir su propio BNO raíz para inventariar lo que su broker expone.
Oportunidades de detección
Casi ningún código legítimo fuera de csrss.exe, services.exe y Process Explorer enumera el namespace del object manager a gran escala, pero **aperturas únicas de directorios comunes** (`\\BaseNamedObjects`, `\\KnownDlls`) son ruidosas y existen en muchas apps legítimas. Pivote de alto valor: **NtOpenDirectoryObject seguido de un bucle apretado de NtQueryDirectoryObject** — la huella canónica de enumeración. La detección vive en ObRegisterCallbacks en modo kernel sobre `IoDirectoryObjectType`, ETW Microsoft-Windows-Kernel-Audit-API-Calls, y — sorprendentemente — Microsoft Defender for Endpoint emite una alerta sintética para procesos no de sistema que abran `\\GLOBAL??` con DIRECTORY_QUERY. Las syscalls directas saltan los hooks de ntdll; los callbacks de kernel disparan igualmente.
Ejemplos de syscalls directos
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 ENDPcOpen 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 }
}Mapeos MITRE ATT&CK
Last verified: 2026-05-20