876 lines
27 KiB
C++
876 lines
27 KiB
C++
/*******************************************************************************
|
|
_ ____ ______
|
|
| |/ /\ \ / / ___|
|
|
| ' / \ \ / / |
|
|
| . \ \ 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;
|
|
} |