Created
November 10, 2025 15:35
-
-
Save justaguywhocodes/5e2c91ac392f1af04907ff600f662e5f to your computer and use it in GitHub Desktop.
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 characters
| #include <windows.h> | |
| #include <shlobj.h> | |
| #include <iostream> | |
| #include <string> | |
| #include "sqlite3.h" | |
| #include <wincrypt.h> | |
| #include <cstdlib> // For system("pause") | |
| #pragma comment(lib, "crypt32.lib") | |
| #pragma comment(lib, "sqlite3.lib") | |
| int main() { | |
| std::cout << "Starting program..." << std::endl; | |
| char localAppData[MAX_PATH]; | |
| HRESULT hr = SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, localAppData); | |
| if (FAILED(hr)) { | |
| std::cerr << "Error getting Local AppData path." << std::endl; | |
| system("pause"); | |
| return 1; | |
| } | |
| std::cout << "Local AppData path: " << localAppData << std::endl; | |
| std::string basePath = std::string(localAppData) + "\\ConnectedDevicesPlatform"; | |
| std::cout << "Scanning base path: " << basePath << std::endl; | |
| WIN32_FIND_DATAA findData; | |
| HANDLE hFind = FindFirstFileA((basePath + "\\*").c_str(), &findData); | |
| if (hFind == INVALID_HANDLE_VALUE) { | |
| std::cerr << "Error opening ConnectedDevicesPlatform directory." << std::endl; | |
| system("pause"); | |
| return 1; | |
| } | |
| std::cout << "Successfully opened directory for scanning subfolders." << std::endl; | |
| bool found = false; | |
| do { | |
| if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && | |
| strcmp(findData.cFileName, ".") != 0 && | |
| strcmp(findData.cFileName, "..") != 0) { | |
| std::string idFolder = basePath + "\\" + findData.cFileName; | |
| std::string dbPath = idFolder + "\\ActivitiesCache.db"; | |
| std::cout << "Checking subfolder: " << idFolder << std::endl; | |
| DWORD attr = GetFileAttributesA(dbPath.c_str()); | |
| if (attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY)) { | |
| std::cout << "Found ActivitiesCache.db at: " << dbPath << std::endl; | |
| // Query the database | |
| sqlite3* db; | |
| int rc = sqlite3_open(dbPath.c_str(), &db); | |
| if (rc != SQLITE_OK) { | |
| std::cerr << "Cannot open database: " << sqlite3_errmsg(db) << std::endl; | |
| sqlite3_close(db); | |
| continue; | |
| } | |
| std::cout << "Database opened successfully." << std::endl; | |
| const char* sql = "SELECT ClipboardPayload FROM ActivityOperation"; | |
| sqlite3_stmt* stmt; | |
| rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); | |
| if (rc != SQLITE_OK) { | |
| std::cerr << "Failed to prepare statement: " << sqlite3_errmsg(db) << std::endl; | |
| sqlite3_close(db); | |
| continue; | |
| } | |
| std::cout << "SQL prepared: " << sql << std::endl; | |
| // Print header for all columns | |
| int num_cols = sqlite3_column_count(stmt); | |
| std::cout << "All Columns:" << std::endl; | |
| for (int i = 0; i < num_cols; ++i) { | |
| std::cout << sqlite3_column_name(stmt, i) << "\t"; | |
| } | |
| std::cout << std::endl; | |
| int row_count = 0; | |
| while (sqlite3_step(stmt) == SQLITE_ROW) { | |
| row_count++; | |
| std::cout << "Row " << row_count << ":" << std::endl; | |
| for (int i = 0; i < num_cols; ++i) { | |
| int col_type = sqlite3_column_type(stmt, i); | |
| const char* col_name = sqlite3_column_name(stmt, i); | |
| switch (col_type) { | |
| case SQLITE_INTEGER: | |
| std::cout << sqlite3_column_int64(stmt, i); | |
| break; | |
| case SQLITE_FLOAT: | |
| std::cout << sqlite3_column_double(stmt, i); | |
| break; | |
| case SQLITE_TEXT: { | |
| const char* text = reinterpret_cast<const char*>(sqlite3_column_text(stmt, i)); | |
| if (text) { | |
| // Attempt base64 decode | |
| DWORD decoded_size = 0; | |
| DWORD text_len = static_cast<DWORD>(strlen(text)); | |
| BOOL crypt_res = CryptStringToBinaryA(text, text_len, FALSE, NULL, &decoded_size, NULL, NULL); | |
| if (crypt_res && decoded_size > 0) { | |
| BYTE* decoded = new BYTE[decoded_size]; | |
| crypt_res = CryptStringToBinaryA(text, text_len, FALSE, decoded, &decoded_size, NULL, NULL); | |
| if (crypt_res) { | |
| // Assume decoded is text, print it | |
| std::cout.write(reinterpret_cast<char*>(decoded), decoded_size); | |
| } | |
| else { | |
| std::cerr << "Failed to decode base64 for TEXT column " << col_name << std::endl; | |
| std::cout << text; // Fallback to original | |
| } | |
| delete[] decoded; | |
| } | |
| else { | |
| // Not base64 or error, print original | |
| std::cout << text; | |
| } | |
| } | |
| else { | |
| std::cout << "NULL"; | |
| } | |
| break; | |
| } | |
| case SQLITE_BLOB: { | |
| const void* blob = sqlite3_column_blob(stmt, i); | |
| int blob_size = sqlite3_column_bytes(stmt, i); | |
| if (blob && blob_size > 0) { | |
| // For ClipboardPayload, it's a JSON blob, so skip direct base64 and go to JSON extraction | |
| // Assume it's JSON text and extract "content" key value | |
| std::string json_str(reinterpret_cast<const char*>(blob), blob_size); | |
| size_t content_pos = json_str.find("\"content\""); | |
| bool extracted = false; | |
| if (content_pos != std::string::npos) { | |
| size_t colon_pos = json_str.find(':', content_pos); | |
| if (colon_pos != std::string::npos) { | |
| size_t value_start = colon_pos + 1; | |
| // Skip whitespace | |
| while (value_start < json_str.size() && | |
| (json_str[value_start] == ' ' || json_str[value_start] == '\t' || | |
| json_str[value_start] == '\n' || json_str[value_start] == '\r')) { | |
| ++value_start; | |
| } | |
| if (value_start < json_str.size() && json_str[value_start] == '"') { | |
| ++value_start; // Skip opening quote | |
| std::string content; | |
| size_t pos = value_start; | |
| while (pos < json_str.size()) { | |
| char c = json_str[pos]; | |
| if (c == '"') { | |
| // End of string | |
| break; | |
| } | |
| else if (c == '\\') { | |
| ++pos; | |
| if (pos < json_str.size()) { | |
| char next = json_str[pos]; | |
| switch (next) { | |
| case '"': content += '"'; break; | |
| case '\\': content += '\\'; break; | |
| case '/': content += '/'; break; | |
| case 'b': content += '\b'; break; | |
| case 'f': content += '\f'; break; | |
| case 'n': content += '\n'; break; | |
| case 'r': content += '\r'; break; | |
| case 't': content += '\t'; break; | |
| default: content += next; break; // Pass through others | |
| } | |
| } | |
| } | |
| else { | |
| content += c; | |
| } | |
| ++pos; | |
| } | |
| // Now base64 decode the content | |
| DWORD content_decoded_size = 0; | |
| DWORD content_len = static_cast<DWORD>(content.length()); | |
| BOOL content_crypt_res = CryptStringToBinaryA(content.c_str(), content_len, FALSE, NULL, &content_decoded_size, NULL, NULL); | |
| if (content_crypt_res && content_decoded_size > 0) { | |
| BYTE* content_decoded = new BYTE[content_decoded_size]; | |
| content_crypt_res = CryptStringToBinaryA(content.c_str(), content_len, FALSE, content_decoded, &content_decoded_size, NULL, NULL); | |
| if (content_crypt_res) { | |
| // Print decoded content | |
| std::cout.write(reinterpret_cast<char*>(content_decoded), content_decoded_size); | |
| extracted = true; | |
| } | |
| else { | |
| std::cerr << "Failed to decode base64 content for BLOB column " << col_name << std::endl; | |
| std::cout << content; // Fallback to raw content | |
| extracted = true; | |
| } | |
| delete[] content_decoded; | |
| } | |
| else { | |
| // Not base64 or error, print raw content | |
| std::cout << content; | |
| extracted = true; | |
| } | |
| } | |
| } | |
| } | |
| if (!extracted) { | |
| std::cout << "[BLOB " << blob_size << " bytes]"; | |
| } | |
| } | |
| else { | |
| std::cout << "NULL"; | |
| } | |
| break; | |
| } | |
| case SQLITE_NULL: | |
| std::cout << "NULL"; | |
| break; | |
| default: | |
| std::cout << "[UNKNOWN]"; | |
| break; | |
| } | |
| std::cout << "\t"; | |
| } | |
| std::cout << std::endl; | |
| } | |
| std::cout << "Processed " << row_count << " rows." << std::endl; | |
| sqlite3_finalize(stmt); | |
| sqlite3_close(db); | |
| found = true; | |
| // Assuming only one such folder; break if found | |
| break; | |
| } | |
| else { | |
| std::cout << "No ActivitiesCache.db in " << idFolder << std::endl; | |
| } | |
| } | |
| } while (FindNextFileA(hFind, &findData)); | |
| FindClose(hFind); | |
| if (!found) { | |
| std::cout << "ActivitiesCache folder not found." << std::endl; | |
| } | |
| std::cout << "Program ending. Press any key to close..." << std::endl; | |
| system("pause"); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment