detecting-modbus-protocol-anomalies

本技能涵盖检测工业控制系统中Modbus/TCP和Modbus RTU通信中的异常。内容涉及功能码监控、寄存器范围验证、时序分析、未授权客户端检测以及针对格式错误Modbus帧的深度包检测。该技能利用带Modbus协议分析器的Zeek、带OT规则的Suricata IDS以及使用马尔可夫链模型分析正常Modbus事务序列的自定义Python检测。

9 stars

Best use case

detecting-modbus-protocol-anomalies is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

本技能涵盖检测工业控制系统中Modbus/TCP和Modbus RTU通信中的异常。内容涉及功能码监控、寄存器范围验证、时序分析、未授权客户端检测以及针对格式错误Modbus帧的深度包检测。该技能利用带Modbus协议分析器的Zeek、带OT规则的Suricata IDS以及使用马尔可夫链模型分析正常Modbus事务序列的自定义Python检测。

Teams using detecting-modbus-protocol-anomalies should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/detecting-modbus-protocol-anomalies/SKILL.md --create-dirs "https://raw.githubusercontent.com/killvxk/cybersecurity-skills-zh/main/skills/detecting-modbus-protocol-anomalies/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/detecting-modbus-protocol-anomalies/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How detecting-modbus-protocol-anomalies Compares

Feature / Agentdetecting-modbus-protocol-anomaliesStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

本技能涵盖检测工业控制系统中Modbus/TCP和Modbus RTU通信中的异常。内容涉及功能码监控、寄存器范围验证、时序分析、未授权客户端检测以及针对格式错误Modbus帧的深度包检测。该技能利用带Modbus协议分析器的Zeek、带OT规则的Suricata IDS以及使用马尔可夫链模型分析正常Modbus事务序列的自定义Python检测。

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# 检测Modbus协议异常

## 适用场景

- 在OT环境中部署Modbus专用入侵检测
- 为确定性Modbus轮询模式构建基线模型
- 调查OT监控工具标记的可疑Modbus流量
- 在工业防火墙上实现功能码白名单
- 检测可能操控过程设定点的未授权Modbus写入命令

**不适用于**端到端保护Modbus通信(Modbus没有原生安全性;防火墙控制参见implementing-network-segmentation-for-ot)、非Modbus协议监控(多协议参见detecting-anomalies-in-industrial-control-systems),或对Modbus实现进行主动模糊测试(参见performing-plc-firmware-security-analysis)。

## 前置条件

- 监控Modbus/TCP流量的网络SPAN/TAP访问(端口502)
- 带Modbus协议分析器的Zeek(原Bro)或带OT规则集的Suricata
- Python 3.9+,包含用于自定义分析的scapy和pymodbus
- 正常Modbus流量的基线捕获(最少1-2周)
- 授权Modbus客户端、功能码和寄存器映射文档

## 工作流程

### 步骤 1:捕获和解析Modbus流量

部署被动监控以捕获所有Modbus/TCP流量,并将其解析为结构化记录用于分析。

