NtDuplicateObject
Duplica un handle desde un proceso origen a un proceso destino, ajustando opcionalmente el acceso o cerrando el origen.
Prototipo
NTSTATUS NtDuplicateObject( HANDLE SourceProcessHandle, HANDLE SourceHandle, HANDLE TargetProcessHandle, PHANDLE TargetHandle, ACCESS_MASK DesiredAccess, ULONG HandleAttributes, ULONG Options );
Argumentos
| Name | Type | Dir | Description |
|---|---|---|---|
| SourceProcessHandle | HANDLE | in | Handle al proceso que posee SourceHandle. Requiere PROCESS_DUP_HANDLE. |
| SourceHandle | HANDLE | in | El handle en el proceso origen que se duplicará. |
| TargetProcessHandle | HANDLE | in | Handle al proceso destino. NULL solo se permite si Options contiene DUPLICATE_CLOSE_SOURCE. |
| TargetHandle | PHANDLE | out | Recibe el nuevo handle válido en el proceso destino. Opcional. |
| DesiredAccess | ACCESS_MASK | in | Máscara de acceso del nuevo handle. Se ignora si Options incluye DUPLICATE_SAME_ACCESS. |
| HandleAttributes | ULONG | in | Banderas como OBJ_INHERIT u OBJ_PROTECT_CLOSE aplicadas al nuevo handle. |
| Options | ULONG | in | Bitmask de DUPLICATE_CLOSE_SOURCE y/o DUPLICATE_SAME_ACCESS / DUPLICATE_SAME_ATTRIBUTES. |
IDs de syscalls por versión de Windows
| Versión de Windows | ID de syscall | Build |
|---|---|---|
| Win10 1507 | 0x3C | win10-1507 |
| Win10 1607 | 0x3C | win10-1607 |
| Win10 1703 | 0x3C | win10-1703 |
| Win10 1709 | 0x3C | win10-1709 |
| Win10 1803 | 0x3C | win10-1803 |
| Win10 1809 | 0x3C | win10-1809 |
| Win10 1903 | 0x3C | win10-1903 |
| Win10 1909 | 0x3C | win10-1909 |
| Win10 2004 | 0x3C | win10-2004 |
| Win10 20H2 | 0x3C | win10-20h2 |
| Win10 21H1 | 0x3C | win10-21h1 |
| Win10 21H2 | 0x3C | win10-21h2 |
| Win10 22H2 | 0x3C | win10-22h2 |
| Win11 21H2 | 0x3C | win11-21h2 |
| Win11 22H2 | 0x3C | win11-22h2 |
| Win11 23H2 | 0x3C | win11-23h2 |
| Win11 24H2 | 0x3C | win11-24h2 |
| Server 2016 | 0x3C | winserver-2016 |
| Server 2019 | 0x3C | winserver-2019 |
| Server 2022 | 0x3C | winserver-2022 |
| Server 2025 | 0x3C | winserver-2025 |
Módulo del kernel
APIs relacionadas
Stub del syscall
4C 8B D1 mov r10, rcx B8 3C 00 00 00 mov eax, 0x3C 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
NtDuplicateObject mantiene el SSN `0x3C` desde Windows 10 1507 hasta Windows 11 24H2 — una curva plana poco habitual que facilita los stubs syscall directos codificados. Internamente pasa por ObDuplicateObject, que valida PROCESS_DUP_HANDLE en el proceso origen y vuelve a referenciar el objeto subyacente en la tabla de handles del destino. Las banderas `Options` (DUPLICATE_SAME_ACCESS, DUPLICATE_SAME_ATTRIBUTES, DUPLICATE_CLOSE_SOURCE) son la parte más relevante para uso ofensivo: DUPLICATE_SAME_ACCESS evita el recorte de DACL en la duplicación, y DUPLICATE_CLOSE_SOURCE es la primitiva barata para robo de handle.
Uso común por malware
Dos patrones dominan. Primero, **contrabando de handles** entre procesos: un implante en un proceso poco privilegiado abre un objetivo más privilegiado con PROCESS_DUP_HANDLE y usa NtDuplicateObject para extraer de él un handle de token o de proceso (combínese con NtOpenProcessToken para cosechar tokens — ladrillo clásico del `steal_token` de Cobalt Strike). Segundo, **robo de handle para acceso a credenciales**: cuando un servicio de alta integridad ya posee un handle a LSASS (antimalware, EDR, lsm.exe en algunos casos), duplicar ese handle al proceso del atacante evita llamar a NtOpenProcess sobre lsass.exe — derrotando las detecciones PPL/EDR más comunes. Documentado en el truco de herencia de handles de Hidden Bee, en el modo `--use-valid-sig` de NanoDump, y abusado por Lazarus y FIN7. DUPLICATE_CLOSE_SOURCE lo usan también ransomware para cerrar a la fuerza locks de archivo de Office/SQL antes de cifrar.
Oportunidades de detección
Los EDR hookean NtDuplicateObject en ntdll para eventos de duplicación entre procesos; en el kernel, ObRegisterCallbacks (ObjectPreCallback / ObjectPostCallback) sobre PsProcessType y PsThreadType dispara independientemente del hooking usermode y es el pivote autoritativo. Sysmon Event ID 10 (ProcessAccess) reporta la apertura origen con acceso PROCESS_DUP_HANDLE, a menudo delatora si el origen es lsass.exe. Vigilar procesos abiertos con DUP_HANDLE pero nunca con VM_OPERATION/VM_READ — patrón clásico de contrabando de handle. ETW Microsoft-Windows-Kernel-Audit-API-Calls (GUID `e02a841c-75a3-4fa7-afc8-ae09cf9b7f23`) emite evento de duplicación con PID origen y destino.
Ejemplos de syscalls directos
asmx64 direct stub
; Direct syscall stub for NtDuplicateObject (SSN 0x3C, all builds)
NtDuplicateObject PROC
mov r10, rcx ; syscall convention
mov eax, 3Ch ; SSN
syscall
ret
NtDuplicateObject ENDPcToken-handle steal via DUPLICATE_SAME_ACCESS
// Pull a primary token handle out of a target process without ever
// calling NtOpenProcessToken on it directly. hTarget must have been
// opened with PROCESS_DUP_HANDLE.
#include <windows.h>
#define DUPLICATE_SAME_ACCESS 0x00000002
typedef NTSTATUS (NTAPI *pNtDuplicateObject)(
HANDLE, HANDLE, HANDLE, PHANDLE, ACCESS_MASK, ULONG, ULONG);
HANDLE StealTokenHandle(HANDLE hTargetProcess, HANDLE hRemoteToken) {
pNtDuplicateObject NtDuplicateObject = (pNtDuplicateObject)
GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtDuplicateObject");
HANDLE hLocal = NULL;
NTSTATUS s = NtDuplicateObject(
hTargetProcess, // SourceProcessHandle
hRemoteToken, // SourceHandle (token in target)
(HANDLE)-1, // TargetProcessHandle = self
&hLocal, // TargetHandle
0, 0,
DUPLICATE_SAME_ACCESS); // keep original ACCESS_MASK
return (s >= 0) ? hLocal : NULL;
}rustDUPLICATE_CLOSE_SOURCE handle eviction
// Cargo: ntapi = "0.4", windows-sys = "0.59"
// Force-close a file handle held by another process (e.g. break an
// exclusive lock before encryption / overwrite).
use ntapi::ntobapi::NtDuplicateObject;
use windows_sys::Win32::Foundation::HANDLE;
const DUPLICATE_CLOSE_SOURCE: u32 = 0x0000_0001;
pub unsafe fn evict_handle(target_proc: HANDLE, victim: HANDLE) {
let mut sink: HANDLE = std::ptr::null_mut();
let _ = NtDuplicateObject(
target_proc as _,
victim as _,
std::ptr::null_mut(), // TargetProcessHandle = NULL
&mut sink as *mut _ as _, // TargetHandle (discarded)
0, 0,
DUPLICATE_CLOSE_SOURCE,
);
}Mapeos MITRE ATT&CK
Last verified: 2026-05-20