#!/usr/bin/env python3
"""LaZagne credential access detection agent.

Detects evidence of LaZagne credential harvesting tool execution on
endpoints by scanning for process artifacts, file system indicators,
and Windows event log entries associated with credential dumping.
Used for defensive detection and incident response, not offensive use.
"""
import argparse
import json
import os
import subprocess
import sys
from datetime import datetime, timezone


LAZAGNE_INDICATORS = {
    "file_paths": [
        "lazagne.exe", "LaZagne.exe", "laZagne.py",
        "lazagne_output.txt", "credentials.txt",
    ],
    "process_names": [
        "lazagne.exe", "python.exe lazagne",
    ],
    "registry_keys": [
        r"SOFTWARE\Microsoft\Windows\CurrentVersion\Run",
    ],
    "event_ids": {
        4688: "Process creation (look for lazagne.exe or suspicious python)",
        4663: "File access audit (credential store access)",
        4656: "Handle request to credential objects",
    },
    "credential_stores": [
        # Windows
        r"%APPDATA%\Mozilla\Firefox\Profiles",
        r"%LOCALAPPDATA%\Google\Chrome\User Data\Default\Login Data",
        r"%APPDATA%\Opera Software\Opera Stable\Login Data",
        # Linux
        os.path.expanduser("~/.mozilla/firefox"),
        os.path.expanduser("~/.config/google-chrome/Default/Login Data"),
        "/etc/shadow",
        os.path.expanduser("~/.ssh"),
    ],
}


def scan_filesystem_indicators(search_paths=None):
    """Scan file system for LaZagne artifacts."""
    findings = []
    if search_paths is None:
        if sys.platform == "win32":
            search_paths = [
                os.environ.get("TEMP", "C:\\Temp"),
                os.environ.get("USERPROFILE", "C:\\Users\\Default"),
                "C:\\Windows\\Temp",
            ]
        else:
            search_paths = ["/tmp", "/var/tmp", os.path.expanduser("~")]

    print("[*] Scanning filesystem for LaZagne indicators...")
    for search_path in search_paths:
        if not os.path.isdir(search_path):
            continue
        for root, dirs, files in os.walk(search_path):
            for fname in files:
                fname_lower = fname.lower()
                for indicator in LAZAGNE_INDICATORS["file_paths"]:
                    if indicator.lower() in fname_lower:
                        full_path = os.path.join(root, fname)
                        stat = os.stat(full_path)
                        findings.append({
                            "type": "file_indicator",
                            "path": full_path,
                            "indicator": indicator,
                            "size": stat.st_size,
                            "modified": datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc).isoformat(),
                            "severity": "CRITICAL",
                            "description": f"LaZagne artifact found: {fname}",
                        })
            # Limit directory depth to avoid slow scans
            if root.count(os.sep) - search_path.count(os.sep) > 3:
                dirs.clear()

    return findings


def check_windows_event_logs():
    """Check Windows Security event logs for LaZagne-related activity."""
    findings = []
    if sys.platform != "win32":
        return findings

    print("[*] Checking Windows Security event logs...")

    # Check for process creation events matching LaZagne
    ps_script = """
    Get-WinEvent -FilterHashtable @{
        LogName='Security'; Id=4688; StartTime=(Get-Date).AddDays(-7)
    } -MaxEvents 1000 -ErrorAction SilentlyContinue |
    Where-Object {
        $_.Properties[5].Value -match 'lazagne|LaZagne' -or
        $_.Properties[8].Value -match 'lazagne|LaZagne'
    } |
    Select-Object TimeCreated, @{N='ProcessName';E={$_.Properties[5].Value}},
        @{N='CommandLine';E={$_.Properties[8].Value}},
        @{N='User';E={$_.Properties[1].Value}} |
    ConvertTo-Json
    """
    result = subprocess.run(
        ["powershell", "-Command", ps_script],
        capture_output=True, text=True, timeout=60,
    )
    if result.returncode == 0 and result.stdout.strip():
        try:
            events = json.loads(result.stdout)
            if isinstance(events, dict):
                events = [events]
            for evt in events:
                findings.append({
                    "type": "event_log",
                    "event_id": 4688,
                    "time": str(evt.get("TimeCreated", "")),
                    "process": evt.get("ProcessName", ""),
                    "command_line": evt.get("CommandLine", "")[:200],
                    "user": evt.get("User", ""),
                    "severity": "CRITICAL",
                    "description": "LaZagne process execution detected in event logs",
                })
        except json.JSONDecodeError:
            pass

    # Check for credential file access (Event ID 4663)
    ps_script2 = """
    Get-WinEvent -FilterHashtable @{
        LogName='Security'; Id=4663; StartTime=(Get-Date).AddDays(-7)
    } -MaxEvents 500 -ErrorAction SilentlyContinue |
    Where-Object {
        $_.Properties[6].Value -match 'Login Data|logins.json|Credentials|vault'
    } |
    Select-Object TimeCreated, @{N='ObjectName';E={$_.Properties[6].Value}},
        @{N='ProcessName';E={$_.Properties[11].Value}} |
    ConvertTo-Json
    """
    result = subprocess.run(
        ["powershell", "-Command", ps_script2],
        capture_output=True, text=True, timeout=60,
    )
    if result.returncode == 0 and result.stdout.strip():
        try:
            events = json.loads(result.stdout)
            if isinstance(events, dict):
                events = [events]
            for evt in events:
                findings.append({
                    "type": "credential_access",
                    "event_id": 4663,
                    "time": str(evt.get("TimeCreated", "")),
                    "object": evt.get("ObjectName", ""),
                    "process": evt.get("ProcessName", ""),
                    "severity": "HIGH",
                    "description": "Credential store accessed by process",
                })
        except json.JSONDecodeError:
            pass

    return findings


