Thread Hijacking
TL;DR: Suspend an existing thread in a target process, point its
RIPat injected shellcode viaSetThreadContext, then resume — code runs under a legitimate thread with noCreateRemoteThreadtelemetry.
What it is
A classic remote code-injection primitive that avoids CreateRemoteThread/NtCreateThreadEx (the calls every behavioural EDR watches). Instead of spawning a new thread, the attacker enumerates an existing thread, suspends it, rewrites the instruction pointer to the address of shellcode previously written via WriteProcessMemory, then resumes the thread. Execution looks like it originated from the victim process’s own thread.
Preconditions / where it applies
- Handle to the target process and thread with
PROCESS_VM_OPERATION | PROCESS_VM_WRITE | THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_SET_CONTEXT - Same integrity level (or SeDebugPrivilege — see tokens-and-privileges)
- Target process must have at least one thread the attacker can safely hijack (avoid GUI threads of foreground apps)
Technique
Enumerate threads with CreateToolhelp32Snapshot / Thread32Next, open one with OpenThread, allocate PAGE_EXECUTE_READ memory in the remote process, write the shellcode, then swap Rip and resume. Cleanly returning to original execution requires saving the stolen context or appending a small stub that restores it.
1
2
3
4
5
6
7
8
9
10
hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
buf = VirtualAllocEx(hProc, NULL, sizeof(sc), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(hProc, buf, sc, sizeof(sc), NULL);
hThr = OpenThread(THREAD_ALL_ACCESS, FALSE, tid);
SuspendThread(hThr);
CONTEXT ctx = { .ContextFlags = CONTEXT_FULL };
GetThreadContext(hThr, &ctx);
ctx.Rip = (DWORD64)buf;
SetThreadContext(hThr, &ctx);
ResumeThread(hThr);
OPSEC: allocate the buffer as PAGE_READWRITE first and re-protect to PAGE_EXECUTE_READ via VirtualProtectEx to dodge RWX heuristics. Picking a worker thread of svchost.exe is noisier than reusing a thread the attacker already controls — see process-injection-techniques and parent-pid-spoofing for related primitives.
Detection and defence
- Sysmon Event ID 8 (
CreateRemoteThread) does not fire — but ID 10 (ProcessAccess) with0x1F1FFForTHREAD_SET_CONTEXTcalls flag this - ETW
Microsoft-Windows-Threat-Intelligenceprovider raisesEtwThreatIntProvRegPSevents onNtSetContextThreadinto another process - EDRs hook
NtGetContextThread/NtSetContextThread; consider this when planning etw-bypass or syscall stubs - Tools like
Get-InjectedThread(Sela) detect threads whose start address is unbacked memory
References
- ired.team — Injecting to Remote Process via Thread Hijacking — original walkthrough
- Elastic — Hunting for Suspicious Windows Libraries — detection of unbacked-thread execution
- MITRE ATT&CK T1055.003 — Thread Execution Hijacking