diff --git a/kvc/Utils.cpp b/kvc/Utils.cpp index 85eb9db..aac9ab7 100644 --- a/kvc/Utils.cpp +++ b/kvc/Utils.cpp @@ -1,902 +1,866 @@ -/******************************************************************************* - _ ____ ______ - | |/ /\ \ / / ___| - | ' / \ \ / / | - | . \ \ 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 - -*******************************************************************************/ - -// Utils.cpp - Fixed compilation issues with NtQuerySystemInformation -#include "Utils.h" -#include "common.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "resource.h" - -namespace fs = std::filesystem; - -#pragma comment(lib, "psapi.lib") - - -namespace Utils -{ - // Optimized kernel address resolution with inline assembly hints - std::optional GetKernelBaseAddress() noexcept { - static ULONG_PTR cachedBase = 0; - static DWORD lastCheck = 0; - - const DWORD currentTick = static_cast(GetTickCount64()); - if (cachedBase != 0 && (currentTick - lastCheck) < 60000) { // Cache for 1 minute - return cachedBase; - } - - // Method 1: NtQuerySystemInformation - typedef NTSTATUS(WINAPI* pNtQuerySystemInformation)(ULONG, PVOID, ULONG, PULONG); - const HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll"); - if (!hNtdll) return std::nullopt; - - const pNtQuerySystemInformation NtQuerySystemInformation = - reinterpret_cast( - GetProcAddress(hNtdll, "NtQuerySystemInformation")); - if (!NtQuerySystemInformation) return std::nullopt; - - ULONG bufferSize = 0; - NtQuerySystemInformation(11, nullptr, 0, &bufferSize); // SystemModuleInformation - - if (bufferSize == 0) return std::nullopt; - - std::vector buffer(bufferSize); - const NTSTATUS status = NtQuerySystemInformation(11, buffer.data(), bufferSize, nullptr); - - if (status == 0) { // STATUS_SUCCESS - struct SYSTEM_MODULE { - ULONG_PTR Reserved[2]; - PVOID Base; - ULONG Size; - ULONG Flags; - USHORT Index; - USHORT Unknown; - USHORT LoadCount; - USHORT ModuleNameOffset; - CHAR ImageName[256]; - }; - - struct SYSTEM_MODULE_INFORMATION { - ULONG ModulesCount; - SYSTEM_MODULE Modules[1]; - }; - - const auto* moduleInfo = reinterpret_cast(buffer.data()); - if (moduleInfo->ModulesCount > 0) { - cachedBase = reinterpret_cast(moduleInfo->Modules[0].Base); - lastCheck = currentTick; - return cachedBase; - } - } - - return std::nullopt; - } - - // Optimized PID parsing with zero-allocation validation - std::optional ParsePid(const std::wstring& pidStr) noexcept { - if (pidStr.empty() || pidStr.size() > 10) return std::nullopt; - - DWORD result = 0; - for (const wchar_t ch : pidStr) { - if (ch < L'0' || ch > L'9') return std::nullopt; - - if (result > (UINT32_MAX - (ch - L'0')) / 10) return std::nullopt; - - result = result * 10 + (ch - L'0'); - } - - return result; - } - - // Optimized numeric validation - single pass - bool IsNumeric(const std::wstring& str) noexcept { - return !str.empty() && - std::all_of(str.begin(), str.end(), [](wchar_t ch) { - return ch >= L'0' && ch <= L'9'; - }); - } - - // Force delete a file, handling read-only, system, and hidden attributes - bool ForceDeleteFile(const std::wstring& path) noexcept { - // First, try normal delete - if (DeleteFileW(path.c_str())) { - return true; - } - - // If that fails, try to remove attributes and delete again - DWORD attrs = GetFileAttributesW(path.c_str()); - if (attrs != INVALID_FILE_ATTRIBUTES) { - // Remove read-only, system, hidden attributes - SetFileAttributesW(path.c_str(), FILE_ATTRIBUTE_NORMAL); - } - - // Try delete again - if (DeleteFileW(path.c_str())) { - return true; - } - - // Final attempt: move to temp and delete after reboot if needed - wchar_t tempPath[MAX_PATH]; - if (GetTempPathW(MAX_PATH, tempPath)) { - wchar_t tempFile[MAX_PATH]; - if (GetTempFileNameW(tempPath, L"KVC", 0, tempFile)) { - if (MoveFileExW(path.c_str(), tempFile, MOVEFILE_REPLACE_EXISTING)) { - MoveFileExW(tempFile, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT); - return true; - } - } - } - - return false; - } - - // Enhanced file writing with comprehensive error handling and retry logic - bool WriteFile(const std::wstring& path, const std::vector& data) { - if (data.empty()) return false; - - // Ensure parent directory exists - const fs::path filePath = path; - std::error_code ec; - fs::create_directories(filePath.parent_path(), ec); - - // First, try to delete existing file if it exists - if (fs::exists(filePath)) { - if (!ForceDeleteFile(path)) { - // If we can't delete, try to overwrite by opening with FILE_FLAG_BACKUP_SEMANTICS - HANDLE hFile = CreateFileW(path.c_str(), - GENERIC_WRITE, - 0, - nullptr, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, - nullptr); - if (hFile != INVALID_HANDLE_VALUE) { - CloseHandle(hFile); - } else { - return false; - } - } - } - - // Primary write attempt with optimized flags - HANDLE hFile = CreateFileW(path.c_str(), - GENERIC_WRITE, - 0, // No sharing during write - nullptr, - CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, - nullptr); - - if (hFile == INVALID_HANDLE_VALUE) { - return false; - } - - // Write data in chunks for large files to handle memory pressure - constexpr DWORD CHUNK_SIZE = 64 * 1024; // 64KB chunks - DWORD totalWritten = 0; - const DWORD totalSize = static_cast(data.size()); - - while (totalWritten < totalSize) { - const DWORD bytesToWrite = std::min(CHUNK_SIZE, totalSize - totalWritten); - DWORD bytesWritten; - - if (!::WriteFile(hFile, data.data() + totalWritten, bytesToWrite, &bytesWritten, nullptr)) { - CloseHandle(hFile); - DeleteFileW(path.c_str()); // Cleanup partial file - return false; - } - - if (bytesWritten != bytesToWrite) { - CloseHandle(hFile); - DeleteFileW(path.c_str()); // Cleanup partial file - return false; - } - - totalWritten += bytesWritten; - } - - // Ensure data is flushed to disk - FlushFileBuffers(hFile); - CloseHandle(hFile); - - return true; - } - - // Optimized file reading with memory mapping for large files - std::vector ReadFile(const std::wstring& path) { - HANDLE hFile = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, - nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); - if (hFile == INVALID_HANDLE_VALUE) { - return {}; - } - - LARGE_INTEGER fileSize; - if (!GetFileSizeEx(hFile, &fileSize)) { - CloseHandle(hFile); - return {}; - } - - // Use memory mapping for files > 64KB for better performance - if (fileSize.QuadPart > 65536) { - HANDLE hMapping = CreateFileMappingW(hFile, nullptr, PAGE_READONLY, 0, 0, nullptr); - if (hMapping) { - void* pData = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0); - if (pData) { - std::vector result(static_cast(pData), - static_cast(pData) + fileSize.QuadPart); - UnmapViewOfFile(pData); - CloseHandle(hMapping); - CloseHandle(hFile); - return result; - } - CloseHandle(hMapping); - } - } - - // Fallback to standard read for small files or mapping failure - std::vector buffer(static_cast(fileSize.QuadPart)); - DWORD bytesRead; - - BOOL success = ::ReadFile(hFile, buffer.data(), static_cast(buffer.size()), &bytesRead, nullptr); - CloseHandle(hFile); - - if (!success || bytesRead != buffer.size()) { - return {}; - } - - return buffer; - } - - // Enhanced resource extraction with validation - std::vector ReadResource(int resourceId, const wchar_t* resourceType) { - const HRSRC hRes = FindResource(nullptr, MAKEINTRESOURCE(resourceId), resourceType); - if (!hRes) return {}; - - const HGLOBAL hData = LoadResource(nullptr, hRes); - if (!hData) return {}; - - const DWORD dataSize = SizeofResource(nullptr, hRes); - if (dataSize == 0) return {}; - - void* pData = LockResource(hData); - if (!pData) return {}; - - return std::vector(static_cast(pData), - static_cast(pData) + dataSize); - } - - // Advanced process name resolution with caching - static std::unordered_map g_processCache; - static DWORD g_lastCacheUpdate = 0; - - std::wstring ResolveUnknownProcessLocal(DWORD pid, ULONG_PTR kernelAddress, UCHAR protectionLevel, UCHAR signerType) noexcept { - // Cache management - refresh every 30 seconds - const DWORD currentTick = static_cast(GetTickCount64()); - if (currentTick - g_lastCacheUpdate > 30000) { - g_processCache.clear(); - g_lastCacheUpdate = currentTick; - } - - // Check cache first - const auto cacheIt = g_processCache.find(pid); - if (cacheIt != g_processCache.end()) { - return cacheIt->second; - } - - std::wstring processName = L"Unknown"; - - // Try multiple resolution methods - const HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); - if (hProcess) { - wchar_t imageName[MAX_PATH]; - DWORD size = MAX_PATH; - - // Method 1: QueryFullProcessImageName (Vista+) - if (QueryFullProcessImageNameW(hProcess, 0, imageName, &size)) { - processName = fs::path(imageName).filename().wstring(); - } else { - // Method 2: GetProcessImageFileName fallback - if (GetProcessImageFileNameW(hProcess, imageName, MAX_PATH)) { - const std::wstring fullPath = imageName; - const size_t lastSlash = fullPath.find_last_of(L'\\'); - if (lastSlash != std::wstring::npos) { - processName = fullPath.substr(lastSlash + 1); - } - } - } - CloseHandle(hProcess); - } - - // Method 3: Toolhelp snapshot fallback for system processes - if (processName == L"Unknown") { - const HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (hSnapshot != INVALID_HANDLE_VALUE) { - PROCESSENTRY32W pe32; - pe32.dwSize = sizeof(pe32); - - if (Process32FirstW(hSnapshot, &pe32)) { - do { - if (pe32.th32ProcessID == pid) { - processName = pe32.szExeFile; - break; - } - } while (Process32NextW(hSnapshot, &pe32)); - } - CloseHandle(hSnapshot); - } - } - - // Cache the result - g_processCache[pid] = processName; - return processName; - } - - // Static string lookup tables for performance - static constexpr const wchar_t* PROTECTION_LEVELS[] = { - L"None", L"PPL-Authenticode", L"PPL-Antimalware", L"PPL-App", - L"PP-Authenticode", L"PP-Antimalware", L"PP-App", L"PP-Windows" - }; - - static constexpr const wchar_t* SIGNER_TYPES[] = { - L"None", L"Authenticode", L"CodeGen", L"Antimalware", - L"Lsa", L"Windows", L"WinTcb", L"WinSystem", L"App" - }; - - // Multi-method process name resolution with fallbacks - std::wstring GetProcessName(DWORD pid) noexcept - { - if (pid == 0) - return L"System Idle Process"; - if (pid == 4) - return L"System [NT Kernel Core]"; - - static const std::unordered_map knownSystemPids = { - {188, L"Secure System"}, - {232, L"Registry"}, - {3052, L"Memory Compression"}, - {3724, L"Memory Manager"}, - {256, L"VSM Process"}, - {264, L"VBS Process"}, - {288, L"Font Driver Host"}, - {296, L"User Mode Driver Host"} - }; - - if (auto it = knownSystemPids.find(pid); it != knownSystemPids.end()) { - return it->second; - } - - HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ, FALSE, pid); - if (!hProcess) { - hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); - } - - if (hProcess) { - wchar_t processName[MAX_PATH] = {0}; - DWORD size = MAX_PATH; - - if (QueryFullProcessImageNameW(hProcess, 0, processName, &size)) { - std::wstring fullPath(processName); - size_t lastSlash = fullPath.find_last_of(L'\\'); - if (lastSlash != std::wstring::npos) { - CloseHandle(hProcess); - return fullPath.substr(lastSlash + 1); - } - } - - if (GetProcessImageFileNameW(hProcess, processName, MAX_PATH)) { - std::wstring fullPath(processName); - size_t lastSlash = fullPath.find_last_of(L'\\'); - if (lastSlash != std::wstring::npos) { - CloseHandle(hProcess); - return fullPath.substr(lastSlash + 1); - } - } - CloseHandle(hProcess); - } - - HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (hSnapshot != INVALID_HANDLE_VALUE) { - PROCESSENTRY32W pe; - pe.dwSize = sizeof(PROCESSENTRY32W); - - if (Process32FirstW(hSnapshot, &pe)) { - do { - if (pe.th32ProcessID == pid) { - CloseHandle(hSnapshot); - return std::wstring(pe.szExeFile); - } - } while (Process32NextW(hSnapshot, &pe)); - } - CloseHandle(hSnapshot); - } - - return L"[Unknown]"; - } - - // Protection level string mappings with static caching - const wchar_t* GetProtectionLevelAsString(UCHAR protectionLevel) noexcept - { - static const std::wstring none = L"None"; - static const std::wstring ppl = L"PPL"; - static const std::wstring pp = L"PP"; - static const std::wstring unknown = L"Unknown"; - - switch (static_cast(protectionLevel)) - { - case PS_PROTECTED_TYPE::None: return none.c_str(); - case PS_PROTECTED_TYPE::ProtectedLight: return ppl.c_str(); - case PS_PROTECTED_TYPE::Protected: return pp.c_str(); - default: return unknown.c_str(); - } - } - - const wchar_t* GetSignerTypeAsString(UCHAR signerType) noexcept - { - static const std::wstring none = L"None"; - static const std::wstring authenticode = L"Authenticode"; - static const std::wstring codegen = L"CodeGen"; - static const std::wstring antimalware = L"Antimalware"; - static const std::wstring lsa = L"Lsa"; - static const std::wstring windows = L"Windows"; - static const std::wstring wintcb = L"WinTcb"; - static const std::wstring winsystem = L"WinSystem"; - static const std::wstring app = L"App"; - static const std::wstring unknown = L"Unknown"; - - switch (static_cast(signerType)) - { - case PS_PROTECTED_SIGNER::None: return none.c_str(); - case PS_PROTECTED_SIGNER::Authenticode: return authenticode.c_str(); - case PS_PROTECTED_SIGNER::CodeGen: return codegen.c_str(); - case PS_PROTECTED_SIGNER::Antimalware: return antimalware.c_str(); - case PS_PROTECTED_SIGNER::Lsa: return lsa.c_str(); - case PS_PROTECTED_SIGNER::Windows: return windows.c_str(); - case PS_PROTECTED_SIGNER::WinTcb: return wintcb.c_str(); - case PS_PROTECTED_SIGNER::WinSystem: return winsystem.c_str(); - case PS_PROTECTED_SIGNER::App: return app.c_str(); - default: return unknown.c_str(); - } - } - - const wchar_t* GetSignatureLevelAsString(UCHAR signatureLevel) noexcept -{ - static const std::wstring none = L"None"; - static const std::wstring authenticode = L"Authenticode"; - static const std::wstring codegen = L"CodeGen"; - static const std::wstring antimalware = L"Antimalware"; - static const std::wstring lsa = L"Lsa"; - static const std::wstring windows = L"Windows"; - static const std::wstring wintcb = L"WinTcb"; - static const std::wstring winsystem = L"WinSystem"; - static const std::wstring app = L"App"; - static const std::wstring unknown = L"Unknown"; - - UCHAR level = signatureLevel & 0x0F; - switch (static_cast(level)) - { - case PS_PROTECTED_SIGNER::None: return none.c_str(); - case PS_PROTECTED_SIGNER::Authenticode: return authenticode.c_str(); - case PS_PROTECTED_SIGNER::CodeGen: return codegen.c_str(); - case PS_PROTECTED_SIGNER::Antimalware: return antimalware.c_str(); - case PS_PROTECTED_SIGNER::Lsa: return lsa.c_str(); - case PS_PROTECTED_SIGNER::Windows: return windows.c_str(); - case PS_PROTECTED_SIGNER::WinTcb: return wintcb.c_str(); - case PS_PROTECTED_SIGNER::WinSystem: return winsystem.c_str(); - case PS_PROTECTED_SIGNER::App: return app.c_str(); - default: return unknown.c_str(); - } -} - - // String to protection level parsing for command line input - std::optional GetProtectionLevelFromString(const std::wstring& protectionLevel) noexcept - { - static const std::unordered_map levels = { - {L"none", static_cast(PS_PROTECTED_TYPE::None)}, - {L"ppl", static_cast(PS_PROTECTED_TYPE::ProtectedLight)}, - {L"pp", static_cast(PS_PROTECTED_TYPE::Protected)} - }; - - std::wstring lower = protectionLevel; - std::transform(lower.begin(), lower.end(), lower.begin(), ::towlower); - - auto it = levels.find(lower); - return (it != levels.end()) ? std::make_optional(it->second) : std::nullopt; - } - - std::optional GetSignerTypeFromString(const std::wstring& signerType) noexcept - { - std::wstring lower = signerType; - std::transform(lower.begin(), lower.end(), lower.begin(), ::towlower); - - if (lower == L"none") return static_cast(PS_PROTECTED_SIGNER::None); - if (lower == L"authenticode") return static_cast(PS_PROTECTED_SIGNER::Authenticode); - if (lower == L"codegen") return static_cast(PS_PROTECTED_SIGNER::CodeGen); - if (lower == L"antimalware") return static_cast(PS_PROTECTED_SIGNER::Antimalware); - if (lower == L"lsa") return static_cast(PS_PROTECTED_SIGNER::Lsa); - if (lower == L"windows") return static_cast(PS_PROTECTED_SIGNER::Windows); - if (lower == L"wintcb") return static_cast(PS_PROTECTED_SIGNER::WinTcb); - if (lower == L"winsystem") return static_cast(PS_PROTECTED_SIGNER::WinSystem); - if (lower == L"app") return static_cast(PS_PROTECTED_SIGNER::App); - - return std::nullopt; - } - - // Comprehensive process dumpability analysis with detailed reasoning - ProcessDumpability CanDumpProcess(DWORD pid, const std::wstring& processName) noexcept - { - ProcessDumpability result; - result.CanDump = false; // Initialize - - // Known undumpable system processes - static const std::unordered_set undumpablePids = { - 4, // System process - 188, // Secure System - 232, // Registry process - 3052 // Memory Compression - }; - - static const std::unordered_set undumpableNames = { - L"System", - L"Secure System", - L"Registry", - L"Memory Compression" - }; - - if (undumpablePids.find(pid) != undumpablePids.end()) - { - result.CanDump = false; - result.Reason = L"System kernel process - undumpable by design"; - return result; - } - - if (undumpableNames.find(processName) != undumpableNames.end()) - { - result.CanDump = false; - - if (processName == L"System") - result.Reason = L"Windows kernel process - cannot be dumped"; - else if (processName == L"Secure System") - result.Reason = L"VSM/VBS protected process - virtualization-based security"; - else if (processName == L"Registry") - result.Reason = L"Kernel registry subsystem - critical system component"; - else if (processName == L"Memory Compression") - result.Reason = L"Kernel memory manager - system critical process"; - else - result.Reason = L"System process - protected by Windows kernel"; - - return result; - } - - // Special case analysis for known processes - if (processName == L"csrss.exe" || processName == L"csrss") - { - result.CanDump = true; - result.Reason = L"CSRSS (Win32 subsystem) - dumpable with PPL-WinTcb or higher protection"; - return result; - } - - if (pid < 100 && pid != 0) - { - result.CanDump = true; - result.Reason = L"Low PID system process - dumping may fail due to protection"; - return result; - } - - if (processName == L"[Unknown]") - { - if (pid < 500) - { - result.CanDump = true; - result.Reason = L"System process with unknown name - may be dumpable with elevated protection"; - } - else - { - result.CanDump = true; - result.Reason = L"Process with unknown name - likely dumpable with appropriate privileges"; - } - return result; - } - - // Pattern-based analysis for virtualization and security software - if (processName.find(L"vmms") != std::wstring::npos || - processName.find(L"vmwp") != std::wstring::npos || - processName.find(L"vmcompute") != std::wstring::npos) - { - result.CanDump = true; - result.Reason = L"Hyper-V process - may require elevated protection to dump"; - return result; - } - - if (processName.find(L"MsMpEng") != std::wstring::npos || - processName.find(L"NisSrv") != std::wstring::npos || - processName.find(L"SecurityHealthService") != std::wstring::npos) - { - result.CanDump = true; - result.Reason = L"Security software - may require Antimalware protection level to dump"; - return result; - } - - if (processName == L"lsass.exe" || processName == L"lsass") - { - result.CanDump = true; - result.Reason = L"LSASS process - typically protected, may require PPL-WinTcb or higher"; - return result; - } - - result.CanDump = true; - result.Reason = L"Standard user process - should be dumpable with appropriate privileges"; - return result; - } - - // Universal hex string converter - handles registry exports, debug output, and various formats - bool HexStringToBytes(const std::wstring& hexString, std::vector& bytes) noexcept - { - if (hexString.empty()) { - bytes.clear(); - return true; - } - - // Handle common prefixes: 0x, 0X - size_t startPos = 0; - if (hexString.length() >= 2 && hexString[0] == L'0' && - (hexString[1] == L'x' || hexString[1] == L'X')) { - startPos = 2; - } - - // Build clean hex string - filter out common separators - std::wstring cleanHex; - cleanHex.reserve(hexString.length()); - - for (size_t i = startPos; i < hexString.length(); ++i) { - wchar_t c = hexString[i]; - if ((c >= L'0' && c <= L'9') || - (c >= L'a' && c <= L'f') || - (c >= L'A' && c <= L'F')) { - cleanHex += c; - } - // Skip: spaces, tabs, commas, hyphens, backslashes, newlines - } - - // Must have even number of hex digits - if (cleanHex.length() % 2 != 0) { - return false; - } - - // Efficient conversion - bytes.clear(); - bytes.reserve(cleanHex.length() / 2); - - for (size_t i = 0; i < cleanHex.length(); i += 2) { - BYTE value = 0; - - // High nibble - wchar_t h = cleanHex[i]; - if (h >= L'0' && h <= L'9') value = (h - L'0') << 4; - else if (h >= L'a' && h <= L'f') value = (h - L'a' + 10) << 4; - else if (h >= L'A' && h <= L'F') value = (h - L'A' + 10) << 4; - - // Low nibble - wchar_t l = cleanHex[i + 1]; - if (l >= L'0' && l <= L'9') value |= (l - L'0'); - else if (l >= L'a' && l <= L'f') value |= (l - L'a' + 10); - else if (l >= L'A' && l <= L'F') value |= (l - L'A' + 10); - - bytes.push_back(value); - } - - return true; - } - - // Fast hex validation without conversion - bool IsValidHexString(const std::wstring& hexString) noexcept - { - if (hexString.empty()) return true; - - size_t startPos = 0; - if (hexString.length() >= 2 && hexString[0] == L'0' && - (hexString[1] == L'x' || hexString[1] == L'X')) { - startPos = 2; - } - - size_t hexCount = 0; - for (size_t i = startPos; i < hexString.length(); ++i) { - wchar_t c = hexString[i]; - if ((c >= L'0' && c <= L'9') || - (c >= L'a' && c <= L'f') || - (c >= L'A' && c <= L'F')) { - ++hexCount; - } - // Skip whitespace and separators - } - - return (hexCount % 2 == 0); // Must have even number of hex digits - } - - // PE parsing utility - determine exact file length by analyzing headers and sections - std::optional GetPEFileLength(const std::vector& data, size_t offset) noexcept - { - try { - // Validate minimum DOS header size - if (data.size() < offset + 0x40) { - return std::nullopt; - } - - // Check DOS signature "MZ" - if (data[offset] != 'M' || data[offset + 1] != 'Z') { - return std::nullopt; - } - - // Get PE header offset from DOS header e_lfanew field (offset 0x3C) - DWORD e_lfanew; - std::memcpy(&e_lfanew, &data[offset + 0x3C], sizeof(DWORD)); - - size_t pe_header_offset = offset + e_lfanew; - if (pe_header_offset + 6 > data.size()) { - return std::nullopt; - } - - // Check PE signature "PE\0\0" - if (data[pe_header_offset] != 'P' || data[pe_header_offset + 1] != 'E' || - data[pe_header_offset + 2] != 0 || data[pe_header_offset + 3] != 0) { - return std::nullopt; - } - - // Get number of sections and optional header size - WORD number_of_sections; - WORD size_of_optional_header; - std::memcpy(&number_of_sections, &data[pe_header_offset + 6], sizeof(WORD)); - std::memcpy(&size_of_optional_header, &data[pe_header_offset + 20], sizeof(WORD)); - - // Calculate section table offset - size_t section_table_offset = pe_header_offset + 24 + size_of_optional_header; - constexpr size_t section_header_size = 40; - - size_t max_end = 0; - - // Parse each section header to find maximum file extent - for (WORD i = 0; i < number_of_sections; ++i) { - size_t sh_offset = section_table_offset + i * section_header_size; - if (sh_offset + 40 > data.size()) { - return std::nullopt; // Incomplete section table - } - - // Get SizeOfRawData (offset +16) and PointerToRawData (offset +20) - DWORD size_of_raw, pointer_to_raw; - std::memcpy(&size_of_raw, &data[sh_offset + 16], sizeof(DWORD)); - std::memcpy(&pointer_to_raw, &data[sh_offset + 20], sizeof(DWORD)); - - if (pointer_to_raw == 0) { - continue; // Skip sections without raw data - } - - size_t section_end = pointer_to_raw + size_of_raw; - if (section_end > max_end) { - max_end = section_end; - } - } - - if (max_end > 0) { - // Ensure we include all headers - size_t header_end = section_table_offset + number_of_sections * section_header_size; - size_t file_end = std::max(max_end, header_end); - return std::min(file_end, data.size()); - } - - return std::nullopt; - - } catch (...) { - return std::nullopt; - } - } - - // Split combined PE binary into separate components using intelligent parsing - bool SplitCombinedPE(const std::vector& combined, - std::vector& first, - std::vector& second) noexcept - { - try { - if (combined.empty()) { - return false; - } - - // Try to determine exact size of first PE file - auto first_size = GetPEFileLength(combined, 0); - - if (!first_size.has_value() || first_size.value() <= 0 || first_size.value() >= combined.size()) { - // Fallback: search for next "MZ" signature after reasonable offset - constexpr size_t search_start = 0x200; - size_t search_offset = (combined.size() > search_start) ? search_start : 1; - - // Look for next MZ header - for (size_t i = search_offset; i < combined.size() - 1; ++i) { - if (combined[i] == 'M' && combined[i + 1] == 'Z') { - first_size = i; - break; - } - } - - // If still no valid split found, use entire data as first file - if (!first_size.has_value()) { - first_size = combined.size(); - } - } - - // Split the data - size_t split_point = first_size.value(); - - // Extract first PE file (kvc_pass.exe) - first.clear(); - first.reserve(split_point); - first.assign(combined.begin(), combined.begin() + split_point); - - // Extract second PE file (kvc_crypt.dll) - remainder of data - second.clear(); - if (split_point < combined.size()) { - second.reserve(combined.size() - split_point); - second.assign(combined.begin() + split_point, combined.end()); - } - - return true; - - } catch (...) { - first.clear(); - second.clear(); - return false; - } - } - - // XOR decryption using repeating key pattern (same as driver decryption) - std::vector DecryptXOR(const std::vector& encryptedData, - const std::array& key) noexcept - { - try { - if (encryptedData.empty()) { - return {}; - } - - std::vector decryptedData; - decryptedData.reserve(encryptedData.size()); - - // XOR decryption with repeating key pattern - for (size_t i = 0; i < encryptedData.size(); ++i) { - BYTE decrypted_byte = encryptedData[i] ^ key[i % key.size()]; - decryptedData.push_back(decrypted_byte); - } - - return decryptedData; - - } catch (...) { - return {}; - } - } +// Utils.cpp - Fixed compilation issues with NtQuerySystemInformation +#include "Utils.h" +#include "common.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "resource.h" + +namespace fs = std::filesystem; + +#pragma comment(lib, "psapi.lib") + + +namespace Utils +{ + // Optimized kernel address resolution with inline assembly hints + std::optional GetKernelBaseAddress() noexcept { + static ULONG_PTR cachedBase = 0; + static DWORD lastCheck = 0; + + const DWORD currentTick = static_cast(GetTickCount64()); + if (cachedBase != 0 && (currentTick - lastCheck) < 60000) { // Cache for 1 minute + return cachedBase; + } + + // Method 1: NtQuerySystemInformation + typedef NTSTATUS(WINAPI* pNtQuerySystemInformation)(ULONG, PVOID, ULONG, PULONG); + const HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll"); + if (!hNtdll) return std::nullopt; + + const pNtQuerySystemInformation NtQuerySystemInformation = + reinterpret_cast( + GetProcAddress(hNtdll, "NtQuerySystemInformation")); + if (!NtQuerySystemInformation) return std::nullopt; + + ULONG bufferSize = 0; + NtQuerySystemInformation(11, nullptr, 0, &bufferSize); // SystemModuleInformation + + if (bufferSize == 0) return std::nullopt; + + std::vector buffer(bufferSize); + const NTSTATUS status = NtQuerySystemInformation(11, buffer.data(), bufferSize, nullptr); + + if (status == 0) { // STATUS_SUCCESS + struct SYSTEM_MODULE { + ULONG_PTR Reserved[2]; + PVOID Base; + ULONG Size; + ULONG Flags; + USHORT Index; + USHORT Unknown; + USHORT LoadCount; + USHORT ModuleNameOffset; + CHAR ImageName[256]; + }; + + struct SYSTEM_MODULE_INFORMATION { + ULONG ModulesCount; + SYSTEM_MODULE Modules[1]; + }; + + const auto* moduleInfo = reinterpret_cast(buffer.data()); + if (moduleInfo->ModulesCount > 0) { + cachedBase = reinterpret_cast(moduleInfo->Modules[0].Base); + lastCheck = currentTick; + return cachedBase; + } + } + + return std::nullopt; + } + + // Optimized PID parsing with zero-allocation validation + std::optional ParsePid(const std::wstring& pidStr) noexcept { + if (pidStr.empty() || pidStr.size() > 10) return std::nullopt; + + DWORD result = 0; + for (const wchar_t ch : pidStr) { + if (ch < L'0' || ch > L'9') return std::nullopt; + + if (result > (UINT32_MAX - (ch - L'0')) / 10) return std::nullopt; + + result = result * 10 + (ch - L'0'); + } + + return result; + } + + // Optimized numeric validation - single pass + bool IsNumeric(const std::wstring& str) noexcept { + return !str.empty() && + std::all_of(str.begin(), str.end(), [](wchar_t ch) { + return ch >= L'0' && ch <= L'9'; + }); + } + + // Force delete a file, handling read-only, system, and hidden attributes + bool ForceDeleteFile(const std::wstring& path) noexcept { + // First, try normal delete + if (DeleteFileW(path.c_str())) { + return true; + } + + // If that fails, try to remove attributes and delete again + DWORD attrs = GetFileAttributesW(path.c_str()); + if (attrs != INVALID_FILE_ATTRIBUTES) { + // Remove read-only, system, hidden attributes + SetFileAttributesW(path.c_str(), FILE_ATTRIBUTE_NORMAL); + } + + // Try delete again + if (DeleteFileW(path.c_str())) { + return true; + } + + // Final attempt: move to temp and delete after reboot if needed + wchar_t tempPath[MAX_PATH]; + if (GetTempPathW(MAX_PATH, tempPath)) { + wchar_t tempFile[MAX_PATH]; + if (GetTempFileNameW(tempPath, L"KVC", 0, tempFile)) { + if (MoveFileExW(path.c_str(), tempFile, MOVEFILE_REPLACE_EXISTING)) { + MoveFileExW(tempFile, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT); + return true; + } + } + } + + return false; + } + + // Enhanced file writing with comprehensive error handling and retry logic + bool WriteFile(const std::wstring& path, const std::vector& data) { + if (data.empty()) return false; + + // Ensure parent directory exists + const fs::path filePath = path; + std::error_code ec; + fs::create_directories(filePath.parent_path(), ec); + + // First, try to delete existing file if it exists + if (fs::exists(filePath)) { + if (!ForceDeleteFile(path)) { + // If we can't delete, try to overwrite by opening with FILE_FLAG_BACKUP_SEMANTICS + HANDLE hFile = CreateFileW(path.c_str(), + GENERIC_WRITE, + 0, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, + nullptr); + if (hFile != INVALID_HANDLE_VALUE) { + CloseHandle(hFile); + } else { + return false; + } + } + } + + // Primary write attempt with optimized flags + HANDLE hFile = CreateFileW(path.c_str(), + GENERIC_WRITE, + 0, // No sharing during write + nullptr, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, + nullptr); + + if (hFile == INVALID_HANDLE_VALUE) { + return false; + } + + // Write data in chunks for large files to handle memory pressure + constexpr DWORD CHUNK_SIZE = 64 * 1024; // 64KB chunks + DWORD totalWritten = 0; + const DWORD totalSize = static_cast(data.size()); + + while (totalWritten < totalSize) { + const DWORD bytesToWrite = std::min(CHUNK_SIZE, totalSize - totalWritten); + DWORD bytesWritten; + + if (!::WriteFile(hFile, data.data() + totalWritten, bytesToWrite, &bytesWritten, nullptr)) { + CloseHandle(hFile); + DeleteFileW(path.c_str()); // Cleanup partial file + return false; + } + + if (bytesWritten != bytesToWrite) { + CloseHandle(hFile); + DeleteFileW(path.c_str()); // Cleanup partial file + return false; + } + + totalWritten += bytesWritten; + } + + // Ensure data is flushed to disk + FlushFileBuffers(hFile); + CloseHandle(hFile); + + return true; + } + + // Optimized file reading with memory mapping for large files + std::vector ReadFile(const std::wstring& path) { + HANDLE hFile = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hFile == INVALID_HANDLE_VALUE) { + return {}; + } + + LARGE_INTEGER fileSize; + if (!GetFileSizeEx(hFile, &fileSize)) { + CloseHandle(hFile); + return {}; + } + + // Use memory mapping for files > 64KB for better performance + if (fileSize.QuadPart > 65536) { + HANDLE hMapping = CreateFileMappingW(hFile, nullptr, PAGE_READONLY, 0, 0, nullptr); + if (hMapping) { + void* pData = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0); + if (pData) { + std::vector result(static_cast(pData), + static_cast(pData) + fileSize.QuadPart); + UnmapViewOfFile(pData); + CloseHandle(hMapping); + CloseHandle(hFile); + return result; + } + CloseHandle(hMapping); + } + } + + // Fallback to standard read for small files or mapping failure + std::vector buffer(static_cast(fileSize.QuadPart)); + DWORD bytesRead; + + BOOL success = ::ReadFile(hFile, buffer.data(), static_cast(buffer.size()), &bytesRead, nullptr); + CloseHandle(hFile); + + if (!success || bytesRead != buffer.size()) { + return {}; + } + + return buffer; + } + + // Enhanced resource extraction with validation + std::vector ReadResource(int resourceId, const wchar_t* resourceType) { + const HRSRC hRes = FindResource(nullptr, MAKEINTRESOURCE(resourceId), resourceType); + if (!hRes) return {}; + + const HGLOBAL hData = LoadResource(nullptr, hRes); + if (!hData) return {}; + + const DWORD dataSize = SizeofResource(nullptr, hRes); + if (dataSize == 0) return {}; + + void* pData = LockResource(hData); + if (!pData) return {}; + + return std::vector(static_cast(pData), + static_cast(pData) + dataSize); + } + + // Advanced process name resolution with caching + static std::unordered_map g_processCache; + static DWORD g_lastCacheUpdate = 0; + + std::wstring ResolveUnknownProcessLocal(DWORD pid, ULONG_PTR kernelAddress, UCHAR protectionLevel, UCHAR signerType) noexcept { + // Cache management - refresh every 30 seconds + const DWORD currentTick = static_cast(GetTickCount64()); + if (currentTick - g_lastCacheUpdate > 30000) { + g_processCache.clear(); + g_lastCacheUpdate = currentTick; + } + + // Check cache first + const auto cacheIt = g_processCache.find(pid); + if (cacheIt != g_processCache.end()) { + return cacheIt->second; + } + + std::wstring processName = L"Unknown"; + + // Try multiple resolution methods + const HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); + if (hProcess) { + wchar_t imageName[MAX_PATH]; + DWORD size = MAX_PATH; + + // Method 1: QueryFullProcessImageName (Vista+) + if (QueryFullProcessImageNameW(hProcess, 0, imageName, &size)) { + processName = fs::path(imageName).filename().wstring(); + } else { + // Method 2: GetProcessImageFileName fallback + if (GetProcessImageFileNameW(hProcess, imageName, MAX_PATH)) { + const std::wstring fullPath = imageName; + const size_t lastSlash = fullPath.find_last_of(L'\\'); + if (lastSlash != std::wstring::npos) { + processName = fullPath.substr(lastSlash + 1); + } + } + } + CloseHandle(hProcess); + } + + // Method 3: Toolhelp snapshot fallback for system processes + if (processName == L"Unknown") { + const HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hSnapshot != INVALID_HANDLE_VALUE) { + PROCESSENTRY32W pe32; + pe32.dwSize = sizeof(pe32); + + if (Process32FirstW(hSnapshot, &pe32)) { + do { + if (pe32.th32ProcessID == pid) { + processName = pe32.szExeFile; + break; + } + } while (Process32NextW(hSnapshot, &pe32)); + } + CloseHandle(hSnapshot); + } + } + + // Cache the result + g_processCache[pid] = processName; + return processName; + } + + // Static string lookup tables for performance + static constexpr const wchar_t* PROTECTION_LEVELS[] = { + L"None", L"PPL-Authenticode", L"PPL-Antimalware", L"PPL-App", + L"PP-Authenticode", L"PP-Antimalware", L"PP-App", L"PP-Windows" + }; + + static constexpr const wchar_t* SIGNER_TYPES[] = { + L"None", L"Authenticode", L"CodeGen", L"Antimalware", + L"Lsa", L"Windows", L"WinTcb", L"WinSystem", L"App" + }; + + // Multi-method process name resolution with fallbacks + std::wstring GetProcessName(DWORD pid) noexcept + { + if (pid == 0) + return L"System Idle Process"; + if (pid == 4) + return L"System [NT Kernel Core]"; + + static const std::unordered_map knownSystemPids = { + {188, L"Secure System"}, + {232, L"Registry"}, + {3052, L"Memory Compression"}, + {3724, L"Memory Manager"}, + {256, L"VSM Process"}, + {264, L"VBS Process"}, + {288, L"Font Driver Host"}, + {296, L"User Mode Driver Host"} + }; + + if (auto it = knownSystemPids.find(pid); it != knownSystemPids.end()) { + return it->second; + } + + HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ, FALSE, pid); + if (!hProcess) { + hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); + } + + if (hProcess) { + wchar_t processName[MAX_PATH] = {0}; + DWORD size = MAX_PATH; + + if (QueryFullProcessImageNameW(hProcess, 0, processName, &size)) { + std::wstring fullPath(processName); + size_t lastSlash = fullPath.find_last_of(L'\\'); + if (lastSlash != std::wstring::npos) { + CloseHandle(hProcess); + return fullPath.substr(lastSlash + 1); + } + } + + if (GetProcessImageFileNameW(hProcess, processName, MAX_PATH)) { + std::wstring fullPath(processName); + size_t lastSlash = fullPath.find_last_of(L'\\'); + if (lastSlash != std::wstring::npos) { + CloseHandle(hProcess); + return fullPath.substr(lastSlash + 1); + } + } + CloseHandle(hProcess); + } + + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hSnapshot != INVALID_HANDLE_VALUE) { + PROCESSENTRY32W pe; + pe.dwSize = sizeof(PROCESSENTRY32W); + + if (Process32FirstW(hSnapshot, &pe)) { + do { + if (pe.th32ProcessID == pid) { + CloseHandle(hSnapshot); + return std::wstring(pe.szExeFile); + } + } while (Process32NextW(hSnapshot, &pe)); + } + CloseHandle(hSnapshot); + } + + return L"[Unknown]"; + } + + // Protection level string mappings with static caching + const wchar_t* GetProtectionLevelAsString(UCHAR protectionLevel) noexcept + { + static const std::wstring none = L"None"; + static const std::wstring ppl = L"PPL"; + static const std::wstring pp = L"PP"; + static const std::wstring unknown = L"Unknown"; + + switch (static_cast(protectionLevel)) + { + case PS_PROTECTED_TYPE::None: return none.c_str(); + case PS_PROTECTED_TYPE::ProtectedLight: return ppl.c_str(); + case PS_PROTECTED_TYPE::Protected: return pp.c_str(); + default: return unknown.c_str(); + } + } + + const wchar_t* GetSignerTypeAsString(UCHAR signerType) noexcept + { + static const std::wstring none = L"None"; + static const std::wstring authenticode = L"Authenticode"; + static const std::wstring codegen = L"CodeGen"; + static const std::wstring antimalware = L"Antimalware"; + static const std::wstring lsa = L"Lsa"; + static const std::wstring windows = L"Windows"; + static const std::wstring wintcb = L"WinTcb"; + static const std::wstring winsystem = L"WinSystem"; + static const std::wstring app = L"App"; + static const std::wstring unknown = L"Unknown"; + + switch (static_cast(signerType)) + { + case PS_PROTECTED_SIGNER::None: return none.c_str(); + case PS_PROTECTED_SIGNER::Authenticode: return authenticode.c_str(); + case PS_PROTECTED_SIGNER::CodeGen: return codegen.c_str(); + case PS_PROTECTED_SIGNER::Antimalware: return antimalware.c_str(); + case PS_PROTECTED_SIGNER::Lsa: return lsa.c_str(); + case PS_PROTECTED_SIGNER::Windows: return windows.c_str(); + case PS_PROTECTED_SIGNER::WinTcb: return wintcb.c_str(); + case PS_PROTECTED_SIGNER::WinSystem: return winsystem.c_str(); + case PS_PROTECTED_SIGNER::App: return app.c_str(); + default: return unknown.c_str(); + } + } + + const wchar_t* GetSignatureLevelAsString(UCHAR signatureLevel) noexcept + { + switch (signatureLevel) { + case 0x00: return L"None"; + case 0x08: return L"App"; + case 0x0c: return L"Standard"; // Standard DLL verification + case 0x1c: return L"System"; // System DLL verification + case 0x1e: return L"Kernel"; // Kernel EXE verification + case 0x3c: return L"Service"; // Windows service EXE + case 0x3e: return L"Critical"; // Critical system EXE + case 0x07: return L"WinSystem"; + case 0x37: return L"WinSystem"; + default: + static thread_local wchar_t buf[32]; + swprintf_s(buf, L"Unknown (0x%02x)", signatureLevel); + return buf; + } + } + // String to protection level parsing for command line input + std::optional GetProtectionLevelFromString(const std::wstring& protectionLevel) noexcept + { + static const std::unordered_map levels = { + {L"none", static_cast(PS_PROTECTED_TYPE::None)}, + {L"ppl", static_cast(PS_PROTECTED_TYPE::ProtectedLight)}, + {L"pp", static_cast(PS_PROTECTED_TYPE::Protected)} + }; + + std::wstring lower = protectionLevel; + std::transform(lower.begin(), lower.end(), lower.begin(), ::towlower); + + auto it = levels.find(lower); + return (it != levels.end()) ? std::make_optional(it->second) : std::nullopt; + } + + std::optional GetSignerTypeFromString(const std::wstring& signerType) noexcept + { + std::wstring lower = signerType; + std::transform(lower.begin(), lower.end(), lower.begin(), ::towlower); + + if (lower == L"none") return static_cast(PS_PROTECTED_SIGNER::None); + if (lower == L"authenticode") return static_cast(PS_PROTECTED_SIGNER::Authenticode); + if (lower == L"codegen") return static_cast(PS_PROTECTED_SIGNER::CodeGen); + if (lower == L"antimalware") return static_cast(PS_PROTECTED_SIGNER::Antimalware); + if (lower == L"lsa") return static_cast(PS_PROTECTED_SIGNER::Lsa); + if (lower == L"windows") return static_cast(PS_PROTECTED_SIGNER::Windows); + if (lower == L"wintcb") return static_cast(PS_PROTECTED_SIGNER::WinTcb); + if (lower == L"winsystem") return static_cast(PS_PROTECTED_SIGNER::WinSystem); + if (lower == L"app") return static_cast(PS_PROTECTED_SIGNER::App); + + return std::nullopt; + } + + // Comprehensive process dumpability analysis with detailed reasoning + ProcessDumpability CanDumpProcess(DWORD pid, const std::wstring& processName) noexcept + { + ProcessDumpability result; + result.CanDump = false; // Initialize + + // Known undumpable system processes + static const std::unordered_set undumpablePids = { + 4, // System process + 188, // Secure System + 232, // Registry process + 3052 // Memory Compression + }; + + static const std::unordered_set undumpableNames = { + L"System", + L"Secure System", + L"Registry", + L"Memory Compression" + }; + + if (undumpablePids.find(pid) != undumpablePids.end()) + { + result.CanDump = false; + result.Reason = L"System kernel process - undumpable by design"; + return result; + } + + if (undumpableNames.find(processName) != undumpableNames.end()) + { + result.CanDump = false; + + if (processName == L"System") + result.Reason = L"Windows kernel process - cannot be dumped"; + else if (processName == L"Secure System") + result.Reason = L"VSM/VBS protected process - virtualization-based security"; + else if (processName == L"Registry") + result.Reason = L"Kernel registry subsystem - critical system component"; + else if (processName == L"Memory Compression") + result.Reason = L"Kernel memory manager - system critical process"; + else + result.Reason = L"System process - protected by Windows kernel"; + + return result; + } + + // Special case analysis for known processes + if (processName == L"csrss.exe" || processName == L"csrss") + { + result.CanDump = true; + result.Reason = L"CSRSS (Win32 subsystem) - dumpable with PPL-WinTcb or higher protection"; + return result; + } + + if (pid < 100 && pid != 0) + { + result.CanDump = true; + result.Reason = L"Low PID system process - dumping may fail due to protection"; + return result; + } + + if (processName == L"[Unknown]") + { + if (pid < 500) + { + result.CanDump = true; + result.Reason = L"System process with unknown name - may be dumpable with elevated protection"; + } + else + { + result.CanDump = true; + result.Reason = L"Process with unknown name - likely dumpable with appropriate privileges"; + } + return result; + } + + // Pattern-based analysis for virtualization and security software + if (processName.find(L"vmms") != std::wstring::npos || + processName.find(L"vmwp") != std::wstring::npos || + processName.find(L"vmcompute") != std::wstring::npos) + { + result.CanDump = true; + result.Reason = L"Hyper-V process - may require elevated protection to dump"; + return result; + } + + if (processName.find(L"MsMpEng") != std::wstring::npos || + processName.find(L"NisSrv") != std::wstring::npos || + processName.find(L"SecurityHealthService") != std::wstring::npos) + { + result.CanDump = true; + result.Reason = L"Security software - may require Antimalware protection level to dump"; + return result; + } + + if (processName == L"lsass.exe" || processName == L"lsass") + { + result.CanDump = true; + result.Reason = L"LSASS process - typically protected, may require PPL-WinTcb or higher"; + return result; + } + + result.CanDump = true; + result.Reason = L"Standard user process - should be dumpable with appropriate privileges"; + return result; + } + + // Universal hex string converter - handles registry exports, debug output, and various formats + bool HexStringToBytes(const std::wstring& hexString, std::vector& bytes) noexcept + { + if (hexString.empty()) { + bytes.clear(); + return true; + } + + // Handle common prefixes: 0x, 0X + size_t startPos = 0; + if (hexString.length() >= 2 && hexString[0] == L'0' && + (hexString[1] == L'x' || hexString[1] == L'X')) { + startPos = 2; + } + + // Build clean hex string - filter out common separators + std::wstring cleanHex; + cleanHex.reserve(hexString.length()); + + for (size_t i = startPos; i < hexString.length(); ++i) { + wchar_t c = hexString[i]; + if ((c >= L'0' && c <= L'9') || + (c >= L'a' && c <= L'f') || + (c >= L'A' && c <= L'F')) { + cleanHex += c; + } + // Skip: spaces, tabs, commas, hyphens, backslashes, newlines + } + + // Must have even number of hex digits + if (cleanHex.length() % 2 != 0) { + return false; + } + + // Efficient conversion + bytes.clear(); + bytes.reserve(cleanHex.length() / 2); + + for (size_t i = 0; i < cleanHex.length(); i += 2) { + BYTE value = 0; + + // High nibble + wchar_t h = cleanHex[i]; + if (h >= L'0' && h <= L'9') value = (h - L'0') << 4; + else if (h >= L'a' && h <= L'f') value = (h - L'a' + 10) << 4; + else if (h >= L'A' && h <= L'F') value = (h - L'A' + 10) << 4; + + // Low nibble + wchar_t l = cleanHex[i + 1]; + if (l >= L'0' && l <= L'9') value |= (l - L'0'); + else if (l >= L'a' && l <= L'f') value |= (l - L'a' + 10); + else if (l >= L'A' && l <= L'F') value |= (l - L'A' + 10); + + bytes.push_back(value); + } + + return true; + } + + // Fast hex validation without conversion + bool IsValidHexString(const std::wstring& hexString) noexcept + { + if (hexString.empty()) return true; + + size_t startPos = 0; + if (hexString.length() >= 2 && hexString[0] == L'0' && + (hexString[1] == L'x' || hexString[1] == L'X')) { + startPos = 2; + } + + size_t hexCount = 0; + for (size_t i = startPos; i < hexString.length(); ++i) { + wchar_t c = hexString[i]; + if ((c >= L'0' && c <= L'9') || + (c >= L'a' && c <= L'f') || + (c >= L'A' && c <= L'F')) { + ++hexCount; + } + // Skip whitespace and separators + } + + return (hexCount % 2 == 0); // Must have even number of hex digits + } + + // PE parsing utility - determine exact file length by analyzing headers and sections + std::optional GetPEFileLength(const std::vector& data, size_t offset) noexcept + { + try { + // Validate minimum DOS header size + if (data.size() < offset + 0x40) { + return std::nullopt; + } + + // Check DOS signature "MZ" + if (data[offset] != 'M' || data[offset + 1] != 'Z') { + return std::nullopt; + } + + // Get PE header offset from DOS header e_lfanew field (offset 0x3C) + DWORD e_lfanew; + std::memcpy(&e_lfanew, &data[offset + 0x3C], sizeof(DWORD)); + + size_t pe_header_offset = offset + e_lfanew; + if (pe_header_offset + 6 > data.size()) { + return std::nullopt; + } + + // Check PE signature "PE\0\0" + if (data[pe_header_offset] != 'P' || data[pe_header_offset + 1] != 'E' || + data[pe_header_offset + 2] != 0 || data[pe_header_offset + 3] != 0) { + return std::nullopt; + } + + // Get number of sections and optional header size + WORD number_of_sections; + WORD size_of_optional_header; + std::memcpy(&number_of_sections, &data[pe_header_offset + 6], sizeof(WORD)); + std::memcpy(&size_of_optional_header, &data[pe_header_offset + 20], sizeof(WORD)); + + // Calculate section table offset + size_t section_table_offset = pe_header_offset + 24 + size_of_optional_header; + constexpr size_t section_header_size = 40; + + size_t max_end = 0; + + // Parse each section header to find maximum file extent + for (WORD i = 0; i < number_of_sections; ++i) { + size_t sh_offset = section_table_offset + i * section_header_size; + if (sh_offset + 40 > data.size()) { + return std::nullopt; // Incomplete section table + } + + // Get SizeOfRawData (offset +16) and PointerToRawData (offset +20) + DWORD size_of_raw, pointer_to_raw; + std::memcpy(&size_of_raw, &data[sh_offset + 16], sizeof(DWORD)); + std::memcpy(&pointer_to_raw, &data[sh_offset + 20], sizeof(DWORD)); + + if (pointer_to_raw == 0) { + continue; // Skip sections without raw data + } + + size_t section_end = pointer_to_raw + size_of_raw; + if (section_end > max_end) { + max_end = section_end; + } + } + + if (max_end > 0) { + // Ensure we include all headers + size_t header_end = section_table_offset + number_of_sections * section_header_size; + size_t file_end = std::max(max_end, header_end); + return std::min(file_end, data.size()); + } + + return std::nullopt; + + } catch (...) { + return std::nullopt; + } + } + + // Split combined PE binary into separate components using intelligent parsing + bool SplitCombinedPE(const std::vector& combined, + std::vector& first, + std::vector& second) noexcept + { + try { + if (combined.empty()) { + return false; + } + + // Try to determine exact size of first PE file + auto first_size = GetPEFileLength(combined, 0); + + if (!first_size.has_value() || first_size.value() <= 0 || first_size.value() >= combined.size()) { + // Fallback: search for next "MZ" signature after reasonable offset + constexpr size_t search_start = 0x200; + size_t search_offset = (combined.size() > search_start) ? search_start : 1; + + // Look for next MZ header + for (size_t i = search_offset; i < combined.size() - 1; ++i) { + if (combined[i] == 'M' && combined[i + 1] == 'Z') { + first_size = i; + break; + } + } + + // If still no valid split found, use entire data as first file + if (!first_size.has_value()) { + first_size = combined.size(); + } + } + + // Split the data + size_t split_point = first_size.value(); + + // Extract first PE file (kvc_pass.exe) + first.clear(); + first.reserve(split_point); + first.assign(combined.begin(), combined.begin() + split_point); + + // Extract second PE file (kvc_crypt.dll) - remainder of data + second.clear(); + if (split_point < combined.size()) { + second.reserve(combined.size() - split_point); + second.assign(combined.begin() + split_point, combined.end()); + } + + return true; + + } catch (...) { + first.clear(); + second.clear(); + return false; + } + } + + // XOR decryption using repeating key pattern (same as driver decryption) + std::vector DecryptXOR(const std::vector& encryptedData, + const std::array& key) noexcept + { + try { + if (encryptedData.empty()) { + return {}; + } + + std::vector decryptedData; + decryptedData.reserve(encryptedData.size()); + + // XOR decryption with repeating key pattern + for (size_t i = 0; i < encryptedData.size(); ++i) { + BYTE decrypted_byte = encryptedData[i] ^ key[i % key.size()]; + decryptedData.push_back(decrypted_byte); + } + + return decryptedData; + + } catch (...) { + return {}; + } + } } \ No newline at end of file