def check_credential_store_integrity():
    """Check if credential stores have been recently accessed unusually."""
    findings = []
    print("[*] Checking credential store integrity...")

    stores_to_check = []
    if sys.platform == "win32":
        appdata = os.environ.get("APPDATA", "")
        localappdata = os.environ.get("LOCALAPPDATA", "")
        stores_to_check = [
            (os.path.join(localappdata, "Google", "Chrome", "User Data", "Default", "Login Data"), "Chrome"),
            (os.path.join(appdata, "Mozilla", "Firefox"), "Firefox"),
        ]
    else:
        stores_to_check = [
            (os.path.expanduser("~/.config/google-chrome/Default/Login Data"), "Chrome"),
            (os.path.expanduser("~/.mozilla/firefox"), "Firefox"),
            (os.path.expanduser("~/.ssh"), "SSH Keys"),
        ]

    for path, store_name in stores_to_check:
        if os.path.exists(path):
            if os.path.isfile(path):
                stat = os.stat(path)
                access_time = datetime.fromtimestamp(stat.st_atime, tz=timezone.utc)
                mod_time = datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc)
                findings.append({
                    "type": "credential_store",
                    "store": store_name,
                    "path": path,
                    "last_accessed": access_time.isoformat(),
                    "last_modified": mod_time.isoformat(),
                    "severity": "INFO",
                })

    return findings


def format_summary(all_findings):
    """Print detection summary."""
    print(f"\n{'='*60}")
    print(f"  LaZagne Credential Access Detection Report")
    print(f"{'='*60}")

    file_findings = [f for f in all_findings if f["type"] == "file_indicator"]
    event_findings = [f for f in all_findings if f["type"] in ("event_log", "credential_access")]
    store_findings = [f for f in all_findings if f["type"] == "credential_store"]

    print(f"  File Indicators  : {len(file_findings)}")
    print(f"  Event Log Hits   : {len(event_findings)}")
    print(f"  Credential Stores: {len(store_findings)}")

    critical = [f for f in all_findings if f.get("severity") == "CRITICAL"]
    if critical:
        print(f"\n  CRITICAL FINDINGS ({len(critical)}):")
        for f in critical:
            print(f"    [{f['type']}] {f.get('description', f.get('path', ''))}")

    severity_counts = {}
    for f in all_findings:
        sev = f.get("severity", "INFO")
        severity_counts[sev] = severity_counts.get(sev, 0) + 1
    return severity_counts


def main():
    parser = argparse.ArgumentParser(
        description="LaZagne credential access detection agent (defensive use)"
    )
    parser.add_argument("--search-paths", nargs="+", help="Paths to scan for artifacts")
    parser.add_argument("--skip-events", action="store_true", help="Skip Windows event log check")
    parser.add_argument("--skip-stores", action="store_true", help="Skip credential store check")
    parser.add_argument("--output", "-o", help="Output JSON report")
    parser.add_argument("--verbose", "-v", action="store_true")
    args = parser.parse_args()

    all_findings = []
    all_findings.extend(scan_filesystem_indicators(args.search_paths))
    if not args.skip_events:
        all_findings.extend(check_windows_event_logs())
    if not args.skip_stores:
        all_findings.extend(check_credential_store_integrity())

    severity_counts = format_summary(all_findings)

    report = {
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "tool": "LaZagne Detection",
        "findings": all_findings,
        "severity_counts": severity_counts,
        "risk_level": (
            "CRITICAL" if severity_counts.get("CRITICAL", 0) > 0
            else "HIGH" if severity_counts.get("HIGH", 0) > 0
            else "LOW"
        ),
    }

    if args.output:
        with open(args.output, "w") as f:
            json.dump(report, f, indent=2)
        print(f"\n[+] Report saved to {args.output}")
    elif args.verbose:
        print(json.dumps(report, indent=2))


if __name__ == "__main__":
    main()
