NtOpenSymbolicLinkObject
Abre un enlace simbólico existente del object manager por nombre, devolviendo un handle para consulta o eliminación posterior.
Prototipo
NTSTATUS NtOpenSymbolicLinkObject( PHANDLE LinkHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes );
Argumentos
| Name | Type | Dir | Description |
|---|---|---|---|
| LinkHandle | PHANDLE | out | Recibe el handle al objeto enlace simbólico abierto. |
| DesiredAccess | ACCESS_MASK | in | SYMBOLIC_LINK_QUERY (0x1) para leer destino, DELETE para eliminarlo, SYMBOLIC_LINK_ALL_ACCESS para ambos. |
| ObjectAttributes | POBJECT_ATTRIBUTES | in | Nombre y RootDirectory opcional que identifican el enlace (p. ej. L"\\??\\C:"). |
IDs de syscalls por versión de Windows
| Versión de Windows | ID de syscall | Build |
|---|---|---|
| Win10 1507 | 0x118 | win10-1507 |
| Win10 1607 | 0x11E | win10-1607 |
| Win10 1703 | 0x122 | win10-1703 |
| Win10 1709 | 0x124 | win10-1709 |
| Win10 1803 | 0x126 | win10-1803 |
| Win10 1809 | 0x127 | win10-1809 |
| Win10 1903 | 0x128 | win10-1903 |
| Win10 1909 | 0x128 | win10-1909 |
| Win10 2004 | 0x12D | win10-2004 |
| Win10 20H2 | 0x12D | win10-20h2 |
| Win10 21H1 | 0x12D | win10-21h1 |
| Win10 21H2 | 0x12E | win10-21h2 |
| Win10 22H2 | 0x12E | win10-22h2 |
| Win11 21H2 | 0x134 | win11-21h2 |
| Win11 22H2 | 0x136 | win11-22h2 |
| Win11 23H2 | 0x136 | win11-23h2 |
| Win11 24H2 | 0x138 | win11-24h2 |
| Server 2016 | 0x11E | winserver-2016 |
| Server 2019 | 0x127 | winserver-2019 |
| Server 2022 | 0x133 | winserver-2022 |
| Server 2025 | 0x138 | winserver-2025 |
Módulo del kernel
APIs relacionadas
Stub del syscall
4C 8B D1 mov r10, rcx B8 38 01 00 00 mov eax, 0x138 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 deriva mucho (`0x118` → `0x138`); resolver en runtime para soportar varios builds. Abrir un enlace con `SYMBOLIC_LINK_QUERY` permite leer la cadena destino vía `NtQuerySymbolicLinkObject`; abrirlo con `DELETE` y luego NtClose (o `NtMakeTemporaryObject`) elimina el enlace del namespace, incluso uno creado con OBJ_PERMANENT. Los symlinks del object manager se evalúan **perezosamente en la resolución de nombres**, así que abrir el enlace vía NtOpenSymbolicLinkObject no *dereferencia* el destino — eso solo ocurre cuando un lookup de nombre lo atraviesa.
Uso común por malware
Tres patrones ofensivos. **Reconocimiento de resolución de ruta**: abrir `\\??\\C:`, `\\??\\GLOBALROOT`, `\\??\\PhysicalDrive0` para consultar las cabinas `\\Device\\...` subyacentes — útil para averiguar qué volumen aloja el sistema, dónde están las shadow copies, y qué letras DOS son reales vs. unidades de red mapeadas (recon de fuga de sandbox). **Desalojo de symlink**: abrir un enlace indeseado (marcador rendezvous de un implante hermano, o symlink anti-tamper de un producto de seguridad) con `DELETE` y cerrar el handle para quitarlo. **Recarga en caliente durante exploit**: patrón clásico de race de fuga — crear symlink con `OBJ_PERMANENT`, usarlo, luego reabrirlo con DELETE+Close para borrar huellas antes de que un scanner defensivo enumere el namespace.
Oportunidades de detección
Volumen menor que NtCreateSymbolicLinkObject y aparece principalmente en código legítimo como la maquinaria de resolución de ruta dentro de RtlGetFullPathName_U_Ex y compañía. La señal digna de caza: **apertura por nombre de enlaces no estándar con acceso DELETE desde un proceso usermode** — casi ningún workflow legítimo abre un symlink para borrarlo. ObRegisterCallbacks en modo kernel sobre `IoSymbolicLinkObjectType` es el pivote autoritativo. ETW Microsoft-Windows-Kernel-Audit-API-Calls también lo expone. Correlacionar con un NtCreateSymbolicLinkObject previo del mismo proceso — open-luego-delete-luego-recreate es la race de swap de symlink usada en fugas de sandbox.
Ejemplos de syscalls directos
asmx64 direct stub (Win11 24H2)
; Direct syscall stub for NtOpenSymbolicLinkObject (SSN 0x138, Win11 24H2)
NtOpenSymbolicLinkObject PROC
mov r10, rcx ; syscall convention
mov eax, 138h ; SSN (BUILD-SPECIFIC, resolve dynamically)
syscall
ret
NtOpenSymbolicLinkObject ENDPcResolve DOS drive letter to underlying device
// Open \??\C: and dump its target string — typically \Device\HarddiskVolume3.
// Useful sandbox-escape recon and for distinguishing real volumes from
// mapped network drives whose target is \Device\Mup\...
#include <windows.h>
#include <winternl.h>
#define SYMBOLIC_LINK_QUERY 0x0001
#define OBJ_CASE_INSENSITIVE 0x00000040
typedef NTSTATUS (NTAPI *pNtOpenSymbolicLinkObject)(
PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES);
typedef NTSTATUS (NTAPI *pNtQuerySymbolicLinkObject)(
HANDLE, PUNICODE_STRING, PULONG);
BOOL ResolveDosDrive(WCHAR letter, PWSTR out, ULONG outChars) {
pNtOpenSymbolicLinkObject NtOpenSymbolicLinkObject =
(pNtOpenSymbolicLinkObject)GetProcAddress(
GetModuleHandleA("ntdll.dll"), "NtOpenSymbolicLinkObject");
pNtQuerySymbolicLinkObject NtQuerySymbolicLinkObject =
(pNtQuerySymbolicLinkObject)GetProcAddress(
GetModuleHandleA("ntdll.dll"), "NtQuerySymbolicLinkObject");
WCHAR path[16]; swprintf_s(path, 16, L"\\??\\%lc", letter);
UNICODE_STRING us; RtlInitUnicodeString(&us, path);
OBJECT_ATTRIBUTES oa;
InitializeObjectAttributes(&oa, &us, OBJ_CASE_INSENSITIVE, NULL, NULL);
HANDLE h = NULL;
if (!NT_SUCCESS(NtOpenSymbolicLinkObject(&h, SYMBOLIC_LINK_QUERY, &oa)))
return FALSE;
UNICODE_STRING target;
target.Buffer = out;
target.Length = 0;
target.MaximumLength = (USHORT)(outChars * sizeof(WCHAR));
NTSTATUS s = NtQuerySymbolicLinkObject(h, &target, NULL);
CloseHandle(h);
return NT_SUCCESS(s);
}rustEvict a planted symlink
// Cargo: ntapi = "0.4", widestring = "1", windows-sys = "0.59"
// Open a symbolic link with DELETE and close it to remove the link from
// the object namespace. Used to swap a sandbox-escape symlink mid-race or
// to clean up an unwanted competing implant's rendezvous marker.
use ntapi::ntobapi::NtOpenSymbolicLinkObject;
use ntapi::ntrtl::RtlInitUnicodeString;
use winapi::shared::ntdef::{OBJECT_ATTRIBUTES, UNICODE_STRING};
use widestring::U16CString;
use windows_sys::Win32::Foundation::CloseHandle;
const DELETE: u32 = 0x0001_0000;
const OBJ_CASE_INSENSITIVE: u32 = 0x40;
pub unsafe fn evict_symlink(nt_path: &str) -> bool {
let w = U16CString::from_str(nt_path).unwrap();
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;
if NtOpenSymbolicLinkObject(&mut h as *mut _ as _, DELETE, &mut oa) < 0 {
return false;
}
CloseHandle(h as _);
true
}Mapeos MITRE ATT&CK
Last verified: 2026-05-20