Compare commits

...

12 Commits

Author SHA1 Message Date
Alex
e686d03bf6
Update README.md 2024-06-07 01:03:06 +02:00
Alex
4e8980b486
Merge pull request #6 from mbomb007/mbomb007-patch-4 2024-06-07 00:19:53 +02:00
Alex
de9830b42d
Merge pull request #7 from ansi0/issue3 2024-06-07 00:19:24 +02:00
ansi0
592c0f566d fixed extension check for extracted image files 2024-06-07 01:06:02 +03:00
mbomb007
ce7c2f6376
Fix display banner 'a'. 2024-06-06 17:01:13 -05:00
xaitax
9c9a660f94 v0.3 - Added permission fix 2024-06-06 23:14:04 +02:00
xaitax
f33b5c5a8b Merge branch 'main' of https://github.com/xaitax/TotalRecall 2024-06-06 22:20:44 +02:00
xaitax
6756cd0671 Added permission change 2024-06-06 22:20:42 +02:00
Alex
f8f29e5563
Merge pull request #1 from Thynix/patch-1
Copy edit README.md
2024-06-06 21:29:44 +02:00
xaitax
2825c29d5d Update totalrecall.py 2024-06-06 21:25:52 +02:00
Steve Dougherty
f932630e9d
Use consistent date formatting 2024-06-05 13:17:19 -04:00
Steve Dougherty
5df54e655d
Copy edit README.md 2024-06-05 13:14:25 -04:00
2 changed files with 85 additions and 82 deletions

View File

