NtSetContextThread
Setzt den CPU-Registerkontext eines Threads — die Kernel-Primitive hinter Thread-Hijacking und Shellcode-Umleitung.
Prototyp
NTSTATUS NtSetContextThread( HANDLE ThreadHandle, PCONTEXT ThreadContext );
Argumente
| Name | Type | Dir | Description |
|---|---|---|---|
| ThreadHandle | HANDLE | in | Handle auf den Ziel-Thread. Erfordert THREAD_SET_CONTEXT-Zugriff (und idealerweise THREAD_SUSPEND_RESUME). |
| ThreadContext | PCONTEXT | in | Vollständig befüllte architekturspezifische CONTEXT-Struktur (1232 B auf x64 plus optional XSTATE). ContextFlags wählt anzuwendende Registergruppen. |
Syscall-IDs pro Windows-Version
| Windows-Version | Syscall-ID | Build |
|---|---|---|
| Win10 1507 | 0x16F | win10-1507 |
| Win10 1607 | 0x178 | win10-1607 |
| Win10 1703 | 0x17E | win10-1703 |
| Win10 1709 | 0x181 | win10-1709 |
| Win10 1803 | 0x183 | win10-1803 |
| Win10 1809 | 0x184 | win10-1809 |
| Win10 1903 | 0x185 | win10-1903 |
| Win10 1909 | 0x185 | win10-1909 |
| Win10 2004 | 0x18B | win10-2004 |
| Win10 20H2 | 0x18B | win10-20h2 |
| Win10 21H1 | 0x18B | win10-21h1 |
| Win10 21H2 | 0x18D | win10-21h2 |
| Win10 22H2 | 0x18D | win10-22h2 |
| Win11 21H2 | 0x195 | win11-21h2 |
| Win11 22H2 | 0x198 | win11-22h2 |
| Win11 23H2 | 0x198 | win11-23h2 |
| Win11 24H2 | 0x19A | win11-24h2 |
| Server 2016 | 0x178 | winserver-2016 |
| Server 2019 | 0x184 | winserver-2019 |
| Server 2022 | 0x193 | winserver-2022 |
| Server 2025 | 0x19A | winserver-2025 |
Kernel-Modul
Verwandte APIs
Syscall-Stub
4C 8B D1 mov r10, rcx B8 9A 01 00 00 mov eax, 0x19A 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
Die wirkmächtige Hälfte des Kontext-Paares. Der Kernel validiert den CONTEXT — CS muss ein zulässiger User-Mode-Selektor sein, EFlags hat reservierte Bits maskiert, und auf CET-fähigen Systemen (Windows 10 20H2+ mit HW-Shadow-Stack) werden SSP/Ssp-Anpassungen von KeVerifyContextRecord saniert. Die CONTEXT-Struktur ist *architekturspezifisch*: 1232 Bytes auf x64, 716 auf x86, ~880 auf ARM64 — und Wow64SetThreadContext existiert, um x86-auf-x64 zu überbrücken, indem es das 32-Bit-CONTEXT in den entsprechenden WOW64-Bereich schreibt. Auf Builds mit erzwungenem CET können Schreibvorgänge auf Rip, die auf nicht CFG/IBT-getaggten Code zeigen, mit STATUS_SET_CONTEXT_DENIED fehlschlagen.
Häufige Malware-Nutzung
Die kanonische Shellcode-Injection-Primitive (T1055.003 Thread Execution Hijacking). Vollständige Sequenz: NtAllocateVirtualMemory(RWX) → NtWriteVirtualMemory(shellcode) → NtOpenThread → NtSuspendThread → NtGetContextThread → ctx.Rip = shellcode → NtSetContextThread → NtResumeThread. Varianten: (a) Early Bird — eine APC einreihen, dann Kontext setzen, um sie beim ersten alertablen Wait auszulösen. (b) Hardware-Breakpoint-Hooking — DR0..DR3 mit Adressen von Zielfunktionen und DR7 mit Steuerbits schreiben, dann die entstehenden #DB-Exceptions in einem VEH-Handler verarbeiten, um Userland-Hooks zu implementieren, die für Inline-Hook-Scanner unsichtbar sind. (c) CET-Bypass-Versuche, die XSTATE-/Shadow-Stack-Pointer-Felder missbrauchen.
Erkennungsmöglichkeiten
NtSetContextThread auf einen Remote-Thread ist eines der signalstärksten Ereignisse in Windows. Microsoft-Windows-Threat-Intelligence-ETW exponiert EtwTiLogSetContextThread mit Quell-/Ziel-PID und neuem Rip — auf jeden Cross-Process-Aufruf alarmieren, dessen neues Rip auf private (nicht image-backed) Memory zeigt. EDRs hooken üblicherweise im User-Mode per Inline-Patch in ntdll!NtSetContextThread; direkte Syscalls umgehen das, TI-ETW bleibt. CET / Shadow-Stack auf unterstützten CPUs macht das klassische Rip-Umschreiben weniger zuverlässig und bringt fehlgeschlagene Versuche als Bugchecks vom Typ KERNEL_SECURITY_CHECK_FAILURE zu Tage, wenn der Angreifer den neuen Kontext fehlerhaft konfiguriert.
Direkte Syscall-Beispiele
cClassic SetThreadContext shellcode hijack
// Assumes shellcode already written to remoteShellcode in target process.
CONTEXT ctx = { .ContextFlags = CONTEXT_FULL };
NtSuspendThread(hThread, NULL);
NtGetContextThread(hThread, &ctx);
ctx.Rip = (DWORD64)remoteShellcode; // redirect on resume
NtSetContextThread(hThread, &ctx);
NtResumeThread(hThread, NULL);cHardware-breakpoint hook install
// Install a userland hook on a target function via DR0 + #DB VEH handler.
CONTEXT ctx = { .ContextFlags = CONTEXT_DEBUG_REGISTERS };
NtGetContextThread(hTargetThread, &ctx);
ctx.Dr0 = (DWORD64)pTargetFn;
ctx.Dr7 = (1ULL << 0) /* L0 */ |
(0ULL << 16) /* RW0=00 execute */ |
(0ULL << 18) /* LEN0=00 1-byte */;
NtSetContextThread(hTargetThread, &ctx);
// AddVectoredExceptionHandler then catches EXCEPTION_SINGLE_STEP at pTargetFn.rustx64 stub (Win11 24H2)
use std::arch::asm;
#[unsafe(naked)]
unsafe extern "system" fn nt_set_context_thread_stub() {
asm!(
"mov r10, rcx",
"mov eax, 0x19A", // Win11 24H2 SSN
"syscall",
"ret",
options(noreturn),
);
}MITRE ATT&CK-Mappings
Last verified: 2026-05-20