diff --git a/analytics/generate_windows_stubs.py b/analytics/generate_windows_stubs.py index cc89422395..9e7aa0e5aa 100755 --- a/analytics/generate_windows_stubs.py +++ b/analytics/generate_windows_stubs.py @@ -55,13 +55,36 @@ def generate_function_pointers(dll_file_path, header_file_path, output_h_path, o initialized pointers, and a dynamic loading function for Windows. Args: - header_file_path (str): The path to the DLL file. + dll_file_path (str): The path to the DLL file. header_file_path (str): The path to the input C header file. output_h_path (str): The path for the generated C header output file. output_c_path (str): The path for the generated C source output file. """ print(f"Reading DLL file: {dll_file_path}") - dll_hash = hash_file(dll_file_path) + dll_hash = hash_file(dll_file_path) # This is binary + + # --- Manage known hashes --- + hash_file_path = os.path.join(os.path.dirname(dll_file_path), "known_dll_hashes.txt") + known_hex_hashes = [] + try: + with open(hash_file_path, 'r') as f: + for line in f: + known_hex_hashes.append(line.strip()) + except FileNotFoundError: + print(f"Info: '{hash_file_path}' not found, will be created.") + pass # File doesn't exist, list remains empty + + current_dll_hex_hash = dll_hash.hex() + if current_dll_hex_hash not in known_hex_hashes: + known_hex_hashes.append(current_dll_hex_hash) + # Sort for consistency, although not strictly required by the prompt + # known_hex_hashes.sort() # Decided against sorting to maintain order of addition for now + + with open(hash_file_path, 'w') as f: + for hex_hash in known_hex_hashes: + f.write(hex_hash + '\n') + print(f"Updated known hashes in: {hash_file_path}") + # --- End of manage known hashes --- print(f"Reading header file: {header_file_path}") try: @@ -157,8 +180,10 @@ def generate_function_pointers(dll_file_path, header_file_path, output_h_path, o f.write("\n// --- Dynamic Loader Declaration for Windows ---\n") f.write("#if defined(_WIN32)\n\n") f.write('#include \n') - f.write(f'\n// Google Analytics Windows DLL SHA256 hash, to be verified before loading.') - f.write(f'\nextern const unsigned char FirebaseAnalytics_WindowsDllHash[{len(dll_hash)}];\n\n'); + f.write('\n// Array of known Google Analytics Windows DLL SHA256 hashes (hex strings).\n') + f.write('extern const char* FirebaseAnalytics_KnownWindowsDllHashes[];\n') + f.write('// Count of known Google Analytics Windows DLL SHA256 hashes.\n') + f.write('extern const int FirebaseAnalytics_KnownWindowsDllHashCount;\n\n') f.write('// Load Analytics functions from the given DLL handle into function pointers.\n') f.write(f'// Returns the number of functions successfully loaded.\n') f.write("int FirebaseAnalytics_LoadDynamicFunctions(HMODULE dll_handle);\n\n") @@ -182,11 +207,25 @@ def generate_function_pointers(dll_file_path, header_file_path, output_h_path, o f.write("// clang-format off\n") f.write(f'\n// Number of Google Analytics functions expected to be loaded from the DLL.') f.write(f'\nconst int FirebaseAnalytics_DynamicFunctionCount = {len(function_details_for_loader)};\n\n'); + # f.write("#if defined(_WIN32)\n") + # f.write('// Google Analytics Windows DLL SHA256 hash, to be verified before loading.\n') + # f.write('const unsigned char FirebaseAnalytics_WindowsDllHash[] = {\n ') + # f.write(', '.join(["0x%02x" % s for s in dll_hash])) + # f.write('\n};\n') + # f.write("#endif // defined(_WIN32)\n") + f.write("#if defined(_WIN32)\n") - f.write('// Google Analytics Windows DLL SHA256 hash, to be verified before loading.\n') - f.write('const unsigned char FirebaseAnalytics_WindowsDllHash[] = {\n ') - f.write(', '.join(["0x%02x" % s for s in dll_hash])) - f.write('\n};\n') + f.write('// Array of known Google Analytics Windows DLL SHA256 hashes (hex strings).\n') + f.write('const char* FirebaseAnalytics_KnownWindowsDllHashes[] = {\n') + if known_hex_hashes: + for i, hex_hash in enumerate(known_hex_hashes): + f.write(f' "{hex_hash}"') + if i < len(known_hex_hashes) - 1: + f.write(',') + f.write('\n') + f.write('};\n\n') + f.write('// Count of known Google Analytics Windows DLL SHA256 hashes.\n') + f.write(f'const int FirebaseAnalytics_KnownWindowsDllHashCount = {len(known_hex_hashes)};\n') f.write("#endif // defined(_WIN32)\n") f.write("\n// --- Stub Function Definitions ---\n") f.write("\n\n".join(stub_functions)) @@ -231,25 +270,21 @@ def generate_function_pointers(dll_file_path, header_file_path, output_h_path, o parser.add_argument( "--windows_dll", default = os.path.join(os.path.dirname(sys.argv[0]), "windows/analytics_win.dll"), - #required=True, help="Path to the DLL file to calculate a hash." ) parser.add_argument( "--windows_header", default = os.path.join(os.path.dirname(sys.argv[0]), "windows/include/public/c/analytics.h"), - #required=True, help="Path to the input C header file." ) parser.add_argument( "--output_header", default = os.path.join(os.path.dirname(sys.argv[0]), INCLUDE_PATH, "analytics_desktop_dynamic.h"), - #required=True, help="Path for the generated output header file." ) parser.add_argument( "--output_source", default = os.path.join(os.path.dirname(sys.argv[0]), INCLUDE_PATH, "analytics_desktop_dynamic.c"), - #required=True, help="Path for the generated output source file." ) args = parser.parse_args() diff --git a/analytics/src/analytics_desktop.cc b/analytics/src/analytics_desktop.cc index 5e9669bf7b..5fd7da9afd 100644 --- a/analytics/src/analytics_desktop.cc +++ b/analytics/src/analytics_desktop.cc @@ -61,12 +61,10 @@ void Initialize(const App& app) { #if defined(_WIN32) if (!g_analytics_module) { - std::vector> allowed_hashes; - std::vector current_hash; - current_hash.assign(FirebaseAnalytics_WindowsDllHash, - FirebaseAnalytics_WindowsDllHash + - sizeof(FirebaseAnalytics_WindowsDllHash)); - allowed_hashes.push_back(current_hash); + std::vector allowed_hashes; + for (int i=0; i < FirebaseAnalytics_KnownWindowsDllHashCount; i++) { + allowed_hashes.push_back(std::string(FirebaseAnalytics_KnownWindowsDllHashes[i])); + } g_analytics_module = firebase::analytics::internal::VerifyAndLoadAnalyticsLibrary( diff --git a/analytics/src/analytics_desktop_dynamic.c b/analytics/src/analytics_desktop_dynamic.c index e629e59474..8ecc9ba057 100644 --- a/analytics/src/analytics_desktop_dynamic.c +++ b/analytics/src/analytics_desktop_dynamic.c @@ -26,10 +26,13 @@ static void* g_stub_memory = NULL; const int FirebaseAnalytics_DynamicFunctionCount = 19; #if defined(_WIN32) -// Google Analytics Windows DLL SHA256 hash, to be verified on load. -const unsigned char FirebaseAnalytics_WindowsDllHash[] = { - 0xc1, 0xb9, 0xff, 0x6e, 0x91, 0x19, 0xc3, 0x0b, 0xbe, 0xb7, 0x47, 0x23, 0x26, 0xdc, 0xde, 0x41, 0x8f, 0x45, 0x68, 0x2e, 0x6b, 0x82, 0x2e, 0x25, 0xee, 0xd9, 0x22, 0xfe, 0x6e, 0x3c, 0xc6, 0x98 +// Array of known Google Analytics Windows DLL SHA256 hashes (hex strings). +const char* FirebaseAnalytics_KnownWindowsDllHashes[] = { + "c1b9ff6e9119c30bbeb7472326dcde418f45682e6b822e25eed922fe6e3cc698" }; + +// Count of known Google Analytics Windows DLL SHA256 hashes. +const int FirebaseAnalytics_KnownWindowsDllHashCount = 1; #endif // defined(_WIN32) // --- Stub Function Definitions --- diff --git a/analytics/src/analytics_desktop_dynamic.h b/analytics/src/analytics_desktop_dynamic.h index 4d7560388b..fe878a4daa 100644 --- a/analytics/src/analytics_desktop_dynamic.h +++ b/analytics/src/analytics_desktop_dynamic.h @@ -120,8 +120,10 @@ extern const int FirebaseAnalytics_DynamicFunctionCount; #include -// Google Analytics Windows DLL SHA256 hash, to be verified before loading. -extern const unsigned char FirebaseAnalytics_WindowsDllHash[32]; +// Array of known Google Analytics Windows DLL SHA256 hashes (hex strings). +extern const char* FirebaseAnalytics_KnownWindowsDllHashes[]; +// Count of known Google Analytics Windows DLL SHA256 hashes. +extern const int FirebaseAnalytics_KnownWindowsDllHashCount; // Load Analytics functions from the given DLL handle into function pointers. // Returns the number of functions successfully loaded. diff --git a/analytics/src/analytics_windows.cc b/analytics/src/analytics_windows.cc index 2eb7ad0efd..1e3991e381 100644 --- a/analytics/src/analytics_windows.cc +++ b/analytics/src/analytics_windows.cc @@ -88,103 +88,134 @@ static std::wstring GetExecutablePath() { } } -// Helper function to calculate SHA256 hash of a file. -static std::vector CalculateFileSha256(HANDLE hFile) { +// Helper function to calculate the SHA256 hash of a file and return it as a hex +// string (upper-case). +static std::string CalculateFileSha256(HANDLE hFile) { HCRYPTPROV hProv = 0; HCRYPTHASH hHash = 0; - std::vector result_hash_value; + // Ensure the file pointer is at the beginning of the file. if (SetFilePointer(hFile, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { DWORD dwError = GetLastError(); LogError(LOG_TAG "CalculateFileSha256.SetFilePointer failed. Error: %u", dwError); - return result_hash_value; + return ""; // Return empty string on failure } // Acquire Crypto Provider. - // Using CRYPT_VERIFYCONTEXT for operations that don't require private key - // access. + // Using CRYPT_VERIFYCONTEXT for operations that don't require private key access. if (!CryptAcquireContextW(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) { DWORD dwError = GetLastError(); LogError(LOG_TAG "CalculateFileSha256.CryptAcquireContextW failed. Error: %u", dwError); - return result_hash_value; + return ""; } + // Create a hash object. if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash)) { DWORD dwError = GetLastError(); LogError(LOG_TAG "CalculateFileSha256.CryptCreateHash failed. Error: %u", dwError); CryptReleaseContext(hProv, 0); - return result_hash_value; + return ""; } + // Read the file in chunks and hash the data. BYTE rgbFile[1024]; DWORD cbRead = 0; - BOOL bReadSuccessLoop = TRUE; - while (true) { - bReadSuccessLoop = ReadFile(hFile, rgbFile, sizeof(rgbFile), &cbRead, NULL); - if (!bReadSuccessLoop) { + if (!ReadFile(hFile, rgbFile, sizeof(rgbFile), &cbRead, NULL)) { DWORD dwError = GetLastError(); LogError(LOG_TAG "CalculateFileSha256.ReadFile failed. Error: %u", dwError); CryptDestroyHash(hHash); CryptReleaseContext(hProv, 0); - return result_hash_value; + return ""; } + // End of file if (cbRead == 0) { break; } + // Add the chunk to the hash object. if (!CryptHashData(hHash, rgbFile, cbRead, 0)) { DWORD dwError = GetLastError(); LogError(LOG_TAG "CalculateFileSha256.CryptHashData failed. Error: %u", dwError); CryptDestroyHash(hHash); CryptReleaseContext(hProv, 0); - return result_hash_value; + return ""; } } + // --- Get the binary hash value --- DWORD cbHashValue = 0; DWORD dwCount = sizeof(DWORD); - if (!CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE*)&cbHashValue, &dwCount, - 0)) { + if (!CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE*)&cbHashValue, &dwCount, 0)) { DWORD dwError = GetLastError(); LogError(LOG_TAG - "CalculateFileSha256.CryptGetHashParam " - "(HP_HASHSIZE) failed. Error: " - "%u", + "CalculateFileSha256.CryptGetHashParam (HP_HASHSIZE) failed. " + "Error: %u", dwError); CryptDestroyHash(hHash); CryptReleaseContext(hProv, 0); - return result_hash_value; + return ""; } - result_hash_value.resize(cbHashValue); - if (!CryptGetHashParam(hHash, HP_HASHVAL, result_hash_value.data(), + std::vector binary_hash_value(cbHashValue); + if (!CryptGetHashParam(hHash, HP_HASHVAL, binary_hash_value.data(), &cbHashValue, 0)) { DWORD dwError = GetLastError(); LogError(LOG_TAG "CalculateFileSha256.CryptGetHashParam (HP_HASHVAL) failed. " "Error: %u", dwError); - result_hash_value.clear(); CryptDestroyHash(hHash); CryptReleaseContext(hProv, 0); - return result_hash_value; + return ""; + } + + // --- Convert the binary hash to a hex string --- + DWORD hex_string_size = 0; + if (!CryptBinaryToStringA(binary_hash_value.data(), binary_hash_value.size(), + CRYPT_STRING_HEXRAW | CRYPT_STRING_NOCRLF, + NULL, &hex_string_size)) { + DWORD dwError = GetLastError(); + LogError(LOG_TAG + "CalculateFileSha256.CryptBinaryToStringA (size) failed. Error: %u", + dwError); + CryptDestroyHash(hHash); + CryptReleaseContext(hProv, 0); + return ""; + } + + std::string hex_hash_string(hex_string_size, '\0'); + if (!CryptBinaryToStringA(binary_hash_value.data(), binary_hash_value.size(), + CRYPT_STRING_HEXRAW | CRYPT_STRING_NOCRLF, + &hex_hash_string[0], &hex_string_size)) { + DWORD dwError = GetLastError(); + LogError(LOG_TAG + "CalculateFileSha256.CryptBinaryToStringA (conversion) failed. Error: %u", + dwError); + CryptDestroyHash(hHash); + CryptReleaseContext(hProv, 0); + return ""; } + // Remove the null terminator from the string. + hex_hash_string.resize(hex_string_size); + + // --- Final Cleanup --- CryptDestroyHash(hHash); CryptReleaseContext(hProv, 0); - return result_hash_value; + + return hex_hash_string; } HMODULE VerifyAndLoadAnalyticsLibrary( const wchar_t* library_filename, - const std::vector>& allowed_hashes) { + const std::vector& allowed_hashes) { if (library_filename == nullptr || library_filename[0] == L'\0') { LogError(LOG_TAG "Invalid arguments."); return nullptr; @@ -245,25 +276,20 @@ HMODULE VerifyAndLoadAnalyticsLibrary( HMODULE hModule = nullptr; - std::vector calculated_hash = CalculateFileSha256(hFile); + std::string calculated_hash = CalculateFileSha256(hFile); - if (calculated_hash.empty()) { + if (calculated_hash.length() == 0) { LogError(LOG_TAG "Hash failed for Analytics DLL."); } else { bool hash_matched = false; for (const auto& expected_hash : allowed_hashes) { - if (calculated_hash.size() != expected_hash.size()) { - LogDebug(LOG_TAG - "Hash size mismatch for Analytics DLL. Expected: %zu, " - "Calculated: %zu. Trying next allowed hash.", - expected_hash.size(), calculated_hash.size()); - continue; - } - if (memcmp(calculated_hash.data(), expected_hash.data(), - expected_hash.size()) == 0) { + if (calculated_hash == expected_hash) { hash_matched = true; break; } + else { + LogDebug(LOG_TAG "Hash mismatch: got %s expected %s", calculated_hash.c_str(), expected_hash.c_str()); + } } if (hash_matched) { diff --git a/analytics/src/analytics_windows.h b/analytics/src/analytics_windows.h index f18683e026..7d9e4489fe 100644 --- a/analytics/src/analytics_windows.h +++ b/analytics/src/analytics_windows.h @@ -17,6 +17,7 @@ #include +#include #include namespace firebase { @@ -25,7 +26,7 @@ namespace internal { HMODULE VerifyAndLoadAnalyticsLibrary( const wchar_t* library_filename, - const std::vector>& allowed_hashes); + const std::vector& allowed_hashes); } // namespace internal } // namespace analytics