#!/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 import hashlib from collections import defaultdict 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', ''))}")