modbus-protocol

theory

Lesson 1 — The Modbus Protocol

History and Context

Modbus was created in 1979 by Modicon (now part of Schneider Electric) for communication between programmable logic controllers over RS-232 serial lines. The design goal was simple: a reliable, deterministic master/slave protocol for factory floor automation. Security was never a consideration — the assumption was that industrial networks were physically isolated from everything else.

That assumption has been wrong for decades.

Modbus is now the most widely deployed industrial protocol on the planet. It runs in power generation and distribution, water treatment plants, oil and gas pipelines, building HVAC systems, manufacturing lines, and rail infrastructure. A 1979 serial protocol is controlling critical infrastructure in 2026, and it has never gained authentication, encryption, or any integrity verification mechanism. That is the attack surface.


Protocol Variants

Modbus RTU is the original serial variant. It runs over RS-232 or RS-485 (differential pair, supports multiple devices on the same cable). The frame uses binary encoding with a CRC-16 checksum appended. Physical access or access to a serial-to-TCP bridge is required to reach RTU devices.

Modbus ASCII is a variant where the same frame is encoded in ASCII hex, typically used on RS-232 connections. Less common, slower, uses LRC checksum instead of CRC.

Modbus TCP is the Ethernet adaptation introduced in 1999. The RTU PDU is wrapped in a 6-byte header called the MBAP (Modbus Application Protocol header) and carried over TCP port 502. There is still no authentication, no encryption, and no session concept beyond the TCP connection. This is the variant you will encounter on networks and on the internet.


Protocol Model

Modbus is a pure request/response protocol. One master (client in TCP terminology) initiates all transactions. One or more slaves (servers) respond. Slaves never send unsolicited data.

In Modbus TCP: - The client opens a TCP connection to port 502 - The client sends a request frame - The server responds - The connection may be kept open or closed after each transaction

There is no handshake. There is no login. If you can reach port 502, you can read and write the device. This is not a misconfiguration — it is the protocol by design.


MBAP Header Structure

Every Modbus TCP frame starts with a 6-byte MBAP header followed by the PDU (Protocol Data Unit):

+------------------+------------------+------------------+------------------+
| Transaction ID   | Protocol ID      | Length           | Unit ID          |
| 2 bytes          | 2 bytes (= 0x0000)| 2 bytes          | 1 byte           |
+------------------+------------------+------------------+------------------+
| Function Code    | Data ...                                                |
| 1 byte           |                                                         |
+--------------------------------------------------------------------------- +
  • Transaction ID: Client-assigned, echoed by server. Allows matching responses to requests.
  • Protocol ID: Always 0x0000 for Modbus.
  • Length: Number of bytes following, starting from Unit ID.
  • Unit ID: Identifies the slave device (equivalent to RTU address). Range 1–247. Value 255 is sometimes used as a broadcast or "ignore" value depending on implementation.
  • Function Code: Specifies the operation (read coils, write register, etc.).

Data Model

Modbus organizes device data into four tables:

Table Prefix Type Access Typical Use
Coils 0x (00001–09999) 1-bit Read/Write Digital outputs: relay on/off, valve open/close
Discrete Inputs 1x (10001–19999) 1-bit Read-only Digital inputs: pushbutton state, limit switch
Input Registers 3x (30001–39999) 16-bit Read-only Analog inputs: temperature, pressure, flow rate
Holding Registers 4x (40001–49999) 16-bit Read/Write Setpoints, configuration, control parameters

Addressing note: Modbus PDUs use zero-based addressing. Register 40001 in HMI display corresponds to address 0 in the PDU. Register 40011 is address 10. This discrepancy causes constant confusion. When you read address 0 with FC03, you're reading what the HMI calls register 40001.

Register size: Each register holds 16 bits (unsigned, 0–65535). Multi-word data types use consecutive registers: - 32-bit integer: 2 registers (big-endian or little-endian, device-dependent) - 32-bit float (IEEE 754): 2 registers - 64-bit values: 4 registers - ASCII strings: each register holds 2 characters


Function Codes

Function codes are single-byte identifiers that define the operation. The attacker-relevant subset:

FC Hex Name Description
01 0x01 Read Coils Read 1–2000 coil states
02 0x02 Read Discrete Inputs Read 1–2000 discrete input states
03 0x03 Read Holding Registers Read 1–125 holding registers
04 0x04 Read Input Registers Read 1–125 input registers
05 0x05 Write Single Coil Write one coil: 0xFF00 = ON, 0x0000 = OFF
06 0x06 Write Single Register Write one holding register
15 0x0F Write Multiple Coils Write 1–1968 coils in one request
16 0x10 Write Multiple Registers Write 1–123 registers in one request
43 0x2B Encapsulated Interface Transport Includes Read Device Identification (MEI type 14)

Error responses: If a request fails, the server returns the function code with the high bit set (e.g., FC03 error = 0x83) followed by an exception code. Exception code 0x02 means "illegal data address" — the address does not exist in this device. Exception code 0x01 means "illegal function" — FC not supported. These responses are useful for mapping the device's register space.

FC43 — Read Device Identification: This function code encapsulates the MEI (Modbus Encapsulated Interface) transport. MEI type 0x0E is the Device Identification sub-function. It returns structured objects identifying the device:

Object ID Name Content
0x01 VendorName Manufacturer name
0x02 ProductCode Model number
0x03 MajorMinorRevision Firmware version
0x05 VendorURL Support URL
0x06 ProductName Human-readable product name

Not all devices implement FC43. Those that do hand you a complete device fingerprint without any authentication.


Why This Protocol Is Still Everywhere

Modicon designed Modbus for a world of dedicated serial cables running at 9600 baud between a single master and a handful of slaves in a sealed cabinet. The protocol works reliably in that environment. Retrofitting authentication onto a protocol that runs on PLCs with 32KB of RAM and no operating system is genuinely difficult.

The installed base is enormous. Replacing Modbus-capable devices means replacing hardware that costs tens or hundreds of thousands of dollars per installation, requires recertification for safety-critical applications, and involves production downtime. Many of these systems have 20-30 year operational lifespans.

The result: a protocol designed with no security, widely deployed on networks connected to the internet, controlling physical processes that affect people's lives. Understanding it is not optional for anyone working in OT/ICS security.


Key Takeaways

  • Modbus TCP runs on port 502 with no authentication and no encryption
  • Four data tables: coils (1-bit RW), discrete inputs (1-bit RO), input registers (16-bit RO), holding registers (16-bit RW)
  • FC03 reads holding registers; FC05/FC06/FC16 write coils and registers
  • FC43 returns device identification — vendor, model, firmware version
  • PDU addresses are zero-based; HMI displays add 40001 offset for holding registers
  • The protocol is by design unauthenticated; exposure on routable networks is the real vulnerability