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
| Name | Type | Dir | Description |
|---|---|---|---|
| LinkHandle | PHANDLE | out | Reçoit le handle vers l'objet lien symbolique ouvert. |
| DesiredAccess | ACCESS_MASK | in | SYMBOLIC_LINK_QUERY (0x1) pour lire la cible, DELETE pour la supprimer, SYMBOLIC_LINK_ALL_ACCESS pour les deux. |
| ObjectAttributes | POBJECT_ATTRIBUTES | in | Nom et RootDirectory optionnel identifiant le lien (ex. L"\\??\\C:"). |
IDs de syscalls par version de Windows
| Version 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 |
Module noyau
APIs liées
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 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
}Mappings MITRE ATT&CK
Last verified: 2026-05-20