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?
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)
@ -29,7 +29,7 @@ With this, they also announced Windows Copilot+ Recall which will be released on
## 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)
@ -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
### [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
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 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?**
@ -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.
**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?

View File

@ -4,38 +4,55 @@ import sqlite3
from datetime import datetime, timedelta
import getpass
import argparse
import subprocess
VERSION = "0.2"
VERSION = "0.3"
BLUE = "\033[94m"
GREEN = "\033[92m"
YELLOW = "\033[93m"
RED = "\033[91m"
ENDC = "\033[0m"
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)
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):
display_banner()
username = getpass.getuser()
base_path = f"C:\\Users\\{username}\\AppData\\Local\\CoreAIPlatform.00\\UKP"
guid_folder = None
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
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.")
@ -46,23 +63,19 @@ 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)
os.makedirs(extraction_folder, exist_ok=True)
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.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")):
image_path = os.path.join(extraction_folder, "ImageStore", image_file)
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)
db_extraction_path = os.path.join(extraction_folder, "ukg.db")
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:
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)
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)