```python
#!/usr/bin/env python3
"""Modbus Protocol Anomaly Detection System.

Monitors Modbus/TCP traffic for anomalies including unauthorized
function codes, unusual register access, timing deviations,
and rogue client devices.
"""

import json
import struct
import sys
import time
from collections import defaultdict, deque
from dataclasses import dataclass, field
from datetime import datetime
from statistics import mean, stdev

try:
    from scapy.all import sniff, rdpcap, IP, TCP
except ImportError:
    print("Install scapy: pip install scapy")
    sys.exit(1)


MODBUS_FUNCTION_CODES = {
    1: ("Read Coils", "read"),
    2: ("Read Discrete Inputs", "read"),
    3: ("Read Holding Registers", "read"),
    4: ("Read Input Registers", "read"),
    5: ("Write Single Coil", "write"),
    6: ("Write Single Register", "write"),
    7: ("Read Exception Status", "diagnostic"),
    8: ("Diagnostics", "diagnostic"),
    11: ("Get Comm Event Counter", "diagnostic"),
    12: ("Get Comm Event Log", "diagnostic"),
    15: ("Write Multiple Coils", "write"),
    16: ("Write Multiple Registers", "write"),
    17: ("Report Slave ID", "diagnostic"),
    22: ("Mask Write Register", "write"),
    23: ("Read/Write Multiple Registers", "read_write"),
    43: ("Encapsulated Interface Transport", "diagnostic"),
}


@dataclass
class ModbusAnomaly:
    timestamp: str
    anomaly_type: str
    severity: str
    src_ip: str
    dst_ip: str
    unit_id: int
    func_code: int
    detail: str
    mitre_technique: str = ""


@dataclass
class ModbusSession:
    """跟踪Modbus主站-从站会话状态。"""
    src_ip: str
    dst_ip: str
    func_codes_seen: dict = field(default_factory=lambda: defaultdict(int))
    register_ranges: set = field(default_factory=set)
    intervals: list = field(default_factory=lambda: deque(maxlen=500))
    last_timestamp: float = 0
    request_count: int = 0
    write_count: int = 0


class ModbusAnomalyDetector:
    """Detects anomalies in Modbus/TCP traffic."""

    def __init__(self):
        self.sessions = {}
        self.baseline_sessions = {}
        self.anomalies = []
        self.authorized_clients = set()
        self.authorized_func_codes = {}  # 每会话允许的功能码
        self.packet_count = 0

    def set_authorized_clients(self, clients):
        """Set list of authorized Modbus client IPs."""
        self.authorized_clients = set(clients)

    def set_authorized_func_codes(self, session_key, func_codes):
        """Set allowed function codes for a specific session."""
        self.authorized_func_codes[session_key] = set(func_codes)

    def load_baseline(self, baseline_file):
        """Load baseline profiles from previous capture analysis."""
        with open(baseline_file) as f:
            baseline = json.load(f)
        for key, data in baseline.get("modbus_baselines", {}).items():
            self.baseline_sessions[key] = data
            self.authorized_func_codes[key] = set(data.get("allowed_function_codes", []))
        print(f"[*] 已加载 {len(self.baseline_sessions)} 个Modbus基线")

    def process_packet(self, pkt):
        """Process a single packet for Modbus anomaly detection."""
        if not pkt.haslayer(TCP) or not pkt.haslayer(IP):
            return

        # 检查Modbus/TCP(端口502)
        if pkt[TCP].dport != 502 and pkt[TCP].sport != 502:
            return

        payload = bytes(pkt[TCP].payload)
        if len(payload) < 8:
            return

        self.packet_count += 1
        timestamp = float(pkt.time)
        ts_str = datetime.fromtimestamp(timestamp).isoformat()

        # 解析MBAP报头
        try:
            trans_id = struct.unpack(">H", payload[0:2])[0]
            proto_id = struct.unpack(">H", payload[2:4])[0]
            length = struct.unpack(">H", payload[4:6])[0]
            unit_id = payload[6]
            func_code = payload[7]
        except (IndexError, struct.error):
            return

        # 确定方向
        if pkt[TCP].dport == 502:
            src_ip = pkt[IP].src
            dst_ip = pkt[IP].dst
            is_request = True
        else:
            src_ip = pkt[IP].dst
            dst_ip = pkt[IP].src
            is_request = False

        if not is_request:
            return  # 仅分析请求

        session_key = f"{src_ip}->{dst_ip}"

        # 获取或创建会话
        if session_key not in self.sessions:
            self.sessions[session_key] = ModbusSession(src_ip=src_ip, dst_ip=dst_ip)

        session = self.sessions[session_key]
        session.request_count += 1
        session.func_codes_seen[func_code] += 1

        # ── 异常检测规则 ──

        # 规则1:未授权Modbus客户端
        if self.authorized_clients and src_ip not in self.authorized_clients:
            self.anomalies.append(ModbusAnomaly(
                timestamp=ts_str,
                anomaly_type="UNAUTHORIZED_CLIENT",
                severity="critical",
                src_ip=src_ip, dst_ip=dst_ip,
                unit_id=unit_id, func_code=func_code,
                detail=f"来自未授权客户端 {src_ip} 的Modbus请求",
                mitre_technique="T0886 - Remote Services",
            ))

        # 规则2:未授权功能码
        allowed_fcs = self.authorized_func_codes.get(session_key)
        if allowed_fcs and func_code not in allowed_fcs:
            fc_info = MODBUS_FUNCTION_CODES.get(func_code, (f"Unknown FC{func_code}", "unknown"))
            severity = "critical" if fc_info[1] == "write" else "high"
            self.anomalies.append(ModbusAnomaly(
                timestamp=ts_str,
                anomaly_type="UNAUTHORIZED_FUNCTION_CODE",
                severity=severity,
                src_ip=src_ip, dst_ip=dst_ip,
                unit_id=unit_id, func_code=func_code,
                detail=f"FC {func_code} ({fc_info[0]}) 不在白名单 {sorted(allowed_fcs)} 中",
                mitre_technique="T0855 - Unauthorized Command Message",
            ))

        # 规则3:写操作检测
        if func_code in (5, 6, 15, 16, 22, 23):
            session.write_count += 1
            fc_name = MODBUS_FUNCTION_CODES.get(func_code, ("Unknown", ""))[0]

            # 提取寄存器地址
            if len(payload) >= 10:
                register_addr = struct.unpack(">H", payload[8:10])[0]
                session.register_ranges.add((func_code, register_addr))

                self.anomalies.append(ModbusAnomaly(
                    timestamp=ts_str,
                    anomaly_type="WRITE_OPERATION",
                    severity="high",
                    src_ip=src_ip, dst_ip=dst_ip,
                    unit_id=unit_id, func_code=func_code,
                    detail=f"写入: {fc_name} 到寄存器 {register_addr} 来自 {src_ip}",
                    mitre_technique="T0836 - Modify Parameter",
                ))

        # 规则4:时序异常
        if session.last_timestamp > 0:
            interval = (timestamp - session.last_timestamp) * 1000  # ms
            session.intervals.append(interval)

            baseline = self.baseline_sessions.get(session_key)
            if baseline and len(session.intervals) > 10:
                expected_interval = baseline.get("polling_interval_avg_sec", 0) * 1000
                expected_std = baseline.get("polling_interval_stddev", 0) * 1000

                if expected_std > 0:
                    z_score = abs(interval - expected_interval) / expected_std
                    if z_score > 5.0:
                        self.anomalies.append(ModbusAnomaly(
                            timestamp=ts_str,
                            anomaly_type="TIMING_ANOMALY",
                            severity="medium",
                            src_ip=src_ip, dst_ip=dst_ip,
                            unit_id=unit_id, func_code=func_code,
                            detail=(
                                f"间隔 {interval:.0f}ms 对比基线 "
                                f"{expected_interval:.0f}ms (z={z_score:.1f})"
                            ),
                            mitre_technique="T0831 - Manipulation of Control",
                        ))

        # 规则5:协议违规 - 无效协议ID
        if proto_id != 0:
            self.anomalies.append(ModbusAnomaly(
                timestamp=ts_str,
                anomaly_type="PROTOCOL_VIOLATION",
                severity="high",
                src_ip=src_ip, dst_ip=dst_ip,
                unit_id=unit_id, func_code=func_code,
                detail=f"非标准协议ID {proto_id}(预期为0)",
                mitre_technique="T0830 - Man in the Middle",
            ))

        # 规则6:广播写入(单元ID 0)
        if unit_id == 0 and func_code in (5, 6, 15, 16):
            self.anomalies.append(ModbusAnomaly(
                timestamp=ts_str,
                anomaly_type="BROADCAST_WRITE",
                severity="critical",
                src_ip=src_ip, dst_ip=dst_ip,
                unit_id=unit_id, func_code=func_code,
                detail="广播写入命令(单元ID 0)影响所有从站",
                mitre_technique="T0855 - Unauthorized Command Message",
            ))

        session.last_timestamp = timestamp

    def analyze_pcap(self, pcap_file):
        """Analyze pcap file for Modbus anomalies."""
        print(f"[*] 正在分析 {pcap_file}...")
        packets = rdpcap(pcap_file)
        for pkt in packets:
            self.process_packet(pkt)
        print(f"[*] 已处理 {self.packet_count} 个Modbus数据包")

    def generate_report(self):
        """Generate anomaly detection report."""
        print(f"\n{'='*70}")
        print("MODBUS协议异常检测报告")
        print(f"{'='*70}")
        print(f"分析的数据包数: {self.packet_count}")
        print(f"跟踪的会话数: {len(self.sessions)}")
        print(f"检测到的异常数: {len(self.anomalies)}")

        severity_counts = defaultdict(int)
        type_counts = defaultdict(int)
        for a in self.anomalies:
            severity_counts[a.severity] += 1
            type_counts[a.anomaly_type] += 1

        print(f"\n按严重级别:")
        for sev in ["critical", "high", "medium", "low"]:
            if severity_counts[sev]:
                print(f"  {sev.upper()}: {severity_counts[sev]}")

        print(f"\n按类型:")
        for atype, count in sorted(type_counts.items(), key=lambda x: -x[1]):
            print(f"  {atype}: {count}")

        print(f"\n主要异常:")
        for a in self.anomalies[:15]:
            print(f"  [{a.severity.upper()}] {a.anomaly_type}: {a.detail}")


if __name__ == "__main__":
    detector = ModbusAnomalyDetector()

    if len(sys.argv) > 1:
        # 如果提供则加载基线
        if len(sys.argv) > 2:
            detector.load_baseline(sys.argv[2])
        detector.analyze_pcap(sys.argv[1])
        detector.generate_report()
    else:
        print("用法: python modbus_detector.py <pcap_file> [baseline.json]")
```

