NtOpenThread
Öffnet ein Handle auf einen vorhandenen Thread, identifiziert per CLIENT_ID, mit den gewünschten Zugriffsrechten.
Prototyp
NTSTATUS NtOpenThread( PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId );
Argumente
| Name | Type | Dir | Description |
|---|---|---|---|
| ThreadHandle | PHANDLE | out | Empfängt bei Erfolg das geöffnete Thread-Handle. |
| DesiredAccess | ACCESS_MASK | in | Kombination von THREAD_*-Rechten (z. B. THREAD_SUSPEND_RESUME, THREAD_GET_CONTEXT, THREAD_SET_CONTEXT, THREAD_ALL_ACCESS). |
| ObjectAttributes | POBJECT_ATTRIBUTES | in | Objektattribute. ObjectName ist üblicherweise NULL; OBJ_CASE_INSENSITIVE in Attributes setzen. |
| ClientId | PCLIENT_ID | in | Zeiger auf CLIENT_ID { UniqueProcess, UniqueThread }. UniqueProcess kann NULL sein, wenn UniqueThread systemweit eindeutig ist. |
Syscall-IDs pro Windows-Version
| Windows-Version | Syscall-ID | Build |
|---|---|---|
| Win10 1507 | 0x119 | win10-1507 |
| Win10 1607 | 0x11F | win10-1607 |
| Win10 1703 | 0x123 | win10-1703 |
| Win10 1709 | 0x125 | win10-1709 |
| Win10 1803 | 0x127 | win10-1803 |
| Win10 1809 | 0x128 | win10-1809 |
| Win10 1903 | 0x129 | win10-1903 |
| Win10 1909 | 0x129 | win10-1909 |
| Win10 2004 | 0x12E | win10-2004 |
| Win10 20H2 | 0x12E | win10-20h2 |
| Win10 21H1 | 0x12E | win10-21h1 |
| Win10 21H2 | 0x12F | win10-21h2 |
| Win10 22H2 | 0x12F | win10-22h2 |
| Win11 21H2 | 0x135 | win11-21h2 |
| Win11 22H2 | 0x137 | win11-22h2 |
| Win11 23H2 | 0x137 | win11-23h2 |
| Win11 24H2 | 0x139 | win11-24h2 |
| Server 2016 | 0x11F | winserver-2016 |
| Server 2019 | 0x128 | winserver-2019 |
| Server 2022 | 0x134 | winserver-2022 |
| Server 2025 | 0x139 | winserver-2025 |
Kernel-Modul
Verwandte APIs
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
Undokumentierte Hinweise
NtOpenThread ist der obligatorische erste Schritt für jede Cross-Thread-Manipulation ohne vorhandenes Handle. Die CLIENT_ID-Struktur macht es mächtig: ein Thread lässt sich allein über TID angeben (UniqueProcess = NULL), und der Kernel löst ihn systemweit auf. Die gewährte Access-Mask wird durch PsThreadType->ValidAccessMask und etwaige ObRegisterCallbacks gefiltert. Die meisten EDR-Produkte registrieren genau einen solchen Callback, um gefährliche Rechte (THREAD_SUSPEND_RESUME, THREAD_SET_CONTEXT) zu entfernen, wenn der Quellprozess nicht vertrauenswürdig ist.
Häufige Malware-Nutzung
Pflichtvorstufe nahezu jeder Thread-Level-Injection. Die interessante Frage ist *welche* Access-Mask angefordert wird — `THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME` ist die kanonische „ich werde dich hijacken“-Kombination. OPSEC-bewusste Loader fordern nur das Nötige: zuerst THREAD_QUERY_LIMITED_INFORMATION zum Enumerieren, dann erneutes Öffnen mit erhöhter Maske nur am gewählten Opfer-Thread. Early-Bird-APC-Varianten öffnen ALERTABLE-Threads, die per NtQuerySystemInformation(SystemProcessInformation) gefunden wurden.
Erkennungsmöglichkeiten
Sysmon Event ID 10 (ProcessAccess) ist das Pendant auf *Prozess*-Seite; für Threads ist das Äquivalent Microsoft-Windows-Kernel-Audit-API-Calls (Event ID 5), das beim zugrunde liegenden ObOpenObjectByPointer feuert. Der trennschärfste Filter ist die Access-Mask: legitime Opens fordern Cross-Process praktisch nie THREAD_SET_CONTEXT (0x10) an. EDRs hooken NtOpenThread üblicherweise per ObRegisterCallbacks (Kernel) und per Inline-Hook (User); Kernel-Callbacks sind aus User-Mode nicht umgehbar und bleiben die maßgebliche Quelle.
Direkte Syscall-Beispiele
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 ENDPrustIndirect 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