
Multithreaded Modbus/TCP detection scanner written in C using libmodbus.
A multithreaded C scanner for detecting Modbus/TCP services using libmodbus.
The scanner does not rely only on TCP port 502 being open. Instead, it establishes a Modbus/TCP connection and sends an application layer Modbus request. A valid Modbus response is used as the primary indicator that a Modbus service is present.
Modbus is an industrial communication protocol commonly used by PLCs, RTUs, HMIs, SCADA systems, sensors, meters, and other industrial devices.
Modbus/TCP transports the Modbus application protocol over TCP.
The standard Modbus/TCP port is:
TCP/502
A typical communication flow is:
Scanner
|
| TCP connection → 502
|
| Modbus/TCP request
v
Modbus Device
|
| Modbus/TCP response
v
Scanner
Unlike protocols that provide a banner immediately after connecting, Modbus/TCP generally requires the client to send a valid Modbus request before the device produces an application layer response.
The scanner performs detection in two stages.
The scanner attempts to establish a TCP connection to:
<target>:502
If the connection cannot be established, the target is treated as not responding to Modbus/TCP.
However, an open TCP/502 port by itself is not considered sufficient evidence of Modbus.
After connecting, the scanner sends a Modbus request using libmodbus.
The primary probe is:
modbus_read_input_registers(ctx, 0, 1, ®);
This generates a Modbus Function Code:
0x04 - Read Input Registers
The request asks the target for one input register starting at address 0.
If the target returns a valid Modbus response, the scanner considers the service detected.
A Modbus/TCP packet consists of:
+----------------------+----------------------+
| MBAP Header | PDU |
+----------------------+----------------------+
MBAP Header:
+------------------+
| Transaction ID | 2 bytes
| Protocol ID | 2 bytes
| Length | 2 bytes
| Unit Identifier | 1 byte
+------------------+
PDU:
+------------------+
| Function Code | 1 byte
| Data | N bytes
+------------------+
The MBAP header is specific to Modbus/TCP.
The scanner's first probe uses Function Code 0x04.
A representative request is:
00 01 00 00 00 06 01 04 00 00 00 01
Breaking this down:
00 01 Transaction Identifier
00 00 Protocol Identifier
00 06 Length
01 Unit Identifier
04 Function Code
00 00 Starting Address
00 01 Quantity
00 01
Identifies the transaction.
The value can vary because the transaction identifier is normally managed by the Modbus client library.
00 00
A value of 0 identifies Modbus.
00 06
Specifies the number of bytes following the length field.
01
Identifies the target Modbus unit.
04
Function Code 0x04 means:
Read Input Registers
00 00
The scanner starts at register address 0.
00 01
The scanner requests one register.
A successful response to the request contains Function Code 0x04 and the requested register data.
A representative response could look like:
00 01 00 00 00 05 01 04 02 00 00
Breaking it down:
00 01 Transaction Identifier
00 00 Protocol Identifier
00 05 Length
01 Unit Identifier
04 Function Code
02 Byte Count
00 00 Register Value
The important part for detection is that the target successfully processes the Modbus request and returns a valid Modbus application-layer response.
The actual register value is device-dependent.
Simply checking:
TCP/502 = OPEN
does not necessarily prove that the service is Modbus.
Port numbers are conventions. A different application can listen on TCP/502, and a Modbus device may also behave differently depending on its configuration.
The scanner therefore uses:
TCP connectivity
+
Modbus protocol response
=
Modbus detection
This makes application-layer detection more meaningful than a simple port scan.
Some devices may not respond to the initial 0x04 request because of their register configuration or supported function codes.
The scanner therefore attempts a second request if the first one fails:
modbus_read_bits(ctx, 0, 1, bits);
This uses Function Code:
0x01 - Read Coils
A representative request is:
00 02 00 00 00 06 01 01 00 00 00 01
Breakdown:
00 02 Transaction Identifier
00 00 Protocol Identifier
00 06 Length
01 Unit Identifier
01 Function Code
00 00 Starting Address
00 01 Quantity
The scanner considers the target detected when either Modbus operation receives a successful response.
Target IP
|
v
TCP connection
port 502
|
+------+------+
| |
Failed Connected
| |
v v
Ignore Function 0x04
|
+------+------+
| |
Valid Failed
| |
v v
MODBUS FOUND Function 0x01
|
+------+------+
| |
Valid Failed
| |
v v
MODBUS FOUND No detection
The scanner uses libmodbus to construct and parse Modbus/TCP packets rather than manually constructing protocol frames.
The primary operation is:
modbus_read_input_registers(ctx, 0, 1, ®);
If this fails:
modbus_read_bits(ctx, 0, 1, bits);
The connection is then closed and the libmodbus context is freed.
This keeps the protocol handling inside the Modbus library while the scanner handles:
The scanner uses short connection and response timeouts:
#define TIMEOUT_SEC 2
This prevents a single unreachable or unresponsive host from blocking a worker for an excessive amount of time.
Industrial networks can contain devices with relatively slow responses, so timeout values may need to be adjusted depending on the environment.
Targets are divided between multiple worker threads.
For example:
Thread 1 → targets 1–64
Thread 2 → targets 65–128
Thread 3 → targets 129–192
Thread 4 → targets 193–254
Each worker independently attempts Modbus/TCP detection.
This allows multiple hosts to be tested concurrently rather than waiting for each target sequentially.
A positive result means that the target successfully responded to a Modbus request understood by the scanner.
It does not necessarily identify:
Those are separate fingerprinting or assessment tasks.
The scanner is primarily a Modbus/TCP service detection tool.
The detection method is intentionally conservative.
A device may be Modbus-capable but fail detection if:
Therefore:
No response ≠ Definitively not Modbus
It means the scanner could not obtain a successful response using the probes it attempted.
Install the required dependencies and compile with:
make
Or directly:
gcc -Wall -Wextra -O2 -o modbus modbus.c -lmodbus -lpthread
Run:
./modbus -i 192.168.1.0 -s /24 -t 10 -o results.txt
Example:
[+] Modbus detected: 192.168.1.20:502
[+] Modbus detected: 192.168.1.42:502
[+] Scanned: 254 | Found: 2 Modbus
[+] Results saved to: results.txt
The scanner detects Modbus/TCP services by performing actual protocol interaction rather than relying exclusively on TCP port detection.
The detection process is:
Connect to TCP/502
↓
Send Modbus Function Code 0x04
↓
Receive valid Modbus response?
↓
YES → Modbus detected
|
NO
↓
Send Modbus Function Code 0x01
↓
Receive valid Modbus response?
↓
YES → Modbus detected
|
NO
↓
No detection
The key principle is simple:
Detect the protocol, not just the port.