> Windows Syscalls
ntoskrnl.exeT1055.003T1057T1106

NtOpenThread

Opens a handle to an existing thread identified by CLIENT_ID with requested access rights.

Prototype

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

Arguments

NameTypeDirDescription
ThreadHandlePHANDLEoutReceives the opened thread handle on success.
DesiredAccessACCESS_MASKinCombination of THREAD_* rights (e.g. THREAD_SUSPEND_RESUME, THREAD_GET_CONTEXT, THREAD_SET_CONTEXT, THREAD_ALL_ACCESS).
ObjectAttributesPOBJECT_ATTRIBUTESinObject attributes. ObjectName is typically NULL; pass OBJ_CASE_INSENSITIVE in Attributes.
ClientIdPCLIENT_IDinPointer to CLIENT_ID { UniqueProcess, UniqueThread }. UniqueProcess may be NULL when UniqueThread is system-wide unique.

Syscall IDs by Windows version

Windows versionSyscall IDBuild
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

Kernel module

ntoskrnl.exeNtOpenThread

Related APIs

OpenThreadNtGetNextThreadNtOpenProcessNtSuspendThreadNtGetContextThreadNtSetContextThread

Syscall stub

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

Undocumented notes

NtOpenThread is the obligatory first step for any cross-thread manipulation that does not already have a handle. The CLIENT_ID structure is what makes it powerful: you can specify a thread by TID alone (UniqueProcess = NULL) and the kernel resolves it across the system. The granted access mask is filtered by PsThreadType->ValidAccessMask and any registered ObRegisterCallbacks. Most EDR products register precisely such a callback to strip dangerous rights (THREAD_SUSPEND_RESUME, THREAD_SET_CONTEXT) when the source process is not trusted.

Common malware usage

Mandatory precursor for nearly every thread-level injection technique. The interesting question is *which* access mask is requested — `THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME` (0x2A in classic terms, 0x42 in extended) is the canonical "I am about to hijack you" combination. More OPSEC-aware loaders request only what they need: THREAD_QUERY_LIMITED_INFORMATION first to enumerate, then re-open with the elevated mask only on the chosen victim thread. Variants of Early Bird APC injection open ALERTABLE threads found via NtQuerySystemInformation(SystemProcessInformation).

Detection opportunities

Sysmon Event ID 10 (ProcessAccess) is the corresponding signal for the *process* side; for threads, the equivalent is Microsoft-Windows-Kernel-Audit-API-Calls (Event ID 5) which fires on the underlying ObOpenObjectByPointer. The most discriminating filter is the access mask: legitimate opens almost never request THREAD_SET_CONTEXT (0x10) cross-process. EDRs commonly hook NtOpenThread via ObRegisterCallbacks (kernel) and via inline hook (user); kernel callbacks are unbypassable from user-mode and remain the authoritative source.

Direct syscall examples

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
}

MITRE ATT&CK mappings

Last verified: 2026-05-20