## 核心概念

| 术语 | 定义 |
|------|------|
| Modbus/TCP | 运行在TCP端口502上的工业协议,由MBAP报头和包含功能码和数据的PDU组成 |
| 功能码(Function Code) | Modbus命令标识符(FC1-4:读取,FC5-6/15-16:写入,FC8:诊断),决定操作类型 |
| MBAP报头(MBAP Header) | Modbus应用协议报头,包含事务ID、协议ID(0x0000)、长度和单元ID |
| 单元ID(Unit ID) | 标识目标从站设备的Modbus地址(0-247);单元ID 0是对所有从站的广播 |
| 寄存器映射(Register Map) | Modbus寄存器地址到过程变量的供应商特定映射(如寄存器40001=反应器温度) |
| 功能码白名单(Function Code Allowlist) | 定义每个源IP到每个目标设备允许使用哪些Modbus功能码的安全策略 |

## 工具与系统

- **Zeek Modbus Analyzer**:带内置Modbus/TCP协议分析和日志记录的网络安全监控工具
- **Suricata with ET Open ICS rules**:带有Modbus命令注入和异常检测规则的IDS/IPS
- **Wireshark Modbus Dissector**:具有完整Modbus/TCP和Modbus RTU解码的协议分析器
- **PyModbus**:用于构建自定义监控和测试工具的Python Modbus库

