#!/usr/bin/env python3
"""Agent for detecting Stuxnet-style ICS/SCADA attacks targeting PLCs."""

import argparse
import json
import os
import subprocess
import sys
from datetime import datetime, timezone


STUXNET_IOCS = {
    "file_hashes": [
        "b4b290906e3a8f1eabbb2b2864e6e7f7",
        "e757c7e29297b7b7a090695e2de82b4b",
    ],
    "registry_keys": [
        r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\MS-DOS Emulation",
        r"HKLM\SYSTEM\CurrentControlSet\Services\MRxCls",
        r"HKLM\SYSTEM\CurrentControlSet\Services\MRxNet",
    ],
    "mutex_names": [
        "Global\\{A3BD0EA3-CD10-4258-8784-2F53E56E2010}",
    ],
    "driver_files": [
        "mrxcls.sys", "mrxnet.sys",
    ],
}

MODBUS_FUNCTION_CODES = {
    5: "Write Single Coil",
    6: "Write Single Register",
    15: "Write Multiple Coils",
    16: "Write Multiple Registers",
    22: "Mask Write Register",
    23: "Read/Write Multiple Registers",
}


def check_plc_communication(pcap_path):
    """Analyze PCAP for suspicious Modbus/S7 PLC communication."""
    findings = []
    try:
        result = subprocess.check_output(
            ["tshark", "-r", pcap_path, "-Y", "modbus || s7comm",
             "-T", "fields", "-e", "ip.src", "-e", "ip.dst",
             "-e", "modbus.func_code", "-e", "s7comm.param.func",
             "-e", "frame.time"],
            text=True, errors="replace", timeout=60
        )
        write_count = 0
        for line in result.strip().splitlines():
            fields = line.split("\t")
            if len(fields) >= 3:
                func_code = fields[2] if fields[2] else None
                if func_code:
                    try:
                        fc = int(func_code)
                        if fc in MODBUS_FUNCTION_CODES:
                            write_count += 1
                            if write_count <= 20:
                                findings.append({
                                    "src": fields[0], "dst": fields[1],
                                    "function": MODBUS_FUNCTION_CODES[fc],
                                    "func_code": fc,
                                })
                    except ValueError:
                        pass
        if write_count > 100:
            findings.append({
                "alert": f"Excessive PLC write operations: {write_count}",
                "severity": "CRITICAL",
            })
    except (subprocess.SubprocessError, FileNotFoundError):
        findings.append({"error": "tshark not available or PCAP parse failed"})
    return findings


def scan_for_rootkit_drivers():
    """Check for Stuxnet-style rootkit driver files."""
    findings = []
    search_dirs = [r"C:\Windows\System32\drivers", r"C:\Windows\inf"]
    for d in search_dirs:
        if not os.path.isdir(d):
            continue
        for fname in os.listdir(d):
            if fname.lower() in [df.lower() for df in STUXNET_IOCS["driver_files"]]:
                findings.append({
                    "type": "rootkit_driver",
                    "path": os.path.join(d, fname),
                    "severity": "CRITICAL",
                })
    return findings


def check_registry_indicators():
    """Check Windows registry for Stuxnet IOCs."""
    findings = []
    if sys.platform != "win32":
        return findings
    for key in STUXNET_IOCS["registry_keys"]:
        try:
            result = subprocess.check_output(
                ["reg", "query", key], text=True, errors="replace", timeout=5
            )
            if result.strip():
                findings.append({
                    "type": "registry_ioc",
                    "key": key,
                    "severity": "CRITICAL",
                })
        except subprocess.SubprocessError:
            pass
    return findings


def analyze_step7_project(project_dir):
    """Check Step 7 project files for unauthorized OB modifications."""
    findings = []
    if not os.path.isdir(project_dir):
        return findings
    for root, _, files in os.walk(project_dir):
        for f in files:
            fpath = os.path.join(root, f)
            if f.lower().startswith("ob") and f.lower().endswith((".awl", ".mc7")):
                stat = os.stat(fpath)
                findings.append({
                    "file": fpath,
                    "size": stat.st_size,
                    "modified": datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc).isoformat(),
                    "note": "Organization Block file — verify integrity",
                })
    return findings


def main():
    parser = argparse.ArgumentParser(
        description="Detect Stuxnet-style ICS/SCADA attacks"
    )
    parser.add_argument("--pcap", help="PCAP file with ICS network traffic")
    parser.add_argument("--step7-project", help="Siemens Step 7 project directory")
    parser.add_argument("--check-host", action="store_true", help="Check host for IOCs")
    parser.add_argument("--output", "-o", help="Output JSON report")
    args = parser.parse_args()

    print("[*] Stuxnet-Style ICS Attack Detection Agent")
    report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": {}}

    if args.pcap:
        plc = check_plc_communication(args.pcap)
        report["findings"]["plc_traffic"] = plc
        print(f"[*] PLC traffic findings: {len(plc)}")

    if args.check_host:
        drivers = scan_for_rootkit_drivers()
        registry = check_registry_indicators()
        report["findings"]["rootkit_drivers"] = drivers
        report["findings"]["registry_iocs"] = registry
        print(f"[*] Driver IOCs: {len(drivers)}, Registry IOCs: {len(registry)}")

    if args.step7_project:
        s7 = analyze_step7_project(args.step7_project)
        report["findings"]["step7_files"] = s7
        print(f"[*] Step 7 files to verify: {len(s7)}")

    total = sum(len(v) if isinstance(v, list) else 0 for v in report["findings"].values())
    report["risk_level"] = "CRITICAL" if total >= 3 else "HIGH" if total >= 1 else "LOW"

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


if __name__ == "__main__":
    main()
