> Windows Syscalls
ntoskrnl.exeT1574T1083T1106

NtOpenSymbolicLinkObject

Ouvre un lien symbolique de l'object manager existant par son nom, renvoyant un handle pour interrogation ou suppression.

Prototype

NTSTATUS NtOpenSymbolicLinkObject(
  PHANDLE            LinkHandle,
  ACCESS_MASK        DesiredAccess,
  POBJECT_ATTRIBUTES ObjectAttributes
);

Arguments

NameTypeDirDescription
LinkHandlePHANDLEoutReçoit le handle vers l'objet lien symbolique ouvert.
DesiredAccessACCESS_MASKinSYMBOLIC_LINK_QUERY (0x1) pour lire la cible, DELETE pour la supprimer, SYMBOLIC_LINK_ALL_ACCESS pour les deux.
ObjectAttributesPOBJECT_ATTRIBUTESinNom et RootDirectory optionnel identifiant le lien (ex. L"\\??\\C:").

IDs de syscalls par version de Windows

Version de WindowsID de syscallBuild
Win10 15070x118win10-1507
Win10 16070x11Ewin10-1607
Win10 17030x122win10-1703
Win10 17090x124win10-1709
Win10 18030x126win10-1803
Win10 18090x127win10-1809
Win10 19030x128win10-1903
Win10 19090x128win10-1909
Win10 20040x12Dwin10-2004
Win10 20H20x12Dwin10-20h2
Win10 21H10x12Dwin10-21h1
Win10 21H20x12Ewin10-21h2
Win10 22H20x12Ewin10-22h2
Win11 21H20x134win11-21h2
Win11 22H20x136win11-22h2
Win11 23H20x136win11-23h2
Win11 24H20x138win11-24h2
Server 20160x11Ewinserver-2016
Server 20190x127winserver-2019
Server 20220x133winserver-2022
Server 20250x138winserver-2025

Module noyau

ntoskrnl.exeNtOpenSymbolicLinkObject

APIs liées

NtCreateSymbolicLinkObjectNtQuerySymbolicLinkObjectQueryDosDeviceWNtMakeTemporaryObject

Stub du 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

Notes non documentées

Le SSN dérive beaucoup (`0x118` → `0x138`) ; résoudre à l'exécution pour viser plusieurs builds. Ouvrir un lien avec `SYMBOLIC_LINK_QUERY` permet de lire la chaîne cible via `NtQuerySymbolicLinkObject` ; l'ouvrir avec `DELETE` puis NtClose (ou `NtMakeTemporaryObject`) retire le lien du namespace, même s'il avait été créé avec OBJ_PERMANENT. Les symlinks dans l'object manager sont évalués **paresseusement à la résolution de nom**, ce qui signifie qu'ouvrir le lien via NtOpenSymbolicLinkObject ne *déréférence pas* la cible — cela n'arrive que quand un lookup de nom le traverse.

Usage courant par les malwares

Trois schémas offensifs. **Reconnaissance de résolution de chemin** : ouvrir `\\??\\C:`, `\\??\\GLOBALROOT`, `\\??\\PhysicalDrive0` pour interroger les cibles `\\Device\\...` sous-jacentes — utile pour découvrir quel volume héberge le système, où vivent les shadow copies, et quelles lettres DOS sont réelles vs. lecteurs réseau mappés (recon évasion sandbox). **Éviction de symlink** : ouvrir un lien indésirable (marqueur de rendez-vous d'un implant frère, ou symlink anti-tamper d'un produit de sécurité) avec `DELETE` et fermer le handle pour le retirer. **Rechargement à chaud pendant exploit** : pattern classique de race d'évasion — créer un symlink en `OBJ_PERMANENT`, l'utiliser, puis le rouvrir en DELETE+Close pour effacer les traces avant qu'un scanner défensif n'énumère le namespace.

Opportunités de détection

Volume plus faible que NtCreateSymbolicLinkObject, principalement présent en légitime via la machinerie de résolution de chemin dans RtlGetFullPathName_U_Ex et al. Signal digne de chasse : **ouverture par nom de liens non standards avec accès DELETE depuis un processus usermode** — quasiment aucun workflow légitime n'ouvre un symlink pour le supprimer. ObRegisterCallbacks côté noyau sur `IoSymbolicLinkObjectType` est le pivot autoritaire. ETW Microsoft-Windows-Kernel-Audit-API-Calls l'expose aussi. Corréler avec un NtCreateSymbolicLinkObject précédent du même processus — open-puis-delete-puis-recreate est la race de swap de symlink utilisée dans les évasions de sandbox.

Exemples de syscalls directs

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 ENDP

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

Mappings MITRE ATT&CK

Last verified: 2026-05-20