[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6AEF7VZ87P3PHBCYJS7J7K9":3,"skill-same:01M6AEF7VZ87P3PHBCYJS7J7K9":113},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":44,"linkOnly":45,"markdownContent":46,"markdownBody":47,"frontMatter":48,"refName":72,"commitSha":73,"skillDirectory":74,"forksCount":75,"repositoryHtmlUrl":76,"repositoryHomepageUrl":77,"ownerHtmlUrl":78,"ownerBlogUrl":79,"ownerDescription":80,"updatedAt":81,"install":82,"resources":95},"01M6AEF7VZ87P3PHBCYJS7J7K9","detecting-modbus-protocol-anomalies",null,"Detect anomalies in Modbus\u002FTCP and Modbus RTU industrial traffic via function code monitoring, register range validation, timing analysis, and deep packet inspection, using Zeek's Modbus analyzer, Suricata IDS with OT rules, and Python Markov chain models of normal transaction sequences. Use for deploying Modbus IDS in OT environments, baselining polling patterns, investigating suspicious Modbus traffic, or building function code allowlists.","mukul975\u002FAnthropic-Cybersecurity-Skills","mukul975","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F42860185?v=4","01M6ADEQR8T5M4EEG6C67BQCWF","Mahipal","01M6ADJPMYWG1E5GM1FWBE3128",35925,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fmukul975\u002FAnthropic-Cybersecurity-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fdetecting-modbus-protocol-anomalies\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"detecting modbus protocol anomalies","modbus","protocol","ids","protocol-anomaly","iec62443","industrial-control","scada","ics","ot-security","suricata","suspicious","Zeek","monitoring","tcp","validation","code","pr","cybersecurity","python","2026-11-10 10:12:30",false,"---\nname: detecting-modbus-protocol-anomalies\ndescription: Detect anomalies in Modbus\u002FTCP and Modbus RTU industrial traffic via function code monitoring, register range validation, timing analysis, and deep packet inspection, using Zeek's Modbus analyzer, Suricata IDS with OT rules, and Python Markov chain models of normal transaction sequences. Use for deploying Modbus IDS in OT environments, baselining polling patterns, investigating suspicious Modbus traffic, or building function code allowlists.\ndomain: cybersecurity\nsubdomain: ot-ics-security\ntags:\n- ot-security\n- ics\n- scada\n- industrial-control\n- iec62443\n- modbus\n- protocol-anomaly\nversion: 1.0.0\nauthor: mahipal\nlicense: Apache-2.0\nnist_ai_rmf:\n- MEASURE-2.7\n- MAP-5.1\n- MANAGE-2.4\natlas_techniques:\n- AML.T0070\n- AML.T0066\n- AML.T0082\nnist_csf:\n- PR.IR-01\n- DE.CM-01\n- ID.AM-05\n- GV.OC-02\nmitre_attack:\n- T1078\n- T1190\n- T1059\n- T0816\n- T0836\n---\n\n# Detecting Modbus Protocol Anomalies\n\n## When to Use\n\n- When deploying Modbus-specific intrusion detection in an OT environment\n- When building baseline models for deterministic Modbus polling patterns\n- When investigating suspicious Modbus traffic flagged by OT monitoring tools\n- When implementing function code allowlisting on industrial firewalls\n- When detecting unauthorized Modbus write commands that could manipulate process setpoints\n\n**Do not use** for securing Modbus communications end-to-end (Modbus has no native security; see implementing-network-segmentation-for-ot for firewall-based controls), for non-Modbus protocol monitoring (see detecting-anomalies-in-industrial-control-systems for multi-protocol), or for active fuzzing of Modbus implementations (see performing-plc-firmware-security-analysis).\n\n## Prerequisites\n\n- Network SPAN\u002FTAP access to monitor Modbus\u002FTCP traffic (port 502)\n- Zeek (formerly Bro) with Modbus protocol analyzer or Suricata with OT rulesets\n- Python 3.9+ with scapy and pymodbus for custom analysis\n- Baseline capture of normal Modbus traffic (minimum 1-2 weeks)\n- Documentation of authorized Modbus clients, function codes, and register maps\n\n## Workflow\n\n### Step 1: Capture and Parse Modbus Traffic\n\nDeploy passive monitoring to capture all Modbus\u002FTCP traffic and parse it into structured records for analysis.\n\n```python\n#!\u002Fusr\u002Fbin\u002Fenv python3\n\"\"\"Modbus Protocol Anomaly Detection System.\n\nMonitors Modbus\u002FTCP traffic for anomalies including unauthorized\nfunction codes, unusual register access, timing deviations,\nand rogue client devices.\n\"\"\"\n\nimport json\nimport struct\nimport sys\nimport time\nfrom collections import defaultdict, deque\nfrom dataclasses import dataclass, field\nfrom datetime import datetime\nfrom statistics import mean, stdev\n\ntry:\n    from scapy.all import sniff, rdpcap, IP, TCP\nexcept ImportError:\n    print(\"Install scapy: pip install scapy\")\n    sys.exit(1)\n\n\nMODBUS_FUNCTION_CODES = {\n    1: (\"Read Coils\", \"read\"),\n    2: (\"Read Discrete Inputs\", \"read\"),\n    3: (\"Read Holding Registers\", \"read\"),\n    4: (\"Read Input Registers\", \"read\"),\n    5: (\"Write Single Coil\", \"write\"),\n    6: (\"Write Single Register\", \"write\"),\n    7: (\"Read Exception Status\", \"diagnostic\"),\n    8: (\"Diagnostics\", \"diagnostic\"),\n    11: (\"Get Comm Event Counter\", \"diagnostic\"),\n    12: (\"Get Comm Event Log\", \"diagnostic\"),\n    15: (\"Write Multiple Coils\", \"write\"),\n    16: (\"Write Multiple Registers\", \"write\"),\n    17: (\"Report Slave ID\", \"diagnostic\"),\n    22: (\"Mask Write Register\", \"write\"),\n    23: (\"Read\u002FWrite Multiple Registers\", \"read_write\"),\n    43: (\"Encapsulated Interface Transport\", \"diagnostic\"),\n}\n\n\n@dataclass\nclass ModbusAnomaly:\n    timestamp: str\n    anomaly_type: str\n    severity: str\n    src_ip: str\n    dst_ip: str\n    unit_id: int\n    func_code: int\n    detail: str\n    mitre_technique: str = \"\"\n\n\n@dataclass\nclass ModbusSession:\n    \"\"\"Tracks state for a Modbus master-slave session.\"\"\"\n    src_ip: str\n    dst_ip: str\n    func_codes_seen: dict = field(default_factory=lambda: defaultdict(int))\n    register_ranges: set = field(default_factory=set)\n    intervals: list = field(default_factory=lambda: deque(maxlen=500))\n    last_timestamp: float = 0\n    request_count: int = 0\n    write_count: int = 0\n\n\nclass ModbusAnomalyDetector:\n    \"\"\"Detects anomalies in Modbus\u002FTCP traffic.\"\"\"\n\n    def __init__(self):\n        self.sessions = {}\n        self.baseline_sessions = {}\n        self.anomalies = []\n        self.authorized_clients = set()\n        self.authorized_func_codes = {}  # per-session allowed FCs\n        self.packet_count = 0\n\n    def set_authorized_clients(self, clients):\n        \"\"\"Set list of authorized Modbus client IPs.\"\"\"\n        self.authorized_clients = set(clients)\n\n    def set_authorized_func_codes(self, session_key, func_codes):\n        \"\"\"Set allowed function codes for a specific session.\"\"\"\n        self.authorized_func_codes[session_key] = set(func_codes)\n\n    def load_baseline(self, baseline_file):\n        \"\"\"Load baseline profiles from previous capture analysis.\"\"\"\n        with open(baseline_file) as f:\n            baseline = json.load(f)\n        for key, data in baseline.get(\"modbus_baselines\", {}).items():\n            self.baseline_sessions[key] = data\n            self.authorized_func_codes[key] = set(data.get(\"allowed_function_codes\", []))\n        print(f\"[*] Loaded {len(self.baseline_sessions)} Modbus baselines\")\n\n    def process_packet(self, pkt):\n        \"\"\"Process a single packet for Modbus anomaly detection.\"\"\"\n        if not pkt.haslayer(TCP) or not pkt.haslayer(IP):\n            return\n\n        # Check for Modbus\u002FTCP (port 502)\n        if pkt[TCP].dport != 502 and pkt[TCP].sport != 502:\n            return\n\n        payload = bytes(pkt[TCP].payload)\n        if len(payload) \u003C 8:\n            return\n\n        self.packet_count += 1\n        timestamp = float(pkt.time)\n        ts_str = datetime.fromtimestamp(timestamp).isoformat()\n\n        # Parse MBAP header\n        try:\n            trans_id = struct.unpack(\">H\", payload[0:2])[0]\n            proto_id = struct.unpack(\">H\", payload[2:4])[0]\n            length = struct.unpack(\">H\", payload[4:6])[0]\n            unit_id = payload[6]\n            func_code = payload[7]\n        except (IndexError, struct.error):\n            return\n\n        # Determine direction\n        if pkt[TCP].dport == 502:\n            src_ip = pkt[IP].src\n            dst_ip = pkt[IP].dst\n            is_request = True\n        else:\n            src_ip = pkt[IP].dst\n            dst_ip = pkt[IP].src\n            is_request = False\n\n        if not is_request:\n            return  # Only analyze requests\n\n        session_key = f\"{src_ip}->{dst_ip}\"\n\n        # Get or create session\n        if session_key not in self.sessions:\n            self.sessions[session_key] = ModbusSession(src_ip=src_ip, dst_ip=dst_ip)\n\n        session = self.sessions[session_key]\n        session.request_count += 1\n        session.func_codes_seen[func_code] += 1\n\n        # ── Anomaly Detection Rules ──\n\n        # Rule 1: Unauthorized Modbus client\n        if self.authorized_clients and src_ip not in self.authorized_clients:\n            self.anomalies.append(ModbusAnomaly(\n                timestamp=ts_str,\n                anomaly_type=\"UNAUTHORIZED_CLIENT\",\n                severity=\"critical\",\n                src_ip=src_ip, dst_ip=dst_ip,\n                unit_id=unit_id, func_code=func_code,\n                detail=f\"Modbus request from unauthorized client {src_ip}\",\n                mitre_technique=\"T0886 - Remote Services\",\n            ))\n\n        # Rule 2: Unauthorized function code\n        allowed_fcs = self.authorized_func_codes.get(session_key)\n        if allowed_fcs and func_code not in allowed_fcs:\n            fc_info = MODBUS_FUNCTION_CODES.get(func_code, (f\"Unknown FC{func_code}\", \"unknown\"))\n            severity = \"critical\" if fc_info[1] == \"write\" else \"high\"\n            self.anomalies.append(ModbusAnomaly(\n                timestamp=ts_str,\n                anomaly_type=\"UNAUTHORIZED_FUNCTION_CODE\",\n                severity=severity,\n                src_ip=src_ip, dst_ip=dst_ip,\n                unit_id=unit_id, func_code=func_code,\n                detail=f\"FC {func_code} ({fc_info[0]}) not in allowlist {sorted(allowed_fcs)}\",\n                mitre_technique=\"T0855 - Unauthorized Command Message\",\n            ))\n\n        # Rule 3: Write operation detection\n        if func_code in (5, 6, 15, 16, 22, 23):\n            session.write_count += 1\n            fc_name = MODBUS_FUNCTION_CODES.get(func_code, (\"Unknown\", \"\"))[0]\n\n            # Extract register address\n            if len(payload) >= 10:\n                register_addr = struct.unpack(\">H\", payload[8:10])[0]\n                session.register_ranges.add((func_code, register_addr))\n\n                self.anomalies.append(ModbusAnomaly(\n                    timestamp=ts_str,\n                    anomaly_type=\"WRITE_OPERATION\",\n                    severity=\"high\",\n                    src_ip=src_ip, dst_ip=dst_ip,\n                    unit_id=unit_id, func_code=func_code,\n                    detail=f\"Write: {fc_name} to register {register_addr} from {src_ip}\",\n                    mitre_technique=\"T0836 - Modify Parameter\",\n                ))\n\n        # Rule 4: Timing anomaly\n        if session.last_timestamp > 0:\n            interval = (timestamp - session.last_timestamp) * 1000  # ms\n            session.intervals.append(interval)\n\n            baseline = self.baseline_sessions.get(session_key)\n            if baseline and len(session.intervals) > 10:\n                expected_interval = baseline.get(\"polling_interval_avg_sec\", 0) * 1000\n                expected_std = baseline.get(\"polling_interval_stddev\", 0) * 1000\n\n                if expected_std > 0:\n                    z_score = abs(interval - expected_interval) \u002F expected_std\n                    if z_score > 5.0:\n                        self.anomalies.append(ModbusAnomaly(\n                            timestamp=ts_str,\n                            anomaly_type=\"TIMING_ANOMALY\",\n                            severity=\"medium\",\n                            src_ip=src_ip, dst_ip=dst_ip,\n                            unit_id=unit_id, func_code=func_code,\n                            detail=(\n                                f\"Interval {interval:.0f}ms vs baseline \"\n                                f\"{expected_interval:.0f}ms (z={z_score:.1f})\"\n                            ),\n                            mitre_technique=\"T0831 - Manipulation of Control\",\n                        ))\n\n        # Rule 5: Protocol violation - invalid protocol ID\n        if proto_id != 0:\n            self.anomalies.append(ModbusAnomaly(\n                timestamp=ts_str,\n                anomaly_type=\"PROTOCOL_VIOLATION\",\n                severity=\"high\",\n                src_ip=src_ip, dst_ip=dst_ip,\n                unit_id=unit_id, func_code=func_code,\n                detail=f\"Non-standard protocol ID {proto_id} (expected 0)\",\n                mitre_technique=\"T0830 - Man in the Middle\",\n            ))\n\n        # Rule 6: Broadcast write (unit ID 0)\n        if unit_id == 0 and func_code in (5, 6, 15, 16):\n            self.anomalies.append(ModbusAnomaly(\n                timestamp=ts_str,\n                anomaly_type=\"BROADCAST_WRITE\",\n                severity=\"critical\",\n                src_ip=src_ip, dst_ip=dst_ip,\n                unit_id=unit_id, func_code=func_code,\n                detail=\"Broadcast write command (unit ID 0) affects ALL slaves\",\n                mitre_technique=\"T0855 - Unauthorized Command Message\",\n            ))\n\n        session.last_timestamp = timestamp\n\n    def analyze_pcap(self, pcap_file):\n        \"\"\"Analyze pcap file for Modbus anomalies.\"\"\"\n        print(f\"[*] Analyzing {pcap_file}...\")\n        packets = rdpcap(pcap_file)\n        for pkt in packets:\n            self.process_packet(pkt)\n        print(f\"[*] Processed {self.packet_count} Modbus packets\")\n\n    def generate_report(self):\n        \"\"\"Generate anomaly detection report.\"\"\"\n        print(f\"\\n{'='*70}\")\n        print(\"MODBUS PROTOCOL ANOMALY DETECTION REPORT\")\n        print(f\"{'='*70}\")\n        print(f\"Packets analyzed: {self.packet_count}\")\n        print(f\"Sessions tracked: {len(self.sessions)}\")\n        print(f\"Anomalies detected: {len(self.anomalies)}\")\n\n        severity_counts = defaultdict(int)\n        type_counts = defaultdict(int)\n        for a in self.anomalies:\n            severity_counts[a.severity] += 1\n            type_counts[a.anomaly_type] += 1\n\n        print(f\"\\nBy Severity:\")\n        for sev in [\"critical\", \"high\", \"medium\", \"low\"]:\n            if severity_counts[sev]:\n                print(f\"  {sev.upper()}: {severity_counts[sev]}\")\n\n        print(f\"\\nBy Type:\")\n        for atype, count in sorted(type_counts.items(), key=lambda x: -x[1]):\n            print(f\"  {atype}: {count}\")\n\n        print(f\"\\nTop Anomalies:\")\n        for a in self.anomalies[:15]:\n            print(f\"  [{a.severity.upper()}] {a.anomaly_type}: {a.detail}\")\n\n\nif __name__ == \"__main__\":\n    detector = ModbusAnomalyDetector()\n\n    if len(sys.argv) > 1:\n        # Load baseline if provided\n        if len(sys.argv) > 2:\n            detector.load_baseline(sys.argv[2])\n        detector.analyze_pcap(sys.argv[1])\n        detector.generate_report()\n    else:\n        print(\"Usage: python modbus_detector.py \u003Cpcap_file> [baseline.json]\")\n```\n\n## Key Concepts\n\n| Term | Definition |\n|------|------------|\n| Modbus\u002FTCP | Industrial protocol running on TCP port 502, consisting of an MBAP header and PDU with function code and data |\n| Function Code | Modbus command identifier (FC1-4: reads, FC5-6\u002F15-16: writes, FC8: diagnostics) determining the operation type |\n| MBAP Header | Modbus Application Protocol header containing transaction ID, protocol ID (0x0000), length, and unit ID |\n| Unit ID | Modbus address (0-247) identifying the target slave device; unit ID 0 is broadcast to all slaves |\n| Register Map | Vendor-specific mapping of Modbus register addresses to process variables (e.g., register 40001 = reactor temperature) |\n| Function Code Allowlist | Security policy defining which Modbus function codes are permitted from each source IP to each target device |\n\n## Tools & Systems\n\n- **Zeek Modbus Analyzer**: Network security monitor with built-in Modbus\u002FTCP protocol analysis and logging\n- **Suricata with ET Open ICS rules**: IDS\u002FIPS with Modbus-specific detection rules for command injection and anomalies\n- **Wireshark Modbus Dissector**: Protocol analyzer with full Modbus\u002FTCP and Modbus RTU decoding\n- **PyModbus**: Python Modbus library for building custom monitoring and testing tools\n\n## Output Format\n\n```\nModbus Protocol Anomaly Detection Report\n==========================================\nCapture Period: YYYY-MM-DD to YYYY-MM-DD\nPackets Analyzed: [N]\nSessions: [N]\n\nANOMALIES: [N]\n  UNAUTHORIZED_CLIENT: [N]\n  UNAUTHORIZED_FUNCTION_CODE: [N]\n  WRITE_OPERATION: [N]\n  TIMING_ANOMALY: [N]\n  BROADCAST_WRITE: [N]\n```\n","# Detecting Modbus Protocol Anomalies\n\n## When to Use\n\n- When deploying Modbus-specific intrusion detection in an OT environment\n- When building baseline models for deterministic Modbus polling patterns\n- When investigating suspicious Modbus traffic flagged by OT monitoring tools\n- When implementing function code allowlisting on industrial firewalls\n- When detecting unauthorized Modbus write commands that could manipulate process setpoints\n\n**Do not use** for securing Modbus communications end-to-end (Modbus has no native security; see implementing-network-segmentation-for-ot for firewall-based controls), for non-Modbus protocol monitoring (see detecting-anomalies-in-industrial-control-systems for multi-protocol), or for active fuzzing of Modbus implementations (see performing-plc-firmware-security-analysis).\n\n## Prerequisites\n\n- Network SPAN\u002FTAP access to monitor Modbus\u002FTCP traffic (port 502)\n- Zeek (formerly Bro) with Modbus protocol analyzer or Suricata with OT rulesets\n- Python 3.9+ with scapy and pymodbus for custom analysis\n- Baseline capture of normal Modbus traffic (minimum 1-2 weeks)\n- Documentation of authorized Modbus clients, function codes, and register maps\n\n## Workflow\n\n### Step 1: Capture and Parse Modbus Traffic\n\nDeploy passive monitoring to capture all Modbus\u002FTCP traffic and parse it into structured records for analysis.\n\n```python\n#!\u002Fusr\u002Fbin\u002Fenv python3\n\"\"\"Modbus Protocol Anomaly Detection System.\n\nMonitors Modbus\u002FTCP traffic for anomalies including unauthorized\nfunction codes, unusual register access, timing deviations,\nand rogue client devices.\n\"\"\"\n\nimport json\nimport struct\nimport sys\nimport time\nfrom collections import defaultdict, deque\nfrom dataclasses import dataclass, field\nfrom datetime import datetime\nfrom statistics import mean, stdev\n\ntry:\n    from scapy.all import sniff, rdpcap, IP, TCP\nexcept ImportError:\n    print(\"Install scapy: pip install scapy\")\n    sys.exit(1)\n\n\nMODBUS_FUNCTION_CODES = {\n    1: (\"Read Coils\", \"read\"),\n    2: (\"Read Discrete Inputs\", \"read\"),\n    3: (\"Read Holding Registers\", \"read\"),\n    4: (\"Read Input Registers\", \"read\"),\n    5: (\"Write Single Coil\", \"write\"),\n    6: (\"Write Single Register\", \"write\"),\n    7: (\"Read Exception Status\", \"diagnostic\"),\n    8: (\"Diagnostics\", \"diagnostic\"),\n    11: (\"Get Comm Event Counter\", \"diagnostic\"),\n    12: (\"Get Comm Event Log\", \"diagnostic\"),\n    15: (\"Write Multiple Coils\", \"write\"),\n    16: (\"Write Multiple Registers\", \"write\"),\n    17: (\"Report Slave ID\", \"diagnostic\"),\n    22: (\"Mask Write Register\", \"write\"),\n    23: (\"Read\u002FWrite Multiple Registers\", \"read_write\"),\n    43: (\"Encapsulated Interface Transport\", \"diagnostic\"),\n}\n\n\n@dataclass\nclass ModbusAnomaly:\n    timestamp: str\n    anomaly_type: str\n    severity: str\n    src_ip: str\n    dst_ip: str\n    unit_id: int\n    func_code: int\n    detail: str\n    mitre_technique: str = \"\"\n\n\n@dataclass\nclass ModbusSession:\n    \"\"\"Tracks state for a Modbus master-slave session.\"\"\"\n    src_ip: str\n    dst_ip: str\n    func_codes_seen: dict = field(default_factory=lambda: defaultdict(int))\n    register_ranges: set = field(default_factory=set)\n    intervals: list = field(default_factory=lambda: deque(maxlen=500))\n    last_timestamp: float = 0\n    request_count: int = 0\n    write_count: int = 0\n\n\nclass ModbusAnomalyDetector:\n    \"\"\"Detects anomalies in Modbus\u002FTCP traffic.\"\"\"\n\n    def __init__(self):\n        self.sessions = {}\n        self.baseline_sessions = {}\n        self.anomalies = []\n        self.authorized_clients = set()\n        self.authorized_func_codes = {}  # per-session allowed FCs\n        self.packet_count = 0\n\n    def set_authorized_clients(self, clients):\n        \"\"\"Set list of authorized Modbus client IPs.\"\"\"\n        self.authorized_clients = set(clients)\n\n    def set_authorized_func_codes(self, session_key, func_codes):\n        \"\"\"Set allowed function codes for a specific session.\"\"\"\n        self.authorized_func_codes[session_key] = set(func_codes)\n\n    def load_baseline(self, baseline_file):\n        \"\"\"Load baseline profiles from previous capture analysis.\"\"\"\n        with open(baseline_file) as f:\n            baseline = json.load(f)\n        for key, data in baseline.get(\"modbus_baselines\", {}).items():\n            self.baseline_sessions[key] = data\n            self.authorized_func_codes[key] = set(data.get(\"allowed_function_codes\", []))\n        print(f\"[*] Loaded {len(self.baseline_sessions)} Modbus baselines\")\n\n    def process_packet(self, pkt):\n        \"\"\"Process a single packet for Modbus anomaly detection.\"\"\"\n        if not pkt.haslayer(TCP) or not pkt.haslayer(IP):\n            return\n\n        # Check for Modbus\u002FTCP (port 502)\n        if pkt[TCP].dport != 502 and pkt[TCP].sport != 502:\n            return\n\n        payload = bytes(pkt[TCP].payload)\n        if len(payload) \u003C 8:\n            return\n\n        self.packet_count += 1\n        timestamp = float(pkt.time)\n        ts_str = datetime.fromtimestamp(timestamp).isoformat()\n\n        # Parse MBAP header\n        try:\n            trans_id = struct.unpack(\">H\", payload[0:2])[0]\n            proto_id = struct.unpack(\">H\", payload[2:4])[0]\n            length = struct.unpack(\">H\", payload[4:6])[0]\n            unit_id = payload[6]\n            func_code = payload[7]\n        except (IndexError, struct.error):\n            return\n\n        # Determine direction\n        if pkt[TCP].dport == 502:\n            src_ip = pkt[IP].src\n            dst_ip = pkt[IP].dst\n            is_request = True\n        else:\n            src_ip = pkt[IP].dst\n            dst_ip = pkt[IP].src\n            is_request = False\n\n        if not is_request:\n            return  # Only analyze requests\n\n        session_key = f\"{src_ip}->{dst_ip}\"\n\n        # Get or create session\n        if session_key not in self.sessions:\n            self.sessions[session_key] = ModbusSession(src_ip=src_ip, dst_ip=dst_ip)\n\n        session = self.sessions[session_key]\n        session.request_count += 1\n        session.func_codes_seen[func_code] += 1\n\n        # ── Anomaly Detection Rules ──\n\n        # Rule 1: Unauthorized Modbus client\n        if self.authorized_clients and src_ip not in self.authorized_clients:\n            self.anomalies.append(ModbusAnomaly(\n                timestamp=ts_str,\n                anomaly_type=\"UNAUTHORIZED_CLIENT\",\n                severity=\"critical\",\n                src_ip=src_ip, dst_ip=dst_ip,\n                unit_id=unit_id, func_code=func_code,\n                detail=f\"Modbus request from unauthorized client {src_ip}\",\n                mitre_technique=\"T0886 - Remote Services\",\n            ))\n\n        # Rule 2: Unauthorized function code\n        allowed_fcs = self.authorized_func_codes.get(session_key)\n        if allowed_fcs and func_code not in allowed_fcs:\n            fc_info = MODBUS_FUNCTION_CODES.get(func_code, (f\"Unknown FC{func_code}\", \"unknown\"))\n            severity = \"critical\" if fc_info[1] == \"write\" else \"high\"\n            self.anomalies.append(ModbusAnomaly(\n                timestamp=ts_str,\n                anomaly_type=\"UNAUTHORIZED_FUNCTION_CODE\",\n                severity=severity,\n                src_ip=src_ip, dst_ip=dst_ip,\n                unit_id=unit_id, func_code=func_code,\n                detail=f\"FC {func_code} ({fc_info[0]}) not in allowlist {sorted(allowed_fcs)}\",\n                mitre_technique=\"T0855 - Unauthorized Command Message\",\n            ))\n\n        # Rule 3: Write operation detection\n        if func_code in (5, 6, 15, 16, 22, 23):\n            session.write_count += 1\n            fc_name = MODBUS_FUNCTION_CODES.get(func_code, (\"Unknown\", \"\"))[0]\n\n            # Extract register address\n            if len(payload) >= 10:\n                register_addr = struct.unpack(\">H\", payload[8:10])[0]\n                session.register_ranges.add((func_code, register_addr))\n\n                self.anomalies.append(ModbusAnomaly(\n                    timestamp=ts_str,\n                    anomaly_type=\"WRITE_OPERATION\",\n                    severity=\"high\",\n                    src_ip=src_ip, dst_ip=dst_ip,\n                    unit_id=unit_id, func_code=func_code,\n                    detail=f\"Write: {fc_name} to register {register_addr} from {src_ip}\",\n                    mitre_technique=\"T0836 - Modify Parameter\",\n                ))\n\n        # Rule 4: Timing anomaly\n        if session.last_timestamp > 0:\n            interval = (timestamp - session.last_timestamp) * 1000  # ms\n            session.intervals.append(interval)\n\n            baseline = self.baseline_sessions.get(session_key)\n            if baseline and len(session.intervals) > 10:\n                expected_interval = baseline.get(\"polling_interval_avg_sec\", 0) * 1000\n                expected_std = baseline.get(\"polling_interval_stddev\", 0) * 1000\n\n                if expected_std > 0:\n                    z_score = abs(interval - expected_interval) \u002F expected_std\n                    if z_score > 5.0:\n                        self.anomalies.append(ModbusAnomaly(\n                            timestamp=ts_str,\n                            anomaly_type=\"TIMING_ANOMALY\",\n                            severity=\"medium\",\n                            src_ip=src_ip, dst_ip=dst_ip,\n                            unit_id=unit_id, func_code=func_code,\n                            detail=(\n                                f\"Interval {interval:.0f}ms vs baseline \"\n                                f\"{expected_interval:.0f}ms (z={z_score:.1f})\"\n                            ),\n                            mitre_technique=\"T0831 - Manipulation of Control\",\n                        ))\n\n        # Rule 5: Protocol violation - invalid protocol ID\n        if proto_id != 0:\n            self.anomalies.append(ModbusAnomaly(\n                timestamp=ts_str,\n                anomaly_type=\"PROTOCOL_VIOLATION\",\n                severity=\"high\",\n                src_ip=src_ip, dst_ip=dst_ip,\n                unit_id=unit_id, func_code=func_code,\n                detail=f\"Non-standard protocol ID {proto_id} (expected 0)\",\n                mitre_technique=\"T0830 - Man in the Middle\",\n            ))\n\n        # Rule 6: Broadcast write (unit ID 0)\n        if unit_id == 0 and func_code in (5, 6, 15, 16):\n            self.anomalies.append(ModbusAnomaly(\n                timestamp=ts_str,\n                anomaly_type=\"BROADCAST_WRITE\",\n                severity=\"critical\",\n                src_ip=src_ip, dst_ip=dst_ip,\n                unit_id=unit_id, func_code=func_code,\n                detail=\"Broadcast write command (unit ID 0) affects ALL slaves\",\n                mitre_technique=\"T0855 - Unauthorized Command Message\",\n            ))\n\n        session.last_timestamp = timestamp\n\n    def analyze_pcap(self, pcap_file):\n        \"\"\"Analyze pcap file for Modbus anomalies.\"\"\"\n        print(f\"[*] Analyzing {pcap_file}...\")\n        packets = rdpcap(pcap_file)\n        for pkt in packets:\n            self.process_packet(pkt)\n        print(f\"[*] Processed {self.packet_count} Modbus packets\")\n\n    def generate_report(self):\n        \"\"\"Generate anomaly detection report.\"\"\"\n        print(f\"\\n{'='*70}\")\n        print(\"MODBUS PROTOCOL ANOMALY DETECTION REPORT\")\n        print(f\"{'='*70}\")\n        print(f\"Packets analyzed: {self.packet_count}\")\n        print(f\"Sessions tracked: {len(self.sessions)}\")\n        print(f\"Anomalies detected: {len(self.anomalies)}\")\n\n        severity_counts = defaultdict(int)\n        type_counts = defaultdict(int)\n        for a in self.anomalies:\n            severity_counts[a.severity] += 1\n            type_counts[a.anomaly_type] += 1\n\n        print(f\"\\nBy Severity:\")\n        for sev in [\"critical\", \"high\", \"medium\", \"low\"]:\n            if severity_counts[sev]:\n                print(f\"  {sev.upper()}: {severity_counts[sev]}\")\n\n        print(f\"\\nBy Type:\")\n        for atype, count in sorted(type_counts.items(), key=lambda x: -x[1]):\n            print(f\"  {atype}: {count}\")\n\n        print(f\"\\nTop Anomalies:\")\n        for a in self.anomalies[:15]:\n            print(f\"  [{a.severity.upper()}] {a.anomaly_type}: {a.detail}\")\n\n\nif __name__ == \"__main__\":\n    detector = ModbusAnomalyDetector()\n\n    if len(sys.argv) > 1:\n        # Load baseline if provided\n        if len(sys.argv) > 2:\n            detector.load_baseline(sys.argv[2])\n        detector.analyze_pcap(sys.argv[1])\n        detector.generate_report()\n    else:\n        print(\"Usage: python modbus_detector.py \u003Cpcap_file> [baseline.json]\")\n```\n\n## Key Concepts\n\n| Term | Definition |\n|------|------------|\n| Modbus\u002FTCP | Industrial protocol running on TCP port 502, consisting of an MBAP header and PDU with function code and data |\n| Function Code | Modbus command identifier (FC1-4: reads, FC5-6\u002F15-16: writes, FC8: diagnostics) determining the operation type |\n| MBAP Header | Modbus Application Protocol header containing transaction ID, protocol ID (0x0000), length, and unit ID |\n| Unit ID | Modbus address (0-247) identifying the target slave device; unit ID 0 is broadcast to all slaves |\n| Register Map | Vendor-specific mapping of Modbus register addresses to process variables (e.g., register 40001 = reactor temperature) |\n| Function Code Allowlist | Security policy defining which Modbus function codes are permitted from each source IP to each target device |\n\n## Tools & Systems\n\n- **Zeek Modbus Analyzer**: Network security monitor with built-in Modbus\u002FTCP protocol analysis and logging\n- **Suricata with ET Open ICS rules**: IDS\u002FIPS with Modbus-specific detection rules for command injection and anomalies\n- **Wireshark Modbus Dissector**: Protocol analyzer with full Modbus\u002FTCP and Modbus RTU decoding\n- **PyModbus**: Python Modbus library for building custom monitoring and testing tools\n\n## Output Format\n\n```\nModbus Protocol Anomaly Detection Report\n==========================================\nCapture Period: YYYY-MM-DD to YYYY-MM-DD\nPackets Analyzed: [N]\nSessions: [N]\n\nANOMALIES: [N]\n  UNAUTHORIZED_CLIENT: [N]\n  UNAUTHORIZED_FUNCTION_CODE: [N]\n  WRITE_OPERATION: [N]\n  TIMING_ANOMALY: [N]\n  BROADCAST_WRITE: [N]\n```\n",{"name":5,"tags":49,"author":50,"domain":42,"license":15,"version":51,"nist_csf":52,"subdomain":57,"description":7,"nist_ai_rmf":58,"mitre_attack":62,"atlas_techniques":68},[33,32,31,30,29,25,28],"mahipal","1.0.0",[53,54,55,56],"PR.IR-01","DE.CM-01","ID.AM-05","GV.OC-02","ot-ics-security",[59,60,61],"MEASURE-2.7","MAP-5.1","MANAGE-2.4",[63,64,65,66,67],"T1078","T1190","T1059","T0816","T0836",[69,70,71],"AML.T0070","AML.T0066","AML.T0082","main","54a798831d2266a3ca61ce68a7acb80b81160d57","skills\u002Fdetecting-modbus-protocol-anomalies",4384,"https:\u002F\u002Fgithub.com\u002Fmukul975\u002FAnthropic-Cybersecurity-Skills","https:\u002F\u002Fmahipal.engineer\u002FAnthropic-Cybersecurity-Skills\u002F","https:\u002F\u002Fgithub.com\u002Fmukul975","Mahipal.engineer","🔐 Cybersecurity | 💻 Dev | 📷 Street Photographer\r\n🎓 MSc | 📚 Research & AI Security","2026-08-31 12:32:44",{"source":8,"skillName":5,"commands":83,"prompt":93,"zipAvailable":94},[84,87,90],{"tool":85,"command":86},"npx","npx skills add mukul975\u002FAnthropic-Cybersecurity-Skills --skill detecting-modbus-protocol-anomalies",{"tool":88,"command":89},"bunx","bunx skills add mukul975\u002FAnthropic-Cybersecurity-Skills --skill detecting-modbus-protocol-anomalies",{"tool":91,"command":92},"pnpm","pnpm dlx skills add mukul975\u002FAnthropic-Cybersecurity-Skills --skill detecting-modbus-protocol-anomalies","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fmukul975\u002FAnthropic-Cybersecurity-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fdetecting-modbus-protocol-anomalies\u002FSKILL.md",true,[96,101,107],{"path":97,"resourceType":97,"resourceTypeDesc":98,"sizeBytes":99,"sourceHtmlUrl":100},"LICENSE","许可证",11255,"https:\u002F\u002Fgithub.com\u002Fmukul975\u002FAnthropic-Cybersecurity-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fdetecting-modbus-protocol-anomalies\u002FLICENSE",{"path":102,"resourceType":103,"resourceTypeDesc":104,"sizeBytes":105,"sourceHtmlUrl":106},"references\u002Fapi-reference.md","REFERENCE","参考文档",1662,"https:\u002F\u002Fgithub.com\u002Fmukul975\u002FAnthropic-Cybersecurity-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fdetecting-modbus-protocol-anomalies\u002Freferences\u002Fapi-reference.md",{"path":108,"resourceType":109,"resourceTypeDesc":110,"sizeBytes":111,"sourceHtmlUrl":112},"scripts\u002Fagent.py","SCRIPT","脚本",5512,"https:\u002F\u002Fgithub.com\u002Fmukul975\u002FAnthropic-Cybersecurity-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fdetecting-modbus-protocol-anomalies\u002Fscripts\u002Fagent.py",[114,139,163,190,216,243],{"skillNo":115,"name":116,"nameZh":6,"description":117,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":118,"categories":119,"keywords":121,"publishedAt":44},"01M6AGDM3HJXTKXX15ZZ0ZVCJ3","verifying-build-provenance-with-slsa-sigstore","Verifies artifact signatures and SLSA provenance using Sigstore's cosign (verify, verify-attestation, verify-blob-attestation) and slsa-verifier (verify-artifact), enforcing keyless OIDC builder identity and source repo against SLSA Build levels. Use in CI\u002FCD before deploying artifacts, when consuming third-party attestations, establishing a SLSA Build L3 pipeline, or confirming provenance during incident response or admission control.","https:\u002F\u002Fgithub.com\u002Fmukul975\u002FAnthropic-Cybersecurity-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fverifying-build-provenance-with-slsa-sigstore\u002FSKILL.md",[120],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[122,123,124,125,126,42,127,128,41,40,129,130,131,132,133,134,135,136,137,138],"sigstore","provenance","slsa","verifying build provenance with slsa sigstore","security","incident response","identity","artifacts","ci","supply-chain","pipeline","supply-chain-security","t1195","attestation","code-signing","cosign","keyless",{"skillNo":140,"name":141,"nameZh":6,"description":142,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":143,"categories":144,"keywords":146,"publishedAt":44},"01M6AGDGBPWYVZ2F11GFKJ4HZ9","validating-tpm-measured-boot-attestation","Verifies TPM 2.0 measured-boot integrity and remote attestation with tpm2-tools -- reading PCRs (tpm2_pcrread), replaying the boot event log, generating and checking signed quotes (tpm2_quote\u002Ftpm2_checkquote), and sealing secrets to a PCR policy. Use to confirm a system booted trusted firmware\u002Fkernel for Zero Trust device posture, detect boot-chain tampering via PCR drift, or build a golden-value baseline for fleet attestation.","https:\u002F\u002Fgithub.com\u002Fmukul975\u002FAnthropic-Cybersecurity-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fvalidating-tpm-measured-boot-attestation\u002FSKILL.md",[145],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[147,135,148,149,126,150,42,41,151,152,153,154,155,156,157,158,159,160,161,162],"boot","TPM","validating tpm measured boot attestation","policy","verification","ZeRO","firmware","Tools","measured-boot","PCR","integrity","secrets","remote-attestation","tpm2-tools","integrity-verification","trusted-computing",{"skillNo":164,"name":165,"nameZh":6,"description":166,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":167,"categories":168,"keywords":173,"publishedAt":44},"01M6AGDCW0Y4FXZ3362GT1941M","validating-backup-integrity-for-recovery","Validates backup integrity through cryptographic hash verification, automated restore testing, corruption detection, and recoverability checks to confirm backups are reliable for disaster recovery and ransomware response. Use before relying on backups for recovery, when building post-backup validation pipelines, auditing backup infrastructure for compliance, or checking immutable\u002Fair-gapped backups for silent corruption or tampering.","https:\u002F\u002Fgithub.com\u002Fmukul975\u002FAnthropic-Cybersecurity-Skills\u002Fblob\u002Fmain\u002Fskills\u002Fvalidating-backup-integrity-for-recovery\u002FSKILL.md",[169,172],{"categoryNo":170,"categoryName":171,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量",{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[157,174,175,176,177,42,178,39,151,179,180,181,182,183,184,185,186,187,188,189],"backup","recovery","validating backup integrity for recovery","testing","infrastructure","compliance","hash-verification","incident-response","ransomware","cryptocurrency","detection","t1078","t1490","immutable","disaster-recovery","restore-testing",{"skillNo":191,"name":192,"nameZh":6,"description":193,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":194,"categories":195,"keywords":197,"publishedAt":44},"01M6AGD9GBG2KZ336AE763J3WH","triaging-windows-with-kape","Runs KAPE (Kroll Artifact Parser and Extractor) to collect targeted forensic artifacts (registry hives, $MFT, event logs, prefetch, browser data) via Targets and parse them with Modules wrapping Eric Zimmerman's EZ Tools (PECmd, MFTECmd, RECmd). Use during early incident containment\u002Ftriage when full disk imaging is impractical but a defensible, parseable Windows artifact set is needed quickly, including at-scale remote collection.","https:\u002F\u002Fgithub.com\u002Fmukul975\u002FAnthropic-Cybersecurity-Skills\u002Fblob\u002Fmain\u002Fskills\u002Ftriaging-windows-with-kape\u002FSKILL.md",[196],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[198,199,200,42,201,202,203,204,205,206,207,208,209,210,211,212,154,213,214,215],"windows","kape","triaging windows with kape","registry","browser","logs","windows-forensics","dfir","mft","mftecmd","data","prefetch","eric-zimmerman","digital-forensics","SET","collection","pecmd","artifact-collection",{"skillNo":217,"name":218,"nameZh":6,"description":219,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":220,"categories":221,"keywords":223,"publishedAt":44},"01M6AGD5PW5J4EVZM958WD74ZQ","triaging-vulnerabilities-with-ssvc-framework","Triages and prioritizes vulnerabilities with CISA's Stakeholder-Specific Vulnerability Categorization (SSVC) decision tree, weighing exploitation status (via the CISA KEV catalog and FIRST EPSS API), technical impact, automatability, and mission prevalence to output Track\u002FTrack*\u002FAttend\u002FAct decisions. Use when prioritizing vulnerability scan results (OpenVAS, Nessus, Qualys) for remediation planning beyond raw CVSS scores.","https:\u002F\u002Fgithub.com\u002Fmukul975\u002FAnthropic-Cybersecurity-Skills\u002Fblob\u002Fmain\u002Fskills\u002Ftriaging-vulnerabilities-with-ssvc-framework\u002FSKILL.md",[222],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[224,225,226,42,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242],"vulnerabilities","framework","triaging vulnerabilities with ssvc framework","status","planning","CISA","vulnerability-management","risk-management","nessus","qualys","openvas","cvss","remediation","risk","vulnerability","api","prioritization","ssvc","decision-tree",{"skillNo":244,"name":245,"nameZh":6,"description":246,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":247,"categories":248,"keywords":250,"publishedAt":44},"01M6AGD24TTQQM4CXG69E3B9TJ","triaging-security-incident","Performs initial triage of security incidents using the NIST SP 800-61r3 and SANS PICERL frameworks, classifying incident type, assigning priority by business impact, and routing to the appropriate response team. Use when a SIEM\u002FEDR alert needs human classification, concurrent alerts must be prioritized, or a user report or threat-intel IOC match requires initial incident categorization.","https:\u002F\u002Fgithub.com\u002Fmukul975\u002FAnthropic-Cybersecurity-Skills\u002Fblob\u002Fmain\u002Fskills\u002Ftriaging-security-incident\u002FSKILL.md",[249],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[126,251,42,252,253,254,255,256,257,258,259,185,260,186,261,262,263,264,265,266],"triaging security incident","siem","file","ioc","soc","NIST-800-61","NIST","severity","edr","execution","analysis","classification","severity-classification","incident-triage","SANS-PICERL","SOC-operations"]