#!/usr/bin/env python3 """Browser forensics analysis agent using Hindsight concepts. Parses Chromium-based browser artifacts (Chrome, Edge, Brave) including history, downloads, cookies, autofill, and extensions from SQLite databases. """ import os import sys import json import sqlite3 import datetime def chrome_time_to_datetime(chrome_time): """Convert Chrome timestamp (microseconds since 1601-01-01) to datetime.""" if not chrome_time or chrome_time == 0: return None try: epoch = datetime.datetime(1601, 1, 1) delta = datetime.timedelta(microseconds=chrome_time) return (epoch + delta).isoformat() + "Z" except (OverflowError, OSError): return None def find_browser_profiles(base_path=None): """Locate Chromium-based browser profile directories.""" if base_path and os.path.isdir(base_path): return [base_path] profiles = [] home = os.path.expanduser("~") candidates = [ os.path.join(home, "AppData", "Local", "Google", "Chrome", "User Data", "Default"), os.path.join(home, "AppData", "Local", "Microsoft", "Edge", "User Data", "Default"), os.path.join(home, "AppData", "Local", "BraveSoftware", "Brave-Browser", "User Data", "Default"), os.path.join(home, ".config", "google-chrome", "Default"), os.path.join(home, ".config", "chromium", "Default"), os.path.join(home, ".config", "microsoft-edge", "Default"), ] for path in candidates: if os.path.isdir(path): profiles.append(path) return profiles def parse_history(profile_path): """Parse browsing history from History SQLite database.""" db_path = os.path.join(profile_path, "History") if not os.path.exists(db_path): return [] entries = [] try: conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True) cursor = conn.cursor() cursor.execute(""" SELECT u.url, u.title, v.visit_time, v.transition, u.visit_count FROM visits v JOIN urls u ON v.url = u.id ORDER BY v.visit_time DESC LIMIT 5000 """) for url, title, visit_time, transition, count in cursor.fetchall(): entries.append({ "url": url, "title": title or "", "visit_time": chrome_time_to_datetime(visit_time), "transition": transition & 0xFF, "visit_count": count, }) conn.close() except sqlite3.Error as e: entries.append({"error": str(e)}) return entries def parse_downloads(profile_path): """Parse download history from History database.""" db_path = os.path.join(profile_path, "History") if not os.path.exists(db_path): return [] downloads = [] try: conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True) cursor = conn.cursor() cursor.execute(""" SELECT target_path, tab_url, total_bytes, start_time, end_time, danger_type, interrupt_reason, mime_type FROM downloads ORDER BY start_time DESC LIMIT 1000 """) for row in cursor.fetchall(): downloads.append({ "target_path": row[0], "source_url": row[1], "total_bytes": row[2], "start_time": chrome_time_to_datetime(row[3]), "end_time": chrome_time_to_datetime(row[4]), "danger_type": row[5], "interrupt_reason": row[6], "mime_type": row[7], }) conn.close() except sqlite3.Error as e: downloads.append({"error": str(e)}) return downloads def parse_cookies(profile_path): """Parse cookies from Cookies database.""" db_path = os.path.join(profile_path, "Cookies") if not os.path.exists(db_path): db_path = os.path.join(profile_path, "Network", "Cookies") if not os.path.exists(db_path): return [] cookies = [] try: conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True) cursor = conn.cursor() cursor.execute(""" SELECT host_key, name, path, creation_utc, expires_utc, is_secure, is_httponly, samesite FROM cookies ORDER BY creation_utc DESC LIMIT 2000 """) for row in cursor.fetchall(): cookies.append({ "host": row[0], "name": row[1], "path": row[2], "created": chrome_time_to_datetime(row[3]), "expires": chrome_time_to_datetime(row[4]), "secure": bool(row[5]), "httponly": bool(row[6]), "samesite": row[7], }) conn.close() except sqlite3.Error as e: cookies.append({"error": str(e)}) return cookies def parse_autofill(profile_path): """Parse autofill data from Web Data database.""" db_path = os.path.join(profile_path, "Web Data") if not os.path.exists(db_path): return [] entries = [] try: conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True) cursor = conn.cursor() cursor.execute(""" SELECT name, value, count, date_created, date_last_used FROM autofill ORDER BY date_last_used DESC LIMIT 500 """) for row in cursor.fetchall(): entries.append({ "field_name": row[0], "value": row[1][:50] + "..." if len(row[1]) > 50 else row[1], "usage_count": row[2], "created": chrome_time_to_datetime(row[3] * 1000000 if row[3] else 0), "last_used": chrome_time_to_datetime(row[4] * 1000000 if row[4] else 0), }) conn.close() except sqlite3.Error as e: entries.append({"error": str(e)}) return entries def parse_extensions(profile_path): """Parse installed browser extensions.""" ext_dir = os.path.join(profile_path, "Extensions") extensions = [] if not os.path.isdir(ext_dir): return extensions for ext_id in os.listdir(ext_dir): ext_path = os.path.join(ext_dir, ext_id) if os.path.isdir(ext_path): versions = sorted(os.listdir(ext_path)) manifest_path = os.path.join(ext_path, versions[-1], "manifest.json") if versions else None name = ext_id if manifest_path and os.path.exists(manifest_path): try: with open(manifest_path, "r", encoding="utf-8") as f: manifest = json.load(f) name = manifest.get("name", ext_id) extensions.append({ "id": ext_id, "name": name, "version": manifest.get("version", "?"), "permissions": manifest.get("permissions", [])[:10], }) except (json.JSONDecodeError, IOError): extensions.append({"id": ext_id, "name": name, "version": "unknown"}) return extensions def detect_suspicious_activity(history, downloads): """Flag suspicious browsing and download patterns.""" findings = [] suspicious_domains = ["pastebin.com", "ngrok.io", "raw.githubusercontent.com", "transfer.sh", "file.io", "temp.sh", "anonfiles.com"] for entry in history: url = entry.get("url", "").lower() for domain in suspicious_domains: if domain in url: findings.append({ "type": "suspicious_url", "url": entry["url"], "domain": domain, "time": entry.get("visit_time"), }) dangerous_mimes = ["application/x-msdownload", "application/x-msdos-program", "application/x-executable", "application/vnd.ms-excel.sheet.macroEnabled"] for dl in downloads: if dl.get("danger_type", 0) > 0: findings.append({ "type": "dangerous_download", "path": dl.get("target_path"), "source": dl.get("source_url"), "danger_type": dl.get("danger_type"), }) if dl.get("mime_type", "") in dangerous_mimes: findings.append({ "type": "suspicious_mime", "mime": dl.get("mime_type"), "path": dl.get("target_path"), }) return findings if __name__ == "__main__": print("=" * 60) print("Browser Forensics Analysis Agent") print("Chromium history, downloads, cookies, extensions") print("=" * 60) target = sys.argv[1] if len(sys.argv) > 1 else None profiles = find_browser_profiles(target) if not profiles: print("\n[!] No browser profiles found.") print("[DEMO] Usage: python agent.py ") print(" e.g. python agent.py ~/AppData/Local/Google/Chrome/User\\ Data/Default") sys.exit(0) for profile in profiles: print(f"\n[*] Profile: {profile}") history = parse_history(profile) print(f" History entries: {len(history)}") for h in history[:5]: print(f" {h.get('visit_time', '?')} | {h.get('title', '')[:50]} | {h.get('url', '')[:60]}") downloads = parse_downloads(profile) print(f" Downloads: {len(downloads)}") for d in downloads[:5]: print(f" {d.get('start_time', '?')} | {d.get('mime_type', '?')} | {os.path.basename(d.get('target_path', ''))}") cookies = parse_cookies(profile) print(f" Cookies: {len(cookies)}") extensions = parse_extensions(profile) print(f" Extensions: {len(extensions)}") for ext in extensions[:5]: print(f" {ext.get('name', '?')} v{ext.get('version', '?')} [{ext.get('id', '')[:20]}]") findings = detect_suspicious_activity(history, downloads) print(f"\n --- Suspicious Activity: {len(findings)} findings ---") for f in findings[:10]: print(f" [{f['type']}] {f.get('url', f.get('path', ''))}")