> Windows Syscalls
ntoskrnl.exeT1003.001T1134.001T1106

NtDuplicateObject

Duplicates a handle from a source process into a target process, optionally adjusting access or closing the source.

Prototype

NTSTATUS NtDuplicateObject(
  HANDLE      SourceProcessHandle,
  HANDLE      SourceHandle,
  HANDLE      TargetProcessHandle,
  PHANDLE     TargetHandle,
  ACCESS_MASK DesiredAccess,
  ULONG       HandleAttributes,
  ULONG       Options
);

Arguments

NameTypeDirDescription
SourceProcessHandleHANDLEinHandle to the process owning SourceHandle. Needs PROCESS_DUP_HANDLE.
SourceHandleHANDLEinThe handle in the source process to duplicate.
TargetProcessHandleHANDLEinHandle to the destination process. NULL is allowed only when Options contains DUPLICATE_CLOSE_SOURCE.
TargetHandlePHANDLEoutReceives the new handle valid in the target process. Optional.
DesiredAccessACCESS_MASKinAccess mask for the new handle. Ignored if Options includes DUPLICATE_SAME_ACCESS.
HandleAttributesULONGinFlags such as OBJ_INHERIT or OBJ_PROTECT_CLOSE applied to the new handle.
OptionsULONGinDUPLICATE_CLOSE_SOURCE and/or DUPLICATE_SAME_ACCESS / DUPLICATE_SAME_ATTRIBUTES bitmask.

Syscall IDs by Windows version

Windows versionSyscall IDBuild
Win10 15070x3Cwin10-1507
Win10 16070x3Cwin10-1607
Win10 17030x3Cwin10-1703
Win10 17090x3Cwin10-1709
Win10 18030x3Cwin10-1803
Win10 18090x3Cwin10-1809
Win10 19030x3Cwin10-1903
Win10 19090x3Cwin10-1909
Win10 20040x3Cwin10-2004
Win10 20H20x3Cwin10-20h2
Win10 21H10x3Cwin10-21h1
Win10 21H20x3Cwin10-21h2
Win10 22H20x3Cwin10-22h2
Win11 21H20x3Cwin11-21h2
Win11 22H20x3Cwin11-22h2
Win11 23H20x3Cwin11-23h2
Win11 24H20x3Cwin11-24h2
Server 20160x3Cwinserver-2016
Server 20190x3Cwinserver-2019
Server 20220x3Cwinserver-2022
Server 20250x3Cwinserver-2025

Kernel module

ntoskrnl.exeNtDuplicateObject

Related APIs

DuplicateHandleNtOpenProcessNtOpenProcessTokenNtDuplicateTokenNtClose

Syscall stub

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

Undocumented notes

NtDuplicateObject carries SSN `0x3C` from Windows 10 1507 through Windows 11 24H2 — a rare flat curve in the table that makes it convenient for hardcoded direct-syscall stubs. Under the hood it routes through ObDuplicateObject which validates PROCESS_DUP_HANDLE on the source process and re-references the underlying object into the target's handle table. The `Options` flags (DUPLICATE_SAME_ACCESS, DUPLICATE_SAME_ATTRIBUTES, DUPLICATE_CLOSE_SOURCE) are the part most consequential for offensive use: DUPLICATE_SAME_ACCESS sidesteps DACL trimming at duplicate time, and DUPLICATE_CLOSE_SOURCE is the cheap primitive for handle theft.

Common malware usage

Two patterns dominate. First, **handle smuggling** between processes: an implant in a low-privilege process opens a more-privileged target with PROCESS_DUP_HANDLE and uses NtDuplicateObject to pull a token or process handle out of it (combine with NtOpenProcessToken to harvest tokens — a classic Cobalt Strike `steal_token` building block). Second, **handle theft for credential access**: when a high-integrity service already holds a LSASS handle (anti-malware product, EDR, lsm.exe in some cases), duplicating that handle into the attacker's process avoids ever calling NtOpenProcess on lsass.exe — defeating the most common PPL/EDR detection. Documented in Hidden Bee's handle-inheritance trick, NanoDump's `--use-valid-sig` mode, and abused by Lazarus and FIN7 implants for token enumeration. DUPLICATE_CLOSE_SOURCE is also used by ransomware to forcibly close file-lock handles held by Office/SQL processes before encryption.

Detection opportunities

EDRs hook NtDuplicateObject in ntdll for cross-process handle creation events; kernel-side ObRegisterCallbacks (ObjectPreCallback / ObjectPostCallback) on PsProcessType and PsThreadType fires regardless of user-mode hooking and is the authoritative pivot. Sysmon Event ID 10 (ProcessAccess) reports the source open with PROCESS_DUP_HANDLE access, often a tell when the source is lsass.exe. Look for ratios where a process is opened with DUP_HANDLE but never with VM_OPERATION/VM_READ — classic handle-smuggling. ETW Microsoft-Windows-Kernel-Audit-API-Calls (provider GUID `e02a841c-75a3-4fa7-afc8-ae09cf9b7f23`) emits a duplicate event with both source and target PIDs.

Direct syscall examples

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 ENDP

cToken-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,
    );
}

MITRE ATT&CK mappings

Last verified: 2026-05-20