## 输出格式

```
Modbus协议异常检测报告
==========================================
捕获周期: YYYY-MM-DD 至 YYYY-MM-DD
分析的数据包数: [N]
会话数: [N]

异常数: [N]
  UNAUTHORIZED_CLIENT: [N]
  UNAUTHORIZED_FUNCTION_CODE: [N]
  WRITE_OPERATION: [N]
  TIMING_ANOMALY: [N]
  BROADCAST_WRITE: [N]
```

Related Skills

performing-s7comm-protocol-security-analysis

9
from killvxk/cybersecurity-skills-zh

对西门子SIMATIC S7 PLC使用的S7comm和S7CommPlus协议进行安全分析,识别漏洞,包括重放攻击、完整性绕过、未授权的CPU停止命令以及针对S7-300、S7-400、S7-1200和S7-1500控制器弱点的程序下载操控。

detecting-wmi-persistence

9
from killvxk/cybersecurity-skills-zh

通过分析 Sysmon 事件 ID 19、20 和 21 中的恶意 EventFilter、EventConsumer 和 FilterToConsumerBinding 创建,检测 WMI 事件订阅持久化。

detecting-t1548-abuse-elevation-control-mechanism

9
from killvxk/cybersecurity-skills-zh

通过监控注册表修改、进程提升标志和异常的父子进程关系,检测提升控制机制滥用,包括 UAC 绕过、sudo 利用和 setuid/setgid 操纵。

