NtDuplicateObject
Duplique un handle d'un processus source vers un processus cible, avec ajustement éventuel des accès ou fermeture de la source.
Prototype
NTSTATUS NtDuplicateObject( HANDLE SourceProcessHandle, HANDLE SourceHandle, HANDLE TargetProcessHandle, PHANDLE TargetHandle, ACCESS_MASK DesiredAccess, ULONG HandleAttributes, ULONG Options );
Arguments
| Name | Type | Dir | Description |
|---|---|---|---|
| SourceProcessHandle | HANDLE | in | Handle vers le processus possédant SourceHandle. Nécessite PROCESS_DUP_HANDLE. |
| SourceHandle | HANDLE | in | Le handle dans le processus source à dupliquer. |
| TargetProcessHandle | HANDLE | in | Handle vers le processus destination. NULL n'est autorisé qu'avec DUPLICATE_CLOSE_SOURCE dans Options. |
| TargetHandle | PHANDLE | out | Reçoit le nouveau handle valide dans le processus cible. Facultatif. |
| DesiredAccess | ACCESS_MASK | in | Masque d'accès pour le nouveau handle. Ignoré si Options contient DUPLICATE_SAME_ACCESS. |
| HandleAttributes | ULONG | in | Drapeaux comme OBJ_INHERIT ou OBJ_PROTECT_CLOSE appliqués au nouveau handle. |
| Options | ULONG | in | Combinaison de DUPLICATE_CLOSE_SOURCE et/ou DUPLICATE_SAME_ACCESS / DUPLICATE_SAME_ATTRIBUTES. |
IDs de syscalls par version de Windows
| Version 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 |
Module noyau
APIs liées
Stub du 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
Notes non documentées
NtDuplicateObject porte le SSN `0x3C` de Windows 10 1507 à Windows 11 24H2 — courbe plate rare dans la table, ce qui rend les stubs syscall directs hardcodés faciles. En interne il passe par ObDuplicateObject qui valide PROCESS_DUP_HANDLE sur le processus source et re-référence l'objet sous-jacent dans la table de handles de la cible. Les drapeaux `Options` (DUPLICATE_SAME_ACCESS, DUPLICATE_SAME_ATTRIBUTES, DUPLICATE_CLOSE_SOURCE) sont la partie la plus exploitable côté offensif : DUPLICATE_SAME_ACCESS contourne le rognage de DACL au moment de la duplication, et DUPLICATE_CLOSE_SOURCE est la primitive bon marché pour le vol de handle.
Usage courant par les malwares
Deux schémas dominent. D'abord, **contrebande de handle** entre processus : un implant dans un processus peu privilégié ouvre une cible plus privilégiée avec PROCESS_DUP_HANDLE et utilise NtDuplicateObject pour en extraire un handle de token ou de processus (à combiner avec NtOpenProcessToken pour aspirer des tokens — brique classique du `steal_token` de Cobalt Strike). Ensuite, **vol de handle pour accès aux credentials** : quand un service haut-intégrité détient déjà un handle vers LSASS (antimalware, EDR, lsm.exe dans certains cas), dupliquer ce handle dans le processus de l'attaquant évite tout appel à NtOpenProcess sur lsass.exe — ce qui défait les détections PPL/EDR les plus courantes. Documenté dans le trick d'héritage de handles de Hidden Bee, le mode `--use-valid-sig` de NanoDump, et abusé par Lazarus et FIN7. DUPLICATE_CLOSE_SOURCE sert aussi aux ransomwares pour forcer la fermeture des verrous fichier détenus par Office/SQL avant chiffrement.
Opportunités de détection
Les EDR hookent NtDuplicateObject dans ntdll pour les événements de duplication inter-processus ; côté noyau, ObRegisterCallbacks (ObjectPreCallback / ObjectPostCallback) sur PsProcessType et PsThreadType se déclenche indépendamment du hooking usermode et reste le pivot autorisé. Sysmon Event ID 10 (ProcessAccess) signale l'ouverture source avec accès PROCESS_DUP_HANDLE, souvent révélatrice si la source est lsass.exe. Surveiller les processus ouverts en DUP_HANDLE mais jamais en VM_OPERATION/VM_READ — schéma classique de contrebande de handle. ETW Microsoft-Windows-Kernel-Audit-API-Calls (GUID `e02a841c-75a3-4fa7-afc8-ae09cf9b7f23`) émet un événement de duplication avec PID source et cible.
Exemples de syscalls directs
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,
);
}Mappings MITRE ATT&CK
Last verified: 2026-05-20