> Windows Syscalls
ntoskrnl.exeT1055.003T1057T1106

NtOpenThread

Ouvre un handle vers un thread existant identifié par CLIENT_ID avec les droits d'accès demandés.

Prototype

NTSTATUS NtOpenThread(
  PHANDLE            ThreadHandle,
  ACCESS_MASK        DesiredAccess,
  POBJECT_ATTRIBUTES ObjectAttributes,
  PCLIENT_ID         ClientId
);

Arguments

NameTypeDirDescription
ThreadHandlePHANDLEoutReçoit le handle de thread ouvert en cas de succès.
DesiredAccessACCESS_MASKinCombinaison de droits THREAD_* (ex. THREAD_SUSPEND_RESUME, THREAD_GET_CONTEXT, THREAD_SET_CONTEXT, THREAD_ALL_ACCESS).
ObjectAttributesPOBJECT_ATTRIBUTESinAttributs d'objet. ObjectName est typiquement NULL ; passez OBJ_CASE_INSENSITIVE dans Attributes.
ClientIdPCLIENT_IDinPointeur vers CLIENT_ID { UniqueProcess, UniqueThread }. UniqueProcess peut être NULL si UniqueThread est unique au niveau système.

IDs de syscalls par version de Windows

Version de WindowsID de syscallBuild
Win10 15070x119win10-1507
Win10 16070x11Fwin10-1607
Win10 17030x123win10-1703
Win10 17090x125win10-1709
Win10 18030x127win10-1803
Win10 18090x128win10-1809
Win10 19030x129win10-1903
Win10 19090x129win10-1909
Win10 20040x12Ewin10-2004
Win10 20H20x12Ewin10-20h2
Win10 21H10x12Ewin10-21h1
Win10 21H20x12Fwin10-21h2
Win10 22H20x12Fwin10-22h2
Win11 21H20x135win11-21h2
Win11 22H20x137win11-22h2
Win11 23H20x137win11-23h2
Win11 24H20x139win11-24h2
Server 20160x11Fwinserver-2016
Server 20190x128winserver-2019
Server 20220x134winserver-2022
Server 20250x139winserver-2025

Module noyau

ntoskrnl.exeNtOpenThread

APIs liées

OpenThreadNtGetNextThreadNtOpenProcessNtSuspendThreadNtGetContextThreadNtSetContextThread

Stub du syscall

4C 8B D1            mov r10, rcx
B8 39 01 00 00      mov eax, 0x139
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

NtOpenThread est l'étape obligée de toute manipulation inter-thread qui n'a pas déjà de handle. La structure CLIENT_ID en fait sa force : on peut spécifier un thread par TID seul (UniqueProcess = NULL) et le noyau le résout à l'échelle du système. Le masque d'accès accordé est filtré par PsThreadType->ValidAccessMask et tout ObRegisterCallbacks enregistré. La plupart des EDR enregistrent précisément un tel callback pour retirer les droits dangereux (THREAD_SUSPEND_RESUME, THREAD_SET_CONTEXT) lorsque le processus source n'est pas de confiance.

Usage courant par les malwares

Prélude obligatoire de presque toute injection au niveau thread. La question intéressante est *quel* masque d'accès est demandé — `THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME` est la combinaison canonique « je vais te détourner ». Les loaders soucieux d'OPSEC ne demandent que le strict nécessaire : THREAD_QUERY_LIMITED_INFORMATION d'abord pour énumérer, puis ré-ouverture avec le masque élevé uniquement sur le thread victime choisi. Les variantes d'Early Bird APC ouvrent les threads ALERTABLE trouvés via NtQuerySystemInformation(SystemProcessInformation).

Opportunités de détection

Sysmon Event ID 10 (ProcessAccess) est le signal correspondant côté *processus* ; pour les threads, l'équivalent est Microsoft-Windows-Kernel-Audit-API-Calls (Event ID 5) qui se déclenche sur le ObOpenObjectByPointer sous-jacent. Le filtre le plus discriminant est le masque d'accès : les ouvertures légitimes ne demandent presque jamais THREAD_SET_CONTEXT (0x10) inter-processus. Les EDR hookent couramment NtOpenThread via ObRegisterCallbacks (noyau) et via hook inline (user) ; les callbacks noyau sont incontournables depuis le user-mode et restent la source faisant foi.

Exemples de syscalls directs

cOpen by TID for hijack chain

CLIENT_ID cid = { .UniqueProcess = (HANDLE)(ULONG_PTR)pid,
                  .UniqueThread  = (HANDLE)(ULONG_PTR)tid };
OBJECT_ATTRIBUTES oa;
InitializeObjectAttributes(&oa, NULL, 0, NULL, NULL);

HANDLE hThread = NULL;
NTSTATUS st = NtOpenThread(
    &hThread,
    THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME,
    &oa,
    &cid);
if (!NT_SUCCESS(st)) return st;

asmx64 stub (Win11 24H2)

NtOpenThread PROC
    mov  r10, rcx
    mov  eax, 139h          ; Win11 24H2 SSN
    syscall
    ret
NtOpenThread ENDP

rustIndirect syscall (HellsHall)

// Resolve gate address from ntdll!NtOpenThread, then jump rather than execute syscall inline.
// Defeats inline-hook detection that whitelists ntdll-originating syscalls.
unsafe fn nt_open_thread(handle: *mut isize, access: u32,
                         oa: *mut ObjectAttributes, cid: *mut ClientId) -> i32 {
    let ssn: u32 = 0x139;            // Win11 24H2
    let gate: usize = find_syscall_insn("NtOpenThread");
    let r: i32;
    core::arch::asm!(
        "mov r10, rcx",
        "mov eax, {ssn:e}",
        "jmp {gate}",
        ssn = in(reg) ssn, gate = in(reg) gate,
        in("rcx") handle, in("rdx") access, in("r8") oa, in("r9") cid,
        lateout("rax") r,
    );
    r
}

Mappings MITRE ATT&CK

Last verified: 2026-05-20