Skip to content

Analytics dll secure multihash #1738

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 47 additions & 12 deletions analytics/generate_windows_stubs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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 <windows.h>\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")
Expand All @@ -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))
Expand Down Expand Up @@ -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()
Expand Down
10 changes: 4 additions & 6 deletions analytics/src/analytics_desktop.cc
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,10 @@ void Initialize(const App& app) {

#if defined(_WIN32)
if (!g_analytics_module) {
std::vector<std::vector<unsigned char>> allowed_hashes;
std::vector<unsigned char> current_hash;
current_hash.assign(FirebaseAnalytics_WindowsDllHash,
FirebaseAnalytics_WindowsDllHash +
sizeof(FirebaseAnalytics_WindowsDllHash));
allowed_hashes.push_back(current_hash);
std::vector<std::string> 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(
Expand Down
9 changes: 6 additions & 3 deletions analytics/src/analytics_desktop_dynamic.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 ---
Expand Down
6 changes: 4 additions & 2 deletions analytics/src/analytics_desktop_dynamic.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,10 @@ extern const int FirebaseAnalytics_DynamicFunctionCount;

#include <windows.h>

// 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.
Expand Down
100 changes: 63 additions & 37 deletions analytics/src/analytics_windows.cc
Original file line number Diff line number Diff line change
Expand Up @@ -88,103 +88,134 @@ static std::wstring GetExecutablePath() {
}
}

// Helper function to calculate SHA256 hash of a file.
static std::vector<BYTE> 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<BYTE> 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<BYTE> 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<std::vector<unsigned char>>& allowed_hashes) {
const std::vector<std::string>& allowed_hashes) {
if (library_filename == nullptr || library_filename[0] == L'\0') {
LogError(LOG_TAG "Invalid arguments.");
return nullptr;
Expand Down Expand Up @@ -245,25 +276,20 @@ HMODULE VerifyAndLoadAnalyticsLibrary(

HMODULE hModule = nullptr;

std::vector<BYTE> 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) {
Expand Down
3 changes: 2 additions & 1 deletion analytics/src/analytics_windows.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include <windows.h>

#include <string>
#include <vector>

namespace firebase {
Expand All @@ -25,7 +26,7 @@ namespace internal {

HMODULE VerifyAndLoadAnalyticsLibrary(
const wchar_t* library_filename,
const std::vector<std::vector<unsigned char>>& allowed_hashes);
const std::vector<std::string>& allowed_hashes);

} // namespace internal
} // namespace analytics
Expand Down
Loading