Modbus - TCP/502
A protocol for PLCs to store values in coils, inputs, and registers at addresses
Last updated
A protocol for PLCs to store values in coils, inputs, and registers at addresses
Last updated
Modbus is a communication protocol commonly used in Programmable Logic Controllers (PLCs). There are a few different versions, but the most common are Modbus RTU and TCP:
Modbus RTU (Remote Terminal Unit): Used in slow serial communication, and makes use of a compact, binary representation of the data for protocol communication
Modbus ASCII: Similar to RTU, but only using printable ASCII characters for protocol communication, meaning it is less efficient
Modbus RTU/IP: A variant of Modbus TCP that differs in that a checksum is included in the payload, as with Modbus RTU
You can see a Modbus server as a small piece of memory, being able to store values at addresses in different sizes. The client asks the server for values or tells it to write a value somewhere. The following table shows all the different types of values:
Coil
Read-write
1 bit (0-1)
00001 – 09999
Discrete input
Read-only
1 bit (0-1)
10001 – 19999
Input register
Read-only
16 bits (0-65535)
30001 – 39999
Holding register
Read-write
16 bits (0-65535)
40001 – 49999
These coils, inputs, and registers can all have arbitrary meanings, like configuration values, status outputs, or ASCII-encoded strings. The addresses are pretty limited, so it is generally useful to dump all the data and then guess what it means locally, and possibly by testing the effects of changing values.
Using a tool like modbus-cli
you can interact with a modbus server easily from the CLI:
This writeup is a good reference as an example of reading data, understanding it, and then changing it using the tool above. First, we try reading from the different registers, which we can even do in bulk for large ranges at a time. Be careful with this however because if your requested range falls out of the range the server holds it will give an InvalidAddress
error message. Here we try reading the first 5 values from the Holding registers:
Do dump the entire space, simply keep increasing this amount until you get an error. From here it is a matter of understanding what the data means. The NahamConCTF 2023 - Where's my Water challenge for example contained the following values:
These values are all in the 50-60 and 90-110 range which is likely an ASCII string padded with 17
s. We can decode it using CyberChef to "ow_enabled:false"
. We could try to change this false
to true
to see what happens, and then write it back to the Modbus server at the correct offset (CyberChef):
This was the solution to the challenge, because when the server now reads the Modbus data it finds our altered "ow_enabled:true"
string instead.
Using the tool above you can even write special data types like signed integers or floating point numbers by providing a --int
or --float
parameter:
word (default, unsigned)
16 bits
%MW100
400101
--word
integer (signed)
16 bits
%MW100
400101
--int
floating point
32 bits
%MF100
400101
--float
double word
32 bits
%MD100
400101
--dword
boolean (coils)
1 bit
%M100
101
N/A
Modbus TCP: Used on a network, over TCP port 502. When hacking remotely this is by far the most common. See for analyzing Wireshark captures of this data