detecting-t1055-process-injection-with-sysmon

9
from killvxk/cybersecurity-skills-zh

通过分析 Sysmon 事件中的跨进程内存操作、远程线程创建和异常 DLL 加载模式,检测进程注入技术(T1055),包括经典 DLL 注入、进程镂空和 APC 注入。

detecting-t1003-credential-dumping-with-edr

9
from killvxk/cybersecurity-skills-zh

利用 EDR 遥测数据、Sysmon 进程访问监控和 Windows 安全事件关联,检测针对 LSASS 内存、SAM 数据库、NTDS.dit 和缓存凭据的 OS 凭据转储技术。

detecting-suspicious-powershell-execution

9
from killvxk/cybersecurity-skills-zh

检测可疑的 PowerShell 执行模式,包括编码命令、下载器(download cradles)、AMSI 绕过尝试以及受限语言模式规避。

detecting-suspicious-oauth-application-consent

9
from killvxk/cybersecurity-skills-zh

使用 Microsoft Graph API、审计日志和权限分析,检测 Azure AD / Microsoft Entra ID 中的高风险 OAuth 应用授权同意,识别非法同意授权攻击。

detecting-supply-chain-attacks-in-ci-cd

9
from killvxk/cybersecurity-skills-zh

扫描 GitHub Actions 工作流和 CI/CD 流水线配置,检测供应链攻击(Supply Chain Attack)向量, 包括未固定的 Action 版本、通过表达式的脚本注入、依赖混淆(Dependency Confusion)和密钥泄露。 使用 PyGithub 和 YAML 解析进行自动化审计。适用于加固 CI/CD 流水线或调查被攻击的构建系统。

detecting-stuxnet-style-attacks

9
from killvxk/cybersecurity-skills-zh

本技能涵盖检测遵循Stuxnet攻击模式的复杂网络物理攻击——在修改PLC逻辑的同时欺骗传感器读数以向操作员隐藏操控行为。内容涉及PLC逻辑完整性监控、基于物理的过程异常检测、工程师工作站入侵指标、USB传播攻击向量,以及从IT到OT横向移动直至过程操控的多阶段攻击链检测。

detecting-sql-injection-via-waf-logs

9
from killvxk/cybersecurity-skills-zh

分析 WAF(Web 应用防火墙,ModSecurity/AWS WAF/Cloudflare)日志,检测 SQL 注入(SQL Injection)攻击活动。 解析 ModSecurity 审计日志和 JSON WAF 事件日志,识别 SQLi 模式(UNION SELECT、OR 1=1、SLEEP()、BENCHMARK()), 追踪攻击源,关联多阶段注入尝试,并生成带 OWASP 分类的事件报告。

detecting-spearphishing-with-email-gateway

9
from killvxk/cybersecurity-skills-zh

鱼叉式网络钓鱼(Spearphishing)使用个性化、经过研究的内容针对特定个人,可绕过通用垃圾邮件过滤器。邮件安全网关(SEG)如 Microsoft Defender for Office 365、Proofpoint、Mimecast 和 Barracuda 提供高级检测能力,包括行为分析、URL 引爆、附件沙箱和冒充检测。本技能涵盖配置这些网关以检测和拦截定向钓鱼攻击。

detecting-shadow-it-cloud-usage

9
from killvxk/cybersecurity-skills-zh

通过使用 Python pandas 分析代理日志、DNS 查询日志和网络流数据,进行流量模式分析和域名分类,检测未授权的 SaaS 和云服务使用(影子 IT)。