From cybersecurity-skills
Monitors SCADA/ICS Modbus TCP traffic for anomalous function codes, unauthorized writes, and suspicious patterns using pymodbus, Scapy, Zeek. For OT network threat detection and PLC security.
npx claudepluginhub mukul975/anthropic-cybersecurity-skills --plugin cybersecurity-skillsThis skill uses the workspace's default tool permissions.
- Monitoring OT/ICS networks for unauthorized Modbus commands targeting PLCs, RTUs, or HMIs
Applies Acme Corporation brand guidelines including colors, fonts, layouts, and messaging to generated PowerPoint, Excel, and PDF documents.
Builds DCF models with sensitivity analysis, Monte Carlo simulations, and scenario planning for investment valuation and risk assessment.
Calculates profitability (ROE, margins), liquidity (current ratio), leverage, efficiency, and valuation (P/E, EV/EBITDA) ratios from financial statements in CSV, JSON, text, or Excel for investment analysis.
Do not use on networks without authorization from the asset owner, for active injection or fuzzing against production SCADA systems, or as a replacement for safety-instrumented systems (SIS) that provide physical process protection.
Establish passive monitoring on the OT network segment and begin capturing Modbus TCP frames:
tcpdump -i eth0 port 502 -c 100 -w modbus_capture.pcap.from scapy.all import rdpcap, TCP
from scapy.contrib.modbus import ModbusADURequest, ModbusADUResponse
packets = rdpcap("modbus_capture.pcap")
for pkt in packets:
if pkt.haslayer(ModbusADURequest):
adu = pkt[ModbusADURequest]
print(f"Src: {pkt['IP'].src} -> Dst: {pkt['IP'].dst} "
f"Unit: {adu.unitId} FuncCode: {adu.funcCode}")
@load policy/protocols/modbus/known-masters-slaves to generate modbus.log entries containing timestamp, source/destination IPs, function code, and exception responses. This provides continuous passive logging without custom scripting.Build a behavioral profile of legitimate Modbus traffic to distinguish normal operations from anomalies:
Normal baseline example (72-hour period):
HMI (10.1.1.10) -> PLC (10.1.1.50):
FC 03 (Read Holding Registers): 432,180 packets (97.2%)
FC 04 (Read Input Registers): 10,540 packets (2.4%)
FC 06 (Write Single Register): 1,780 packets (0.4%)
FC 16 (Write Multiple Registers): 0 packets (0.0%)
FC 43 (Read Device ID): 0 packets (0.0%)
Apply rule-based and statistical detection to identify suspicious function code usage:
WRITE_FUNCTION_CODES = {5, 6, 15, 16}
AUTHORIZED_WRITERS = {"10.1.1.10", "10.1.1.11"} # HMI and engineering WS
def check_unauthorized_write(src_ip, function_code):
if function_code in WRITE_FUNCTION_CODES and src_ip not in AUTHORIZED_WRITERS:
return {
"alert": "UNAUTHORIZED_MODBUS_WRITE",
"severity": "CRITICAL",
"src_ip": src_ip,
"function_code": function_code,
"description": f"Write FC {function_code} from unauthorized source {src_ip}"
}
return None
Exception response correlation:
- Isolated exception (1-2 per hour): Normal operational error
- Burst (>10 per minute): Active scanning or fuzzing attempt
- Continuous (>100 per hour): Denial-of-service or tool malfunction
Detect attempts to manipulate physical process parameters through register value analysis:
REGISTER_LIMITS = {
40001: {"name": "Reactor Temperature Setpoint", "min": 50, "max": 200, "unit": "C",
"max_rate": 5}, # Max 5 degrees per write cycle
40010: {"name": "Pump Speed", "min": 0, "max": 3600, "unit": "RPM",
"max_rate": 200}, # Max 200 RPM change per cycle
40020: {"name": "Valve Position", "min": 0, "max": 100, "unit": "%",
"max_rate": 10}, # Max 10% per cycle
}
def check_register_value(register_addr, new_value, previous_value):
if register_addr not in REGISTER_LIMITS:
return None
limits = REGISTER_LIMITS[register_addr]
alerts = []
if new_value < limits["min"] or new_value > limits["max"]:
alerts.append({
"alert": "REGISTER_VALUE_OUT_OF_RANGE",
"severity": "CRITICAL",
"register": register_addr,
"name": limits["name"],
"value": new_value,
"range": f"{limits['min']}-{limits['max']} {limits['unit']}"
})
if previous_value is not None:
rate = abs(new_value - previous_value)
if rate > limits["max_rate"]:
alerts.append({
"alert": "REGISTER_VALUE_EXCESSIVE_RATE",
"severity": "HIGH",
"register": register_addr,
"name": limits["name"],
"change": rate,
"max_allowed": limits["max_rate"]
})
return alerts if alerts else None
Identify anomalies in communication patterns that may indicate man-in-the-middle, replay, or denial-of-service attacks:
AUTHORIZED_MASTERS = {"10.1.1.10", "10.1.1.11"}
def detect_rogue_master(src_ip, dst_ip, dst_port):
if dst_port == 502 and src_ip not in AUTHORIZED_MASTERS:
return {
"alert": "ROGUE_MODBUS_MASTER",
"severity": "CRITICAL",
"src_ip": src_ip,
"target_slave": dst_ip,
"description": "Unauthorized device initiating Modbus connection"
}
return None
import numpy as np
from collections import defaultdict
class TimingAnomalyDetector:
def __init__(self, window_size=1000, threshold_sigma=3.0):
self.windows = defaultdict(list)
self.window_size = window_size
self.threshold_sigma = threshold_sigma
def check(self, src_ip, dst_ip, timestamp):
key = (src_ip, dst_ip)
window = self.windows[key]
if len(window) > 0:
interval = timestamp - window[-1]
if len(window) >= 100:
mean = np.mean(np.diff(window[-100:]))
std = np.std(np.diff(window[-100:]))
if std > 0 and abs(interval - mean) > self.threshold_sigma * std:
return {
"alert": "TIMING_ANOMALY",
"severity": "MEDIUM",
"pair": f"{src_ip}->{dst_ip}",
"interval": interval,
"expected_mean": mean,
"deviation_sigma": abs(interval - mean) / std
}
window.append(timestamp)
if len(window) > self.window_size:
window.pop(0)
return None
| Term | Definition |
|---|---|
| Modbus TCP | An application-layer protocol encapsulating Modbus frames in TCP/IP, communicating on port 502. It uses a 7-byte MBAP header (transaction ID, protocol ID, length, unit ID) followed by the Modbus PDU containing the function code and data. |
| Function Code | A single-byte identifier in the Modbus PDU specifying the operation: read coils (01), read discrete inputs (02), read holding registers (03), read input registers (04), write single coil (05), write single register (06), write multiple coils (15), write multiple registers (16), diagnostics (08), and device identification (43). |
| MBAP Header | Modbus Application Protocol header used in Modbus TCP. Contains Transaction ID for request-response matching, Protocol ID (always 0x0000 for Modbus), Length of remaining bytes, and Unit Identifier for addressing slaves behind gateways. |
| Holding Register | A 16-bit read/write register in a Modbus slave addressed at range 40001-49999 (protocol address 0-9998). Used for setpoints, configuration, and control values that can be written by the master. Primary target for process manipulation attacks. |
| Coil | A single-bit read/write data element in a Modbus slave addressed at range 00001-09999. Controls discrete outputs (valves, pumps, breakers). Write operations (FC 05/15) to coils can directly affect physical equipment state. |
| Deep Packet Inspection | Analysis beyond TCP/IP headers into the Modbus application-layer payload to extract function codes, register addresses, and values. Required because standard firewalls only inspect IP/port, missing protocol-level attacks that use legitimate Modbus framing. |
| Rogue Master | An unauthorized device sending Modbus requests to slave devices. In OT environments, only designated HMI servers and engineering workstations should act as Modbus masters. A rogue master can read process data or write dangerous values to PLCs. |
| Register Value Baseline | The statistical profile (min, max, mean, standard deviation) of values observed in specific registers during normal operations. Deviations beyond physical process bounds indicate sensor failure or malicious manipulation. |
modbus.func_code == 6), and exporting specific fields for analysis.Context: A water treatment facility uses Modbus TCP to communicate between the SCADA server (10.1.1.10) and six PLCs controlling chemical dosing pumps, filtration valves, and flow meters. The security team deploys passive Modbus traffic monitoring after an industry advisory about attacks targeting water utilities.
Approach:
Pitfalls:
Context: A manufacturing plant's SOC observes unusual network activity from an engineering workstation (10.1.2.20) that is authorized for PLC programming. The OT security team uses Modbus traffic monitoring to determine if the workstation is being used for reconnaissance.
Approach:
Pitfalls:
## Modbus Traffic Anomaly Report
**Monitoring Period**: 2026-03-15 00:00:00 UTC to 2026-03-15 23:59:59 UTC
**Network Segment**: OT VLAN 10 (10.1.1.0/24)
**Packets Analyzed**: 2,847,320
**Anomalies Detected**: 4
---
### Alert 1: Unauthorized Write Operation
**Timestamp**: 2026-03-15 14:23:17 UTC
**Severity**: CRITICAL
**Source**: 10.1.2.20 (Engineering Workstation)
**Destination**: 10.1.1.52 (PLC-3 Chemical Dosing)
**Function Code**: 16 (Write Multiple Registers)
**Registers**: 40050-40055
**Values Written**: [250, 100, 0, 1, 3600, 1]
**Baseline**: FC 16 never observed for this source-destination pair
**Context**: Register 40050 (Chlorine Dosing Rate) changed from 25 to 250
(safe range: 10-40). Register 40054 (Dosing Timer) changed from 1800 to 3600.
Combined effect would double chlorine concentration over extended period.
**Recommended Action**: Immediately verify physical process state. Isolate
source device. Check register values against expected setpoints with
plant operator.
---
### Alert 2: Device Enumeration Detected
**Timestamp**: 2026-03-15 14:20:05 to 14:20:47 UTC
**Severity**: HIGH
**Source**: 10.1.2.20
**Targets**: 10.1.1.50, 10.1.1.51, 10.1.1.52, 10.1.1.53, 10.1.1.54 (+10 more)
**Function Code**: 43 (Read Device Identification)
**Baseline**: FC 43 never observed from this source
**Context**: Sequential scanning of 15 devices in 42 seconds. Device
identification responses reveal PLC vendor, model, and firmware versions
for all scanned devices.
**Recommended Action**: Investigate source workstation for compromise
indicators. Block FC 43 from non-engineering subnets at OT firewall.