From 9c9a660f94d6153f43e276dfe8818536165b26bd Mon Sep 17 00:00:00 2001 From: xaitax Date: Thu, 6 Jun 2024 23:14:04 +0200 Subject: [PATCH] v0.3 - Added permission fix --- README.md | 36 ++++++++----- totalrecall.py | 144 +++++++++++++------------------------------------ 2 files changed, 61 insertions(+), 119 deletions(-) diff --git a/README.md b/README.md index d0e9135..714561a 100644 --- a/README.md +++ b/README.md @@ -79,28 +79,30 @@ options: ### Example Output ```bash -$ totalrecall.py --search password --from_date 2024-06-04 --to_date 2024-06-04 -___________ __ .__ __________ .__ .__ -\__ ___/____/ |______ | |\______ \ ____ ____ _____ | | | | - | | / _ \ __\__ \ | | | _// __ \_/ ___\\__ \ | | | | +$ totalrecall.py --search password --from_date 2024-06-04 --to_date 2024-06-05 + +___________ __ .__ __________ .__ .__ +\__ ___/____/ |______ | |\______ \ ____ ____ _____ | | | | + | | / _ \ __\__ \ | | | _// __ \_/ ___\\__ \ | | | | | |( <_> ) | / __ \| |_| | \ ___/\ \___ / __ \| |_| |__ |____| \____/|__| (____ /____/____|_ /\___ >\___ >____ /____/____/ - \/ \/ \/ \/ \/ -v0.2 / Alexander Hagenah / @xaitax / ah@primepage.de + \/ \/ \/ \/ +v0.3 / Alexander Hagenah / @xaitax / ah@primepage.de +āœ… Permissions modified for C:\Users\alex\AppData\Local\CoreAIPlatform.00\UKP and all its subdirectories and files šŸ“ Recall folder found: C:\Users\alex\AppData\Local\CoreAIPlatform.00\UKP\{D87DDB65-90BE-4399-BB1B-5BEB0B1D12CB} 🟢 Windows Recall feature found. Do you want to proceed with the extraction? (yes/no): yes -šŸ“‚ Creating extraction folder: C:\Users\alex\Downloads\TotalRecall\2024-06-04-13-49_Recall_Extraction +šŸ“‚ Creating extraction folder: C:\Users\alex\Downloads\TotalRecall\2024-06-06-21-02_Recall_Extraction -🪟 Captured Windows: 133 -šŸ“ø Images Taken: 36 -šŸ” Search results for 'password': 22 +🪟 Captured Windows: 166 +šŸ“ø Images Taken: 46 +šŸ” Search results for 'password': 32 šŸ“„ Summary of the extraction is available in the file: -C:\Users\alex\Downloads\TotalRecall\2024-06-04-13-49_Recall_Extraction\TotalRecall.txt +C:\Users\alex\Downloads\TotalRecall\2024-06-06-21-02_Recall_Extraction\TotalRecall.txt šŸ“‚ Full extraction folder path: -C:\Users\alex\Downloads\TotalRecall\2024-06-04-13-49_Recall_Extraction +C:\Users\alex\Downloads\TotalRecall\2024-06-06-21-02_Recall_Extraction ``` ### How TotalRecall Works @@ -133,6 +135,16 @@ C:\Users\alex\Downloads\TotalRecall\2024-06-04-13-49_Recall_Extraction TotalRecall provides a straightforward way to explore the data collected by Windows Recall. It's no rocket science whatsoever. +## Changelog + +### [24. May 2024] - Version 0.3 + +- **Permission Fix**: Added the `modify_permissions` function to ensure the script has the necessary permissions to access and manipulate files within the target directories, using the `icacls` command. Thank you [James Forshaw](https://x.com/tiraniddo). + +### [04. June 2024] - Version 0.2 + +- **Initial release** + ## FAQ Kevin Beaumont ([@GossiTheDog](https://x.com/GossiTheDog)) wrote a [very good article](https://doublepulsar.com/recall-stealing-everything-youve-ever-typed-or-viewed-on-your-own-windows-pc-is-now-possible-da3e12e9465e) about the Recall disaster as well with a spot-on FAQ that I will blatantly steal with his permission. diff --git a/totalrecall.py b/totalrecall.py index 57fed75..bc71249 100644 --- a/totalrecall.py +++ b/totalrecall.py @@ -14,7 +14,6 @@ YELLOW = "\033[93m" RED = "\033[91m" ENDC = "\033[0m" - def display_banner(): banner = ( r""" @@ -31,7 +30,6 @@ v""" ) print(BLUE + banner + ENDC) - def modify_permissions(path): try: subprocess.run( @@ -40,31 +38,22 @@ def modify_permissions(path): stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) - print( - f"{GREEN}āœ… Permissions modified for {path} and all its subdirectories and files{ENDC}" - ) + print(f"{GREEN}āœ… Permissions modified for {path} and all its subdirectories and files{ENDC}") except subprocess.CalledProcessError as e: print(f"{RED}āŒ Failed to modify permissions for {path}: {e}{ENDC}") - def main(from_date=None, to_date=None, search_term=None): display_banner() username = getpass.getuser() - base_path = f"C:\\Users\\{username}\\AppData\\Local\\CoreAIPlatform.00\\UKP" - guid_folder = None - if os.path.exists(base_path): - modify_permissions(base_path) - for folder_name in os.listdir(base_path): - folder_path = os.path.join(base_path, folder_name) - if os.path.isdir(folder_path): - guid_folder = folder_path - break - else: + if not os.path.exists(base_path): print("🚫 Base path does not exist.") return + modify_permissions(base_path) + guid_folder = next((os.path.join(base_path, folder_name) for folder_name in os.listdir(base_path) if os.path.isdir(os.path.join(base_path, folder_name))), None) + if not guid_folder: print("🚫 Could not find the GUID folder.") return @@ -74,32 +63,22 @@ def main(from_date=None, to_date=None, search_term=None): db_path = os.path.join(guid_folder, "ukg.db") image_store_path = os.path.join(guid_folder, "ImageStore") - if not os.path.exists(db_path) or not os.path.exists(image_store_path): + if not (os.path.exists(db_path) and os.path.exists(image_store_path)): print("🚫 Windows Recall feature not found. Nothing to extract.") return - proceed = input( - "🟢 Windows Recall feature found. Do you want to proceed with the extraction? (yes/no): " - ) - if proceed.lower() != "yes": + proceed = input("🟢 Windows Recall feature found. Do you want to proceed with the extraction? (yes/no): ").strip().lower() + if proceed != "yes": print("āš ļø Extraction aborted.") return timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M") extraction_folder = os.path.join(os.getcwd(), f"{timestamp}_Recall_Extraction") - - if not os.path.exists(extraction_folder): - os.makedirs(extraction_folder) - print(f"šŸ“‚ Creating extraction folder: {extraction_folder}\n") - else: - print(f"šŸ“‚ Using existing extraction folder: {extraction_folder}\n") + os.makedirs(extraction_folder, exist_ok=True) + print(f"šŸ“‚ Creating extraction folder: {extraction_folder}\n") shutil.copy(db_path, extraction_folder) - shutil.copytree( - image_store_path, - os.path.join(extraction_folder, "ImageStore"), - dirs_exist_ok=True, - ) + shutil.copytree(image_store_path, os.path.join(extraction_folder, "ImageStore"), dirs_exist_ok=True) for image_file in os.listdir(os.path.join(extraction_folder, "ImageStore")): image_path = os.path.join(extraction_folder, "ImageStore", image_file) @@ -111,72 +90,42 @@ def main(from_date=None, to_date=None, search_term=None): conn = sqlite3.connect(db_extraction_path) cursor = conn.cursor() - from_date_timestamp = None - to_date_timestamp = None + from_date_timestamp = int(datetime.strptime(from_date, "%Y-%m-%d").timestamp()) * 1000 if from_date else None + to_date_timestamp = int((datetime.strptime(to_date, "%Y-%m-%d") + timedelta(days=1)).timestamp()) * 1000 if to_date else None - if from_date: - from_date_timestamp = ( - int(datetime.strptime(from_date, "%Y-%m-%d").timestamp()) * 1000 - ) - if to_date: - to_date_timestamp = ( - int( - (datetime.strptime(to_date, "%Y-%m-%d") + timedelta(days=1)).timestamp() - ) - * 1000 - ) - - query = """ - SELECT WindowTitle, TimeStamp, ImageToken - FROM WindowCapture - WHERE (WindowTitle IS NOT NULL OR ImageToken IS NOT NULL) - """ + query = "SELECT WindowTitle, TimeStamp, ImageToken FROM WindowCapture WHERE (WindowTitle IS NOT NULL OR ImageToken IS NOT NULL)" cursor.execute(query) rows = cursor.fetchall() - output = [] - captured_windows_count = 0 - images_taken_count = 0 - captured_windows = [] images_taken = [] - - for row in rows: - window_title, timestamp, image_token = row - if (from_date_timestamp is None or from_date_timestamp <= timestamp) and ( - to_date_timestamp is None or timestamp < to_date_timestamp - ): - readable_timestamp = datetime.fromtimestamp(timestamp / 1000).strftime( - "%Y-%m-%d %H:%M:%S" - ) + for window_title, timestamp, image_token in rows: + if (from_date_timestamp is None or from_date_timestamp <= timestamp) and (to_date_timestamp is None or timestamp < to_date_timestamp): + readable_timestamp = datetime.fromtimestamp(timestamp / 1000).strftime("%Y-%m-%d %H:%M:%S") if window_title: captured_windows.append(f"[{readable_timestamp}] {window_title}") - captured_windows_count += 1 if image_token: images_taken.append(f"[{readable_timestamp}] {image_token}") - images_taken_count += 1 - output.append(f"🪟 Captured Windows: {captured_windows_count}") - output.append(f"šŸ“ø Images Taken: {images_taken_count}") + captured_windows_count = len(captured_windows) + images_taken_count = len(images_taken) + output = [ + f"🪟 Captured Windows: {captured_windows_count}", + f"šŸ“ø Images Taken: {images_taken_count}" + ] if search_term: - search_query = f""" - SELECT c1, c2 - FROM WindowCaptureTextIndex_content - WHERE c1 LIKE '%{search_term}%' OR c2 LIKE '%{search_term}%' - """ - cursor.execute(search_query) + search_query = f"SELECT c1, c2 FROM WindowCaptureTextIndex_content WHERE c1 LIKE ? OR c2 LIKE ?" + cursor.execute(search_query, (f"%{search_term}%", f"%{search_term}%")) search_results = cursor.fetchall() search_results_count = len(search_results) output.append(f"šŸ” Search results for '{search_term}': {search_results_count}") + search_output = [f"c1: {result[0]}, c2: {result[1]}" for result in search_results] + else: search_output = [] - for result in search_results: - search_output.append(f"c1: {result[0]}, c2: {result[1]}") - with open( - os.path.join(extraction_folder, "TotalRecall.txt"), "w", encoding="utf-8" - ) as file: + with open(os.path.join(extraction_folder, "TotalRecall.txt"), "w", encoding="utf-8") as file: file.write("Captured Windows:\n") file.write("\n".join(captured_windows)) file.write("\n\nImages Taken:\n") @@ -195,38 +144,19 @@ def main(from_date=None, to_date=None, search_term=None): print(f"\nšŸ“‚ Full extraction folder path:") print(f"{YELLOW}{extraction_folder}{ENDC}") - if __name__ == "__main__": - parser = argparse.ArgumentParser( - description="Extract and display Windows Recall data." - ) - parser.add_argument( - "--from_date", - help="The start date in YYYY-MM-DD format.", - type=str, - default=None, - ) - parser.add_argument( - "--to_date", help="The end date in YYYY-MM-DD format.", type=str, default=None - ) - parser.add_argument( - "--search", - help="Search term for text recognition data.", - type=str, - default=None, - ) + parser = argparse.ArgumentParser(description="Extract and display Windows Recall data.") + parser.add_argument("--from_date", help="The start date in YYYY-MM-DD format.", type=str, default=None) + parser.add_argument("--to_date", help="The end date in YYYY-MM-DD format.", type=str, default=None) + parser.add_argument("--search", help="Search term for text recognition data.", type=str, default=None) args = parser.parse_args() - from_date = args.from_date - to_date = args.to_date - search_term = args.search - date_format = "%Y-%m-%d" try: - if from_date: - datetime.strptime(from_date, date_format) - if to_date: - datetime.strptime(to_date, date_format) + if args.from_date: + datetime.strptime(args.from_date, "%Y-%m-%d") + if args.to_date: + datetime.strptime(args.to_date, "%Y-%m-%d") except ValueError: parser.error("Date format must be YYYY-MM-DD.") - main(from_date, to_date, search_term) + main(args.from_date, args.to_date, args.search)