Wiki / Protocols

Modbus

Legacy industrial request/response protocol for PLCs and RTUs. Plaintext, unauthenticated, and on TCP port 502 the only barrier is network reachability.

Modbus data model
Slave (PLC/RTU)
Coils (RW)
FC01 / FC05
Discrete inputs (R)
FC02
Holding regs (RW)
FC03 / FC06
Input regs (R)
FC04
A Modbus slave exposes four tables: 1-bit coils (read/write digital outputs) and discrete inputs (read-only), and 16-bit holding registers (read/write) and input registers (read-only). A function code (FC) selects the table and the read or write operation.

Modbus is a request/response industrial protocol for reading and writing the registers and coils of PLCs and RTUs. It dates to 1979 and carries no authentication and no encryption. On its TCP form (port 502) anyone who can reach the device on the network can read every sensor value and force every output.

What it is

Modbus is a strict master/slave (client/server) protocol: a master polls, a slave answers, slaves never speak unsolicited. It runs over serial (Modbus RTU, a compact binary frame with a CRC, on RS-485/RS-232) and over Ethernet (Modbus TCP, the same PDU wrapped in an MBAP header on port 502, no CRC because TCP handles integrity). The data a slave exposes is four simple tables:

Table Access Width Use
Coils read/write 1 bit digital outputs
Discrete inputs read 1 bit digital inputs
Holding registers read/write 16 bit setpoints, config
Input registers read 16 bit sensor readings

A one-byte function code selects the table and operation. The common ones: FC01 (read coils), FC02 (read discrete inputs), FC03 (read holding registers), FC04 (read input registers), FC05 (write single coil), FC06 (write single register), FC0F/FC10 (write multiple), and FC2B (read device identification).

Why it matters

Modbus sits under real infrastructure: water treatment, building automation, power, and manufacturing lines. Because there is no notion of identity, reading is unauthenticated and so is writing. Flipping a coil can energise a relay, open a valve, or stop a pump; changing a holding register can move a setpoint out of a safe range. There is no protocol-level control preventing any of it; the only gate is whether you can route a packet to port 502.

How to work it

  1. Find Modbus endpoints on a network:
nmap -p502 --script modbus-discover <subnet>
  1. Read the data tables with a client (pymodbus, modpoll, or mbpoll):
# read 10 holding registers starting at 0 from unit 1
mbpoll -m tcp -t4 -r1 -c10 -1 <ip>
  1. Read the device identity (FC2B) to fingerprint vendor and model, then map registers against the vendor's memory map.
  2. Write an output to actuate hardware, knowing exactly what it drives:
# force coil 1 ON on unit 1 (FC05)
mbpoll -m tcp -t0 -r1 -1 <ip> 1
  1. Capture a live session in Wireshark (the modbus dissector), then replay or tamper with function codes and setpoints to disrupt or study the process.

Pitfalls

  • This is live plant control. On a real target, a coil or register write moves physical actuators; treat any write as potentially unsafe and stay on a lab bench or a system you own.
  • Addressing off-by-one: documentation often uses 1-based "4xxxx" reference numbers while the wire protocol is 0-based. Register 40001 is protocol address 0.
  • Unit/slave IDs matter over a serial-to-TCP gateway: one IP can front many RTU units, each at its own unit ID.
  • Byte and word order for 32-bit values (floats, longs spanning two registers) is vendor-specific; a "garbage" reading is usually a swap issue, not a wrong address.

Further reading