@ -5,9 +5,9 @@ This very simple tool extracts and displays data from the Recall feature in Wind
## What is Windows Recall? ## What is Windows Recall?
On May, 20th [2024 Microsoft announced it's new Copilot+ PCs](https://blogs.microsoft.com/blog/2024/05/20/introducing-copilot-pcs/) running on ARM architecture. On May 20th 2024 [Microsoft announced its new Copilot+ PCs](https://blogs.microsoft.com/blog/2024/05/20/introducing-copilot-pcs/) running on ARM architecture.
With this, they also announced Windows Copilot+ Recall which will be released on 18. June 2024. With this, they also announced Windows Copilot+ Recall which will be released on June 18th 2024.
![image](https://github.com/xaitax/TotalRecall/assets/5014849/eff4619c-700c-47d7-bb89-a05054309517) ![image](https://github.com/xaitax/TotalRecall/assets/5014849/eff4619c-700c-47d7-bb89-a05054309517)
@ -29,7 +29,7 @@ With this, they also announced Windows Copilot+ Recall which will be released on
## Requirements ## Requirements
To run or use this feature, you need to have one of the new CoPilot+ PCs running on ARM. Some of them can be found [here](https://www.microsoft.com/en-us/windows/copilot-plus-pcs?r=1#shop) To run or use this feature, you need to have one of the new Copilot+ PCs running on ARM. Some of them can be found [here](https://www.microsoft.com/en-us/windows/copilot-plus-pcs?r=1#shop)
![image](https://github.com/xaitax/TotalRecall/assets/5014849/972aedc8-6e3c-4c5a-8d40-55b534d248b4) ![image](https://github.com/xaitax/TotalRecall/assets/5014849/972aedc8-6e3c-4c5a-8d40-55b534d248b4)
@ -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
### [06. June 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.
@ -221,7 +233,7 @@ A. Yes. I have automated exfiltration, and made a website where you can upload a
I am deliberately holding back technical details until Microsoft ship the feature as I want to give them time to do something. I am deliberately holding back technical details until Microsoft ship the feature as I want to give them time to do something.
I actually have a whole bunch of things to show and think the wider cyber community will have so much fun with this when generally available.. but I also think thats really sad, as real world harm will ensue. I actually have a whole bunch of things to show and think the wider cyber community will have so much fun with this when generally available... but I also think thats really sad, as real world harm will ensue.
**Q. What kind of things are in the database?** **Q. What kind of things are in the database?**
@ -247,7 +259,7 @@ Recall enables threat actors to automate scraping everything youve ever looke
During testing this with an off the shelf infostealer, I used Microsoft Defender for Endpoint — which detected the off the shelve infostealer — but by the time the automated remediation kicked in (which took over ten minutes) my Recall data was already long gone. During testing this with an off the shelf infostealer, I used Microsoft Defender for Endpoint — which detected the off the shelve infostealer — but by the time the automated remediation kicked in (which took over ten minutes) my Recall data was already long gone.
**Q. Does this enable mass data breaches of website?** **Q. Does this enable mass data breaches of websites?**
A. Yes. The next time you see a major data breach where customer data is clearly visible in the breach, youre going to presume company who processes the data are at fault, right? A. Yes. The next time you see a major data breach where customer data is clearly visible in the breach, youre going to presume company who processes the data are at fault, right?

View File

@ -4,38 +4,55 @@ import sqlite3
from datetime import datetime, timedelta from datetime import datetime, timedelta
import getpass import getpass
import argparse import argparse
import subprocess
VERSION = "0.2" VERSION = "0.3"
BLUE = "\033[94m" BLUE = "\033[94m"
GREEN = "\033[92m" GREEN = "\033[92m"
YELLOW = "\033[93m" YELLOW = "\033[93m"
RED = "\033[91m"
ENDC = "\033[0m" ENDC = "\033[0m"
def display_banner(): def display_banner():
banner = r""" banner = (
r"""
___________ __ .__ __________ .__ .__ ___________ __ .__ __________ .__ .__
\__ ___/____/ |______ | |\______ \ ____ ____ _____ | | | | \__ ___/____/ |______ | |\______ \ ____ ____ _____ | | | |
| | / _ \ __\__ \ | | | _// __ \_/ ___\\__ \ | | | | | | / _ \ __\__ \ | | | _// __ \_/ ___\\__ \ | | | |
| |( <_> ) | / __ \| |_| | \ ___/\ \___ / __ \| |_| |__ | |( <_> ) | / __ \| |_| | \ ___/\ \___ / __ \| |_| |__
|____| \____/|__| (____ /____/____|_ /\___ >\___ >____ /____/____/ |____| \____/|__| (____ /____/____|_ /\___ >\___ >____ /____/____/
\/ \/ \/ \/ \/ \/ \/ \/ \/ \/
v""" + VERSION + """ / Alexander Hagenah / @xaitax / ah@primepage.de v"""
+ VERSION
+ """ / Alexander Hagenah / @xaitax / ah@primepage.de
""" """
)
print(BLUE + banner + ENDC) print(BLUE + banner + ENDC)
def modify_permissions(path):
try:
subprocess.run(
["icacls", path, "/grant", f"{getpass.getuser()}:(OI)(CI)F", "/T", "/C", "/Q"],
check=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
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): 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):
for folder_name in os.listdir(base_path): print("🚫 Base path does not exist.")
folder_path = os.path.join(base_path, folder_name) return
if os.path.isdir(folder_path):
guid_folder = folder_path modify_permissions(base_path)
break 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.")
@ -46,23 +63,19 @@ 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("🟢 Windows Recall feature found. Do you want to proceed with the extraction? (yes/no): ") proceed = input("🟢 Windows Recall feature found. Do you want to proceed with the extraction? (yes/no): ").strip().lower()
if proceed.lower() != 'yes': if proceed != "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):
os.makedirs(extraction_folder)
print(f"📂 Creating extraction folder: {extraction_folder}\n") 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(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)
@ -70,65 +83,47 @@ def main(from_date=None, to_date=None, search_term=None):
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)
new_image_path = f"{image_path}.jpg" new_image_path = f"{image_path}.jpg"
if not new_image_path.endswith('.jpg'): if not image_path.endswith(".jpg"):
os.rename(image_path, new_image_path) os.rename(image_path, new_image_path)
db_extraction_path = os.path.join(extraction_folder, "ukg.db") db_extraction_path = os.path.join(extraction_folder, "ukg.db")
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(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("Captured Windows:\n")
@ -156,16 +151,12 @@ if __name__ == "__main__":
parser.add_argument("--search", help="Search term for text recognition data.", 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)