mirror of
https://github.com/xaitax/TotalRecall.git
synced 2025-08-31 17:41:58 +02:00
v0.3 - Added permission fix
This commit is contained in:
30
README.md
30
README.md
@@ -79,28 +79,30 @@ options:
|
|||||||
### Example Output
|
### Example Output
|
||||||
|
|
||||||
```bash
|
```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}
|
📁 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
|
🟢 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
|
🪟 Captured Windows: 166
|
||||||
📸 Images Taken: 36
|
📸 Images Taken: 46
|
||||||
🔍 Search results for 'password': 22
|
🔍 Search results for 'password': 32
|
||||||
|
|
||||||
📄 Summary of the extraction is available in the file:
|
📄 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:
|
📂 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
|
### 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.
|
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
|
## 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.
|
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.
|
||||||
|
144
totalrecall.py
144
totalrecall.py
@@ -14,7 +14,6 @@ YELLOW = "\033[93m"
|
|||||||
RED = "\033[91m"
|
RED = "\033[91m"
|
||||||
ENDC = "\033[0m"
|
ENDC = "\033[0m"
|
||||||
|
|
||||||
|
|
||||||
def display_banner():
|
def display_banner():
|
||||||
banner = (
|
banner = (
|
||||||
r"""
|
r"""
|
||||||
@@ -31,7 +30,6 @@ v"""
|
|||||||
)
|
)
|
||||||
print(BLUE + banner + ENDC)
|
print(BLUE + banner + ENDC)
|
||||||
|
|
||||||
|
|
||||||
def modify_permissions(path):
|
def modify_permissions(path):
|
||||||
try:
|
try:
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
@@ -40,31 +38,22 @@ def modify_permissions(path):
|
|||||||
stdout=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.DEVNULL,
|
||||||
)
|
)
|
||||||
print(
|
print(f"{GREEN}✅ Permissions modified for {path} and all its subdirectories and files{ENDC}")
|
||||||
f"{GREEN}✅ Permissions modified for {path} and all its subdirectories and files{ENDC}"
|
|
||||||
)
|
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
print(f"{RED}❌ Failed to modify permissions for {path}: {e}{ENDC}")
|
print(f"{RED}❌ Failed to modify permissions for {path}: {e}{ENDC}")
|
||||||
|
|
||||||
|
|
||||||
def main(from_date=None, to_date=None, search_term=None):
|
def main(from_date=None, to_date=None, search_term=None):
|
||||||
display_banner()
|
display_banner()
|
||||||
username = getpass.getuser()
|
username = getpass.getuser()
|
||||||
|
|
||||||
base_path = f"C:\\Users\\{username}\\AppData\\Local\\CoreAIPlatform.00\\UKP"
|
base_path = f"C:\\Users\\{username}\\AppData\\Local\\CoreAIPlatform.00\\UKP"
|
||||||
|
|
||||||
guid_folder = None
|
if not os.path.exists(base_path):
|
||||||
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:
|
|
||||||
print("🚫 Base path does not exist.")
|
print("🚫 Base path does not exist.")
|
||||||
return
|
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:
|
if not guid_folder:
|
||||||
print("🚫 Could not find the GUID folder.")
|
print("🚫 Could not find the GUID folder.")
|
||||||
return
|
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")
|
db_path = os.path.join(guid_folder, "ukg.db")
|
||||||
image_store_path = os.path.join(guid_folder, "ImageStore")
|
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.")
|
print("🚫 Windows Recall feature not found. Nothing to extract.")
|
||||||
return
|
return
|
||||||
|
|
||||||
proceed = input(
|
proceed = input("🟢 Windows Recall feature found. Do you want to proceed with the extraction? (yes/no): ").strip().lower()
|
||||||
"🟢 Windows Recall feature found. Do you want to proceed with the extraction? (yes/no): "
|
if proceed != "yes":
|
||||||
)
|
|
||||||
if proceed.lower() != "yes":
|
|
||||||
print("⚠️ Extraction aborted.")
|
print("⚠️ Extraction aborted.")
|
||||||
return
|
return
|
||||||
|
|
||||||
timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M")
|
timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M")
|
||||||
extraction_folder = os.path.join(os.getcwd(), f"{timestamp}_Recall_Extraction")
|
extraction_folder = os.path.join(os.getcwd(), f"{timestamp}_Recall_Extraction")
|
||||||
|
os.makedirs(extraction_folder, exist_ok=True)
|
||||||
if not os.path.exists(extraction_folder):
|
print(f"📂 Creating extraction folder: {extraction_folder}\n")
|
||||||
os.makedirs(extraction_folder)
|
|
||||||
print(f"📂 Creating extraction folder: {extraction_folder}\n")
|
|
||||||
else:
|
|
||||||
print(f"📂 Using existing extraction folder: {extraction_folder}\n")
|
|
||||||
|
|
||||||
shutil.copy(db_path, extraction_folder)
|
shutil.copy(db_path, extraction_folder)
|
||||||
shutil.copytree(
|
shutil.copytree(image_store_path, os.path.join(extraction_folder, "ImageStore"), dirs_exist_ok=True)
|
||||||
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")):
|
for image_file in os.listdir(os.path.join(extraction_folder, "ImageStore")):
|
||||||
image_path = os.path.join(extraction_folder, "ImageStore", image_file)
|
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)
|
conn = sqlite3.connect(db_extraction_path)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
from_date_timestamp = None
|
from_date_timestamp = int(datetime.strptime(from_date, "%Y-%m-%d").timestamp()) * 1000 if from_date else None
|
||||||
to_date_timestamp = 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:
|
query = "SELECT WindowTitle, TimeStamp, ImageToken FROM WindowCapture WHERE (WindowTitle IS NOT NULL OR ImageToken IS NOT NULL)"
|
||||||
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)
|
|
||||||
"""
|
|
||||||
cursor.execute(query)
|
cursor.execute(query)
|
||||||
rows = cursor.fetchall()
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
output = []
|
|
||||||
captured_windows_count = 0
|
|
||||||
images_taken_count = 0
|
|
||||||
|
|
||||||
captured_windows = []
|
captured_windows = []
|
||||||
images_taken = []
|
images_taken = []
|
||||||
|
for window_title, timestamp, image_token in rows:
|
||||||
for row in rows:
|
if (from_date_timestamp is None or from_date_timestamp <= timestamp) and (to_date_timestamp is None or timestamp < to_date_timestamp):
|
||||||
window_title, timestamp, image_token = row
|
readable_timestamp = datetime.fromtimestamp(timestamp / 1000).strftime("%Y-%m-%d %H:%M:%S")
|
||||||
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:
|
if window_title:
|
||||||
captured_windows.append(f"[{readable_timestamp}] {window_title}")
|
captured_windows.append(f"[{readable_timestamp}] {window_title}")
|
||||||
captured_windows_count += 1
|
|
||||||
if image_token:
|
if image_token:
|
||||||
images_taken.append(f"[{readable_timestamp}] {image_token}")
|
images_taken.append(f"[{readable_timestamp}] {image_token}")
|
||||||
images_taken_count += 1
|
|
||||||
|
|
||||||
output.append(f"🪟 Captured Windows: {captured_windows_count}")
|
captured_windows_count = len(captured_windows)
|
||||||
output.append(f"📸 Images Taken: {images_taken_count}")
|
images_taken_count = len(images_taken)
|
||||||
|
output = [
|
||||||
|
f"🪟 Captured Windows: {captured_windows_count}",
|
||||||
|
f"📸 Images Taken: {images_taken_count}"
|
||||||
|
]
|
||||||
|
|
||||||
if search_term:
|
if search_term:
|
||||||
search_query = f"""
|
search_query = f"SELECT c1, c2 FROM WindowCaptureTextIndex_content WHERE c1 LIKE ? OR c2 LIKE ?"
|
||||||
SELECT c1, c2
|
cursor.execute(search_query, (f"%{search_term}%", f"%{search_term}%"))
|
||||||
FROM WindowCaptureTextIndex_content
|
|
||||||
WHERE c1 LIKE '%{search_term}%' OR c2 LIKE '%{search_term}%'
|
|
||||||
"""
|
|
||||||
cursor.execute(search_query)
|
|
||||||
search_results = cursor.fetchall()
|
search_results = cursor.fetchall()
|
||||||
search_results_count = len(search_results)
|
search_results_count = len(search_results)
|
||||||
output.append(f"🔍 Search results for '{search_term}': {search_results_count}")
|
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 = []
|
search_output = []
|
||||||
for result in search_results:
|
|
||||||
search_output.append(f"c1: {result[0]}, c2: {result[1]}")
|
|
||||||
|
|
||||||
with open(
|
with open(os.path.join(extraction_folder, "TotalRecall.txt"), "w", encoding="utf-8") as file:
|
||||||
os.path.join(extraction_folder, "TotalRecall.txt"), "w", encoding="utf-8"
|
|
||||||
) as file:
|
|
||||||
file.write("Captured Windows:\n")
|
file.write("Captured Windows:\n")
|
||||||
file.write("\n".join(captured_windows))
|
file.write("\n".join(captured_windows))
|
||||||
file.write("\n\nImages Taken:\n")
|
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"\n📂 Full extraction folder path:")
|
||||||
print(f"{YELLOW}{extraction_folder}{ENDC}")
|
print(f"{YELLOW}{extraction_folder}{ENDC}")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(description="Extract and display Windows Recall data.")
|
||||||
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(
|
parser.add_argument("--search", help="Search term for text recognition data.", type=str, default=None)
|
||||||
"--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()
|
args = parser.parse_args()
|
||||||
|
|
||||||
from_date = args.from_date
|
|
||||||
to_date = args.to_date
|
|
||||||
search_term = args.search
|
|
||||||
date_format = "%Y-%m-%d"
|
|
||||||
try:
|
try:
|
||||||
if from_date:
|
if args.from_date:
|
||||||
datetime.strptime(from_date, date_format)
|
datetime.strptime(args.from_date, "%Y-%m-%d")
|
||||||
if to_date:
|
if args.to_date:
|
||||||
datetime.strptime(to_date, date_format)
|
datetime.strptime(args.to_date, "%Y-%m-%d")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
parser.error("Date format must be YYYY-MM-DD.")
|
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)
|
||||||
|
Reference in New Issue
Block a user