Может кому пригодится в тестах. Не пропадать же кодесу) Код (C++): #include <iostream> #include <Windows.h> #include <psapi.h> #include <TlHelp32.h> #include <iomanip> // For std::setw and other formatting #include <string> #include "cfg_exception.h" // Configuration settings // ====================================================================== // Path to the target executable constexpr const char* TARGET_EXECUTABLE_PATH = "loader.exe"; // Wait times (in milliseconds) constexpr DWORD PROCESS_INIT_WAIT_TIME = 3000; // Time to wait for process initialization constexpr DWORD PROCESS_OUTPUT_WAIT_TIME = 10000; // Time to wait for process output constexpr DWORD PROGRESS_CHECK_INTERVAL = 1000; // Interval to check process status // Memory settings constexpr SIZE_T DEFAULT_PAGE_SIZE = 4096; // Standard memory page size constexpr SIZE_T MIN_REGION_SIZE = 16; // Minimum region size for CFG // CFG Target settings constexpr const char* TARGET_FUNCTION = "NtSetContextThread"; // Function to add CFG exception for constexpr const char* TARGET_MODULE = "ntdll.dll"; // Module containing the function // ====================================================================== // Logger class to help with debug output class Logger { public: enum LogLevel { INFO, WARNING, ERROR_CASE, DEBUG }; static void Log(LogLevel level, const std::string &message) { std::string prefix; switch (level) { case INFO: prefix = "[INFO] "; break; case WARNING: prefix = "[WARN] "; break; case ERROR_CASE: prefix = "[ERROR] "; break; case DEBUG: prefix = "[DEBUG] "; break; } std::cout << prefix << message << std::endl; } static void LogError(const std::string &message, DWORD errorCode) { char errorBuffer[256]; FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM, NULL, errorCode, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), // Use English locale errorBuffer, sizeof(errorBuffer), NULL); std::string errorMsg = message + " Error code: " + std::to_string(errorCode) + " (" + std::string(errorBuffer) + ")"; Log(ERROR_CASE, errorMsg); } }; // Check if the process is running with administrator privileges BOOL IsElevated() { BOOL fIsElevated = FALSE; HANDLE hToken = NULL; if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) { TOKEN_ELEVATION elevation; DWORD cbSize = sizeof(TOKEN_ELEVATION); if (GetTokenInformation(hToken, TokenElevation, &elevation, sizeof(elevation), &cbSize)) { fIsElevated = elevation.TokenIsElevated; } } if (hToken) { CloseHandle(hToken); } return fIsElevated; } // Implementation of helper function BOOL GetMemoryAllocationBaseAndRegionSize(PVOID pvAddress, PVOID *ppvAllocationBase, PSIZE_T pstRegionSize) { MEMORY_BASIC_INFORMATION tMemoryBasicInformation = {0}; SIZE_T queryResult = VirtualQuery(pvAddress, &tMemoryBasicInformation, sizeof(tMemoryBasicInformation)); if (0 == queryResult) { return FALSE; } *ppvAllocationBase = tMemoryBasicInformation.BaseAddress; *pstRegionSize = tMemoryBasicInformation.RegionSize; return TRUE; } // Get module base address in a process HMODULE GetRemoteModuleHandle(DWORD processId, const char *moduleName) { MODULEENTRY32W moduleEntry = {0}; moduleEntry.dwSize = sizeof(MODULEENTRY32W); HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, processId); if (snapshot == INVALID_HANDLE_VALUE) { std::cerr << "Failed to create module snapshot. Error: " << GetLastError() << std::endl; return NULL; } BOOL result = Module32FirstW(snapshot, &moduleEntry); while (result) { // Convert wide string to narrow string for comparison char moduleNameA[MAX_PATH]; WideCharToMultiByte(CP_ACP, 0, moduleEntry.szModule, -1, moduleNameA, MAX_PATH, NULL, NULL); if (_stricmp(moduleNameA, moduleName) == 0) { CloseHandle(snapshot); return moduleEntry.hModule; } result = Module32NextW(snapshot, &moduleEntry); } CloseHandle(snapshot); return NULL; } // Two-step process creation and module enumeration LONG AddCfgExceptionToTestProcess() { Logger::Log(Logger::INFO, "Starting CFG exception addition process..."); STARTUPINFOA startupInfo = {0}; PROCESS_INFORMATION processInfo = {0}; LONG result = ERROR_SUCCESS; HMODULE hNtdll = nullptr; PVOID targetAddress = nullptr; PVOID allocationBase = nullptr; SIZE_T regionSize = 0; SETPROCESSVALIDCALLTARGETS pSetProcessValidCallTargets = nullptr; CFG_CALL_TARGET_INFO cfgCallTargetInfo = {0}; // Get SetProcessValidCallTargets function pointer Logger::Log(Logger::INFO, "Loading required system libraries..."); HMODULE hKernelbase = LoadLibraryW(L"Kernelbase.dll"); if (!hKernelbase) { DWORD error = GetLastError(); Logger::LogError("Failed to load Kernelbase.dll", error); return ERROR_MOD_NOT_FOUND; } pSetProcessValidCallTargets = reinterpret_cast<SETPROCESSVALIDCALLTARGETS>( GetProcAddress(hKernelbase, "SetProcessValidCallTargets")); if (!pSetProcessValidCallTargets) { DWORD error = GetLastError(); Logger::LogError("Failed to get SetProcessValidCallTargets function pointer", error); FreeLibrary(hKernelbase); return ERROR_PROC_NOT_FOUND; } Logger::Log(Logger::INFO, "Successfully loaded required libraries and functions"); // Step 1: First create the process normally to allow it to initialize Logger::Log(Logger::INFO, "Creating process from specified path..."); startupInfo.cb = sizeof(startupInfo); // Create the process suspended to prevent it from terminating too quickly if (!CreateProcessA(NULL, (LPSTR)TARGET_EXECUTABLE_PATH, NULL, NULL, FALSE, CREATE_SUSPENDED | CREATE_NEW_CONSOLE, // Start suspended NULL, NULL, &startupInfo, &processInfo)) { DWORD error = GetLastError(); Logger::LogError("Failed to create process", error); FreeLibrary(hKernelbase); return error; } Logger::Log(Logger::INFO, "Process created with PID: " + std::to_string(processInfo.dwProcessId)); // Check if process is still active before continuing DWORD waitResult = WaitForSingleObject(processInfo.hProcess, 0); if (waitResult == WAIT_OBJECT_0) { Logger::Log(Logger::ERROR_CASE, "Process terminated immediately after creation"); CloseHandle(processInfo.hThread); CloseHandle(processInfo.hProcess); FreeLibrary(hKernelbase); return ERROR_PROCESS_ABORTED; } // Resume the process from suspended state ResumeThread(processInfo.hThread); // Give the process more time to initialize fully Logger::Log(Logger::INFO, "Waiting for process to initialize (" + std::to_string(PROCESS_INIT_WAIT_TIME/1000) + " seconds)..."); Sleep(PROCESS_INIT_WAIT_TIME); // Wait for initialization // Check again if process is still running waitResult = WaitForSingleObject(processInfo.hProcess, 0); if (waitResult == WAIT_OBJECT_0) { Logger::Log(Logger::WARNING, "Process terminated before we could analyze it"); CloseHandle(processInfo.hThread); CloseHandle(processInfo.hProcess); FreeLibrary(hKernelbase); return ERROR_PROCESS_ABORTED; } // Step 2: Now get module information Logger::Log(Logger::INFO, "Attempting to create module snapshot..."); HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, processInfo.dwProcessId); if (snapshot == INVALID_HANDLE_VALUE) { DWORD error = GetLastError(); Logger::LogError("Failed to create module snapshot", error); TerminateProcess(processInfo.hProcess, 0); CloseHandle(processInfo.hThread); CloseHandle(processInfo.hProcess); FreeLibrary(hKernelbase); return error; } Logger::Log(Logger::INFO, "Successfully created module snapshot"); // Временно приостанавливаем процесс и даем возможность подключить отладчик if (SuspendThread(processInfo.hThread) == -1) { DWORD error = GetLastError(); Logger::LogError("Failed to suspend process for debugging", error); } else { Logger::Log(Logger::INFO, "================================================="); Logger::Log(Logger::INFO, "Process is now SUSPENDED and ready for debugging"); Logger::Log(Logger::INFO, "Process ID (PID): " + std::to_string(processInfo.dwProcessId)); Logger::Log(Logger::INFO, "You can now attach a debugger to this process"); Logger::Log(Logger::INFO, "For WinDbg: .attach " + std::to_string(processInfo.dwProcessId)); Logger::Log(Logger::INFO, "For Visual Studio: Debug > Attach to Process..."); Logger::Log(Logger::INFO, "================================================="); Logger::Log(Logger::INFO, "Press Enter to continue execution..."); std::cin.get(); // Возобновляем процесс после подключения отладчика ResumeThread(processInfo.hThread); Logger::Log(Logger::INFO, "Process resumed. Continuing with CFG exception setup..."); } // Step 3: Find ntdll.dll in the snapshot Logger::Log(Logger::INFO, "Searching for ntdll.dll in process modules..."); MODULEENTRY32W moduleEntry = {0}; moduleEntry.dwSize = sizeof(MODULEENTRY32W); BOOL moduleResult = Module32FirstW(snapshot, &moduleEntry); bool found = false; while (moduleResult) { // Convert wide string to narrow string for logging and comparison char moduleNameA[MAX_PATH]; WideCharToMultiByte(CP_ACP, 0, moduleEntry.szModule, -1, moduleNameA, MAX_PATH, NULL, NULL); Logger::Log(Logger::DEBUG, "Found module: " + std::string(moduleNameA)); if (_stricmp(moduleNameA, TARGET_MODULE) == 0) { Logger::Log(Logger::INFO, "Found ntdll.dll at address: " + std::to_string(reinterpret_cast<uintptr_t>(moduleEntry.modBaseAddr))); hNtdll = moduleEntry.hModule; found = true; break; } moduleResult = Module32NextW(snapshot, &moduleEntry); } CloseHandle(snapshot); if (!found) { Logger::Log(Logger::ERROR_CASE, "Could not find ntdll.dll in the process modules"); TerminateProcess(processInfo.hProcess, 0); CloseHandle(processInfo.hThread); CloseHandle(processInfo.hProcess); FreeLibrary(hKernelbase); return ERROR_MOD_NOT_FOUND; } // Step 4: Now suspend the process for our modifications Logger::Log(Logger::INFO, "Suspending the process..."); if (SuspendThread(processInfo.hThread) == -1) { DWORD error = GetLastError(); Logger::LogError("Failed to suspend process", error); TerminateProcess(processInfo.hProcess, 0); CloseHandle(processInfo.hThread); CloseHandle(processInfo.hProcess); FreeLibrary(hKernelbase); return error; } // Check if CFG is enabled for the process PROCESS_MITIGATION_CONTROL_FLOW_GUARD_POLICY cfgPolicy = { 0 }; BOOL isCfgEnabled = FALSE; if (GetProcessMitigationPolicy(processInfo.hProcess, ProcessControlFlowGuardPolicy, &cfgPolicy, sizeof(cfgPolicy))) { isCfgEnabled = cfgPolicy.EnableControlFlowGuard; Logger::Log(Logger::INFO, "CFG status in target process: " + std::string(isCfgEnabled ? "ENABLED" : "DISABLED")); if (!isCfgEnabled) { Logger::Log(Logger::WARNING, "CFG is not enabled in target process. Exceptions may not work."); } } else { DWORD error = GetLastError(); Logger::LogError("Failed to query CFG status", error); } // Get address of NtSetContextThread in our process to calculate offset Logger::Log(Logger::INFO, "Loading ntdll.dll in the current process..."); HMODULE hLocalNtdll = LoadLibraryW(L"ntdll.dll"); if (!hLocalNtdll) { DWORD error = GetLastError(); Logger::LogError("Failed to load ntdll.dll locally", error); ResumeThread(processInfo.hThread); // Resume before exiting TerminateProcess(processInfo.hProcess, 0); CloseHandle(processInfo.hThread); CloseHandle(processInfo.hProcess); FreeLibrary(hKernelbase); return error; } Logger::Log(Logger::INFO, "Getting address of NtSetContextThread..."); PVOID localFuncAddress = GetProcAddress(hLocalNtdll, TARGET_FUNCTION); if (!localFuncAddress) { DWORD error = GetLastError(); Logger::LogError("Failed to get address of NtSetContextThread", error); FreeLibrary(hLocalNtdll); ResumeThread(processInfo.hThread); TerminateProcess(processInfo.hProcess, 0); CloseHandle(processInfo.hThread); CloseHandle(processInfo.hProcess); FreeLibrary(hKernelbase); return ERROR_PROC_NOT_FOUND; } // Calculate the offset from ntdll base ULONG_PTR offset = (ULONG_PTR)localFuncAddress - (ULONG_PTR)hLocalNtdll; Logger::Log(Logger::INFO, "Function offset from ntdll base: 0x" + std::to_string(offset)); // Calculate the target address in the target process targetAddress = (PVOID)((ULONG_PTR)hNtdll + offset); Logger::Log(Logger::INFO, "Target function address in target process: " + std::to_string(reinterpret_cast<uintptr_t>(targetAddress))); // Get allocation base and region size with VirtualQueryEx Logger::Log(Logger::INFO, "Getting memory information for target function..."); MEMORY_BASIC_INFORMATION memInfo = {0}; HANDLE hTargetProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_SET_INFORMATION, FALSE, processInfo.dwProcessId); if (!hTargetProcess) { DWORD error = GetLastError(); Logger::LogError("Failed to open target process for memory operations", error); FreeLibrary(hLocalNtdll); ResumeThread(processInfo.hThread); TerminateProcess(processInfo.hProcess, 0); CloseHandle(processInfo.hThread); CloseHandle(processInfo.hProcess); FreeLibrary(hKernelbase); return error; } SIZE_T queryResult = VirtualQueryEx(hTargetProcess, targetAddress, &memInfo, sizeof(memInfo)); if (queryResult == 0) { DWORD error = GetLastError(); Logger::LogError("Failed to query remote memory", error); CloseHandle(hTargetProcess); FreeLibrary(hLocalNtdll); ResumeThread(processInfo.hThread); TerminateProcess(processInfo.hProcess, 0); CloseHandle(processInfo.hThread); CloseHandle(processInfo.hProcess); FreeLibrary(hKernelbase); return error; } allocationBase = memInfo.AllocationBase; regionSize = memInfo.RegionSize; // Additional validation for memory type and protection if (!(memInfo.Type & MEM_IMAGE)) { Logger::Log(Logger::ERROR_CASE, "Target memory is not an executable image (MEM_IMAGE)"); CloseHandle(hTargetProcess); FreeLibrary(hLocalNtdll); ResumeThread(processInfo.hThread); TerminateProcess(processInfo.hProcess, 0); CloseHandle(processInfo.hThread); CloseHandle(processInfo.hProcess); FreeLibrary(hKernelbase); return ERROR_INVALID_ADDRESS; } // Check if the memory region has execute permissions BOOL hasExecutePermission = memInfo.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY); if (!hasExecutePermission) { Logger::Log(Logger::WARNING, "Target memory region does not have execute permissions"); } Logger::Log(Logger::INFO, "Memory information retrieved:"); Logger::Log(Logger::INFO, " Allocation base: " + std::to_string(reinterpret_cast<uintptr_t>(allocationBase))); Logger::Log(Logger::INFO, " Region size: " + std::to_string(regionSize)); Logger::Log(Logger::INFO, " Protection: 0x" + std::to_string(memInfo.Protect)); Logger::Log(Logger::INFO, " Type: 0x" + std::to_string(memInfo.Type)); // Configure CFG exception Logger::Log(Logger::INFO, "Configuring CFG exception..."); // Calculate the relative offset from allocation base ULONG_PTR relativeOffset = static_cast<ULONG_PTR>( reinterpret_cast<ULONG_PTR>(targetAddress) - reinterpret_cast<ULONG_PTR>(allocationBase)); // Setup the call target info structure cfgCallTargetInfo.Offset = relativeOffset; cfgCallTargetInfo.Flags = CFG_CALL_TARGET_VALID; // Simplified flag usage Logger::Log(Logger::INFO, "Setting CFG exception with offset: 0x" + std::to_string(cfgCallTargetInfo.Offset)); Logger::Log(Logger::INFO, "Target address: 0x" + std::to_string(reinterpret_cast<uintptr_t>(targetAddress))); Logger::Log(Logger::INFO, "Allocation base: 0x" + std::to_string(reinterpret_cast<uintptr_t>(allocationBase))); // Try different approaches to add the CFG exception Logger::Log(Logger::INFO, "Calling SetProcessValidCallTargets with allocation base..."); // First approach: use allocation base with the full region size if (!pSetProcessValidCallTargets(hTargetProcess, allocationBase, regionSize, 1, &cfgCallTargetInfo)) { result = GetLastError(); Logger::LogError("First approach failed", result); // Second approach: Try with page size aligned region SIZE_T pageSize = DEFAULT_PAGE_SIZE; PVOID pageAlignedAddress = (PVOID)(((ULONG_PTR)targetAddress) & ~(pageSize - 1)); SIZE_T pageAlignedSize = pageSize; // Adjust offset for page aligned address cfgCallTargetInfo.Offset = (ULONG_PTR)targetAddress - (ULONG_PTR)pageAlignedAddress; Logger::Log(Logger::INFO, "Trying with page aligned address: 0x" + std::to_string(reinterpret_cast<uintptr_t>(pageAlignedAddress))); Logger::Log(Logger::INFO, "Adjusted offset: 0x" + std::to_string(cfgCallTargetInfo.Offset)); if (!pSetProcessValidCallTargets(hTargetProcess, pageAlignedAddress, pageAlignedSize, 1, &cfgCallTargetInfo)) { result = GetLastError(); Logger::LogError("Second approach failed", result); // Third approach: Try directly at target with minimal region and no offset cfgCallTargetInfo.Offset = 0; Logger::Log(Logger::INFO, "Trying direct target approach..."); if (!pSetProcessValidCallTargets(hTargetProcess, targetAddress, MIN_REGION_SIZE, 1, &cfgCallTargetInfo)) { result = GetLastError(); Logger::LogError("Third approach failed", result); // Fourth approach: Allocate our own memory and try setting CFG on that Logger::Log(Logger::INFO, "Attempting to create our own allocation for CFG testing..."); // Allocate executable memory in the target process SIZE_T allocSize = pageSize; PVOID testMemory = VirtualAllocEx( hTargetProcess, NULL, allocSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE ); if (testMemory) { Logger::Log(Logger::INFO, "Created test allocation at: 0x" + std::to_string(reinterpret_cast<uintptr_t>(testMemory))); // Write a simple RET instruction to the memory (0xC3) BYTE retInstr = 0xC3; SIZE_T bytesWritten; if (WriteProcessMemory(hTargetProcess, testMemory, &retInstr, sizeof(retInstr), &bytesWritten)) { Logger::Log(Logger::INFO, "Wrote RET instruction to test memory"); // Try setting CFG on our own allocation cfgCallTargetInfo.Offset = 0; // Start of our allocation cfgCallTargetInfo.Flags = CFG_CALL_TARGET_VALID; if (!pSetProcessValidCallTargets(hTargetProcess, testMemory, allocSize, 1, &cfgCallTargetInfo)) { result = GetLastError(); Logger::LogError("Fourth approach failed on custom allocation", result); } else { result = ERROR_SUCCESS; Logger::Log(Logger::INFO, "Successfully set CFG exception on our custom allocation!"); } } else { DWORD error = GetLastError(); Logger::LogError("Failed to write to test memory", error); } } else { DWORD error = GetLastError(); Logger::LogError("Failed to allocate test memory", error); } } else { result = ERROR_SUCCESS; Logger::Log(Logger::INFO, "Successfully set CFG exception using third approach!"); } } else { result = ERROR_SUCCESS; Logger::Log(Logger::INFO, "Successfully set CFG exception using second approach!"); } } else { result = ERROR_SUCCESS; Logger::Log(Logger::INFO, "Successfully set CFG exception using first approach!"); } // Clean up FreeLibrary(hLocalNtdll); CloseHandle(hTargetProcess); // Resume and let the process run normally if (result == ERROR_SUCCESS) { Logger::Log(Logger::INFO, "Resuming process and waiting for any output..."); ResumeThread(processInfo.hThread); // Подождите немного дольше, чтобы увидеть любую активность DWORD waitTime = PROCESS_OUTPUT_WAIT_TIME; Logger::Log(Logger::INFO, "Waiting up to 10 seconds for process to produce output..."); // Периодически проверяйте, жив ли процесс for (int i = 0; i < 10; i++) { Sleep(PROGRESS_CHECK_INTERVAL); DWORD exitCode = 0; if (GetExitCodeProcess(processInfo.hProcess, &exitCode) && exitCode != STILL_ACTIVE) { Logger::Log(Logger::INFO, "Process terminated with exit code: " + std::to_string(exitCode)); break; } Logger::Log(Logger::DEBUG, "Process still active: " + std::to_string(i+1) + " seconds elapsed"); } Logger::Log(Logger::INFO, "Press Enter to terminate the process and exit..."); std::cin.get(); TerminateProcess(processInfo.hProcess, 0); } else { Logger::Log(Logger::INFO, "Terminating process due to error..."); ResumeThread(processInfo.hThread); // Resume before terminating TerminateProcess(processInfo.hProcess, 0); } CloseHandle(processInfo.hThread); CloseHandle(processInfo.hProcess); FreeLibrary(hKernelbase); return result; } int main() { // Check for administrator privileges if (!IsElevated()) { std::cerr << "ERROR: This application requires administrator privileges." << std::endl; std::cerr << "Please right-click and select 'Run as administrator'." << std::endl; return ERROR_ACCESS_DENIED; } Logger::Log(Logger::INFO, "Starting CFG Exception Utility..."); LONG result = AddCfgExceptionToTestProcess(); if (result == ERROR_SUCCESS) { Logger::Log(Logger::INFO, "CFG exception operation completed successfully"); } else { Logger::Log(Logger::ERROR_CASE, "Failed to add CFG exception. Error code: " + std::to_string(result)); } return result; }
Решил подождать немного, ничего не писать сюда не ходить, с головой были трудности А как это работает ?
Вы уже как Эминем в его прайм-тайм Утилита работает следующим образом: сначала создаётся целевой процесс (указанный как "loader.exe") в приостановленном состоянии, затем определяется адрес функции NtSetContextThread внутри этого процесса. После этого программа пробует несколько подходов для добавления исключения CFG для этого адреса, используя функцию Windows API SetProcessValidCallTargets. --- Сообщение объединено, 1 апр 2025 в 00:03 --- Это чисто для души потестить. --- Сообщение объединено, 1 апр 2025 в 00:34 --- этому парню пригодился бы кодес --- Сообщение объединено, 1 апр 2025 в 00:45 --- Из демки выходит, что смена eip для cfg безопасна. Если cfg чёто не понрав то вы точно узнаете.
Тут что то довольно трудно найти, поиск весьма кривой. Если поискать гуглом, он находит это. Как тут ссылку найти я не знаю. Обход CFG посредством слим APC. Это было время срачей", виксов, покрытий и тп. Если тема сохранилась, то там должны сурки быть. --- Сообщение объединено, 1 апр 2025 в 01:19 --- Кстате а что с кл, чего он висит ?
кл вроде норм работает. если тот который с домином тим. Набросал кодес под ваше описание из pdf, в RtlReleaseSRWLockExclusive не падает Код (C++): SlimLock->Ptr = (PVOID)1; // Simple signaling for demo код не претендует на конечную реализацию механизма)