Modbus
December 9, 2025 · View on GitHub
Modbus Protocol Threat Emulation Tooling
Overview
The Modbus plugin provides adversary emulation abilities specific to the Modbus protocol. The specification for the Modbus protocol is free and available to download from the Modbus organization at modbus.org The following table outlines MITRE ATT&CK for ICS Tactic coverage provided by the Modbus plugin.
| Collection | Discovery | Impair Process Control |
|---|---|---|
| Point & Tag Identification | Remote System Information Discovery | Brute Force I/O |
| Automated Collection | Modify Parameter |
Ability Overview Tables
The following tables list each plugin ability by their corresponding tactic. A heatmap of plugin abilities is available to view here.
Collection Abilities
| Name | Tactic | Technique | Technique ID |
|---|---|---|---|
| Modbus - Read Coils | Collection | Point & Tag Identification | T0861 |
| Modbus - Read Discrete Inputs | Collection | Point & Tag Identification | T0861 |
| Modbus - Read Holding Registers | Collection | Point & Tag Identification | T0861 |
| Modbus - Read Input Registers | Collection | Point & Tag Identification | T0861 |
| Modbus - Scan Device | Collection | Automated Collection | T0802 |
Discovery Abilities
| Name | Tactic | Technique | Technique ID |
|---|---|---|---|
| Modbus - Read Device Information | Discovery | Remote System Information Discovery | T0888 |
Impair Process Control Abilities
| Name | Tactic | Technique | Technique ID |
|---|---|---|---|
| Modbus - Write Single Coil | Impair Process Control | Modify Parameter | T0836 |
| Modbus - Write Single Register | Impair Process Control | Modify Parameter | T0836 |
| Modbus - Write Multiple Coils | Impair Process Control | Brute Force I/O | T0806 |
| Modbus - Write Multiple Registers | Impair Process Control | Brute Force I/O | T0806 |
| Modbus - Fuzz Coils | Impair Process Control | Brute Force I/O | T0806 |
| Modbus - Fuzz Registers | Impair Process Control | Brute Force I/O | T0806 |
Architecture
This section describes the main components of the plugin and how they interface.
Block Diagram

The Modbus Plugin allows a user to execute several abilities once added to the Caldera instance. The abilities will be executed via the Caldera agent and corresponding payload. This is intended to target devices communicating via the Modbus protocol, likely over port 502.
Payloads
The Modbus Plugin includes one payload, compiled for three different host architectures:
modbus_cli.exe(Windows)modbus_cli(Linux)modbus_cli_darwin(Darwin / macOS ARM)
Compatibility
The payloads were compiled in the following environments:
| Linux | Windows | Darwin | |
|---|---|---|---|
| OS Version | Ubuntu 22.04 | Windows 2022 | macOS v14 |
| Python Version | 3.10 | 3.10 | 3.10 |
Libraries
The following libraries were used to build the Modbus payloads:
| Library | Version | License |
|---|---|---|
| pymodbus | 3.9.2 | BSD |
Usage
This section describes how to initially deploy and execute the abilities present within the Modbus plugin.
Deployment
-
Select Your Target System
Determine the system you want to communicate with via the Modbus protocol. This could be an industrial control system, a programmable logic controller (PLC), or any other Modbus-compatible device. -
Choose a Host for the Caldera Agent
Identify a suitable machine to host the Caldera agent. This machine will act as the intermediary, sending Modbus messages to your target system. Ensure the host has network access to the target system and meets the deployment requirements. -
Deploy the Caldera Agent
Deploy the Caldera agent to the chosen host. Instructions and scripts to acheive this are found on the Caldera server GUI on the "Agents" page. -
Execute Modbus Plugin Abilities
Utilize the Modbus plugin's abilities to perform specific actions on the target system. Combine abilities such as reading registers and writing coils to achieve your desired outcome.
Reference the Caldera training plugin for a step-by-step tutorial on how to
deploy an agent and run abilities via an operation.
Modbus Sources and Facts
Caldera fact sources allow you to save information about your target environment to simplify executing abilities. This plugin comes with a sample fact source named "Modbus Sample Facts" that will have loaded with the plugin. Navigate to the Sources tab of the Caldera interface to view and modify these facts.
Abilities
Modbus - Read Coils
Modbus Function 1 (0x01): Read Coils
This function code is used to read from 1 to 2000 contiguous states of coils in a remote device.
Ability Command:
Windows (psh)
.\modbus_cli.exe #{modbus.server.ip} -p #{modbus.server.port} read_c #{modbus.read_coil.start} #{modbus.read_coil.count}
Linux (sh)
./modbus_cli #{modbus.server.ip} -p #{modbus.server.port} read_c #{modbus.read_coil.start} #{modbus.read_coil.count}
Darwin (sh)
./modbus_cli_darwin #{modbus.server.ip} -p #{modbus.server.port} read_c #{modbus.read_coil.start} #{modbus.read_coil.count}
Facts:
| Name | Description | Type | Choices |
|---|---|---|---|
modbus.server.ip | The target device IP address | string | |
modbus.server.port | The target device Modbus port | int | |
modbus.read_coil.start | The starting address to read from | int | 0-65535 |
modbus.read_coil.count | The number of items to read | int | 1-2000 |
Optional Flags:
| Flag | Description | Type | Default |
|---|---|---|---|
-d, --device | Device ID to be targeted [0-255] | int | 1 |
Modbus - Read Discrete Inputs
Modbus Function 2 (0x02): Read Discrete Inputs
This function code is used to read from 1 to 2000 contiguous discrete inputs in a remote device.
Ability Command:
Windows (psh)
.\modbus_cli.exe #{modbus.server.ip} -p #{modbus.server.port} read_di #{modbus.read_discrete.start} #{modbus.read_discrete.count}
Linux (sh)
./modbus_cli #{modbus.server.ip} -p #{modbus.server.port} read_di #{modbus.read_discrete.start} #{modbus.read_discrete.count}
Darwin (sh)
./modbus_cli_darwin #{modbus.server.ip} -p #{modbus.server.port} read_di #{modbus.read_discrete.start} #{modbus.read_discrete.count}
Facts:
| Name | Description | Type | Choices |
|---|---|---|---|
modbus.server.ip | The target device IP address | string | |
modbus.server.port | The target device Modbus port | int | |
modbus.read_discrete.start | The starting address to read from | int | 0-65535 |
modbus.read_discrete.count | The number of items to read | int | 1-2000 |
Optional Flags:
| Flag | Description | Type | Default |
|---|---|---|---|
-d, --device | Device ID to be targeted [0-255] | int | 1 |
Modbus - Read Holding Registers
Modbus Function 3 (0x03): Read Holding Registers
This function code is used to read the contents of a contiguous block of holding registers in a remote device.
Ability Command:
Windows (psh)
.\modbus_cli.exe #{modbus.server.ip} -p #{modbus.server.port} read_hr #{modbus.read_holding.start} #{modbus.read_holding.count}
Linux (sh)
./modbus_cli #{modbus.server.ip} -p #{modbus.server.port} read_hr #{modbus.read_holding.start} #{modbus.read_holding.count}
Darwin (sh)
./modbus_cli_darwin #{modbus.server.ip} -p #{modbus.server.port} read_hr #{modbus.read_holding.start} #{modbus.read_holding.count}
Facts:
| Name | Description | Type | Choices |
|---|---|---|---|
modbus.server.ip | The target device IP address | string | |
modbus.server.port | The target device Modbus port | int | |
modbus.read_holding.start | The starting address to read from | int | 0-65535 |
modbus.read_holding.count | The number of items to read | int | 1-125 |
Optional Flags:
| Flag | Description | Type | Default |
|---|---|---|---|
-d, --device | Device ID to be targeted [0-255] | int | 1 |
Modbus - Read Input Registers
Modbus Function 4 (0x04): Read Input Registers
This function code is used to read from 1 to 125 contiguous input registers in a remote device.
Ability Command:
Windows (psh)
.\modbus_cli.exe #{modbus.server.ip} -p #{modbus.server.port} read_ir #{modbus.read_input.start} #{modbus.read_input.count}
Linux (sh)
./modbus_cli #{modbus.server.ip} -p #{modbus.server.port} read_ir #{modbus.read_input.start} #{modbus.read_input.count}
Darwin (sh)
./modbus_cli_darwin #{modbus.server.ip} -p #{modbus.server.port} read_ir #{modbus.read_input.start} #{modbus.read_input.count}
Facts:
| Name | Description | Type | Choices |
|---|---|---|---|
modbus.server.ip | The target device IP address | string | |
modbus.server.port | The target device Modbus port | int | |
modbus.read_input.start | The starting address to read from | int | 0-65535 |
modbus.read_input.count | The number of items to read | int | 1-125 |
Optional Flags:
| Flag | Description | Type | Default |
|---|---|---|---|
-d, --device | Device ID to be targeted [0-255] | int | 1 |
Modbus - Scan Device
This ability runs a sequence of Modbus read functions to automatically discover and read all available Modbus data (coils and registers) on a device starting from a specified address.
The included parser generates clean, non-redundant facts for each register type (count and start_address) for use with other Modbus abilities.
Ability Command:
Windows (psh)
.\modbus_cli.exe #{modbus.server.ip} -p #{modbus.server.port} scan
Linux (sh)
./modbus_cli #{modbus.server.ip} -p #{modbus.server.port} scan
Darwin (sh)
./modbus_cli_darwin #{modbus.server.ip} -p #{modbus.server.port} scan
Facts:
| Name | Description | Type | Choices |
|---|---|---|---|
modbus.server.ip | The target device IP address | string | |
modbus.server.port | The target device Modbus port | int |
Optional Flags:
| Flag | Description | Type | Default |
|---|---|---|---|
-d, --device | Device ID to be targeted [0-255] | int | 1 |
Modbus - Read Device Information
Modbus Function 43, MEI Type 14 (0x2B, MEI Type 0x0E): Read Device Identification
This function provides a mechanism for tunneling services requests, request type 14 is used to read the identification and additional information about the remote device.
Ability Command:
Windows (psh)
.\modbus_cli.exe #{modbus.server.ip} -p #{modbus.server.port} read_device_info --level #{modbus.read_device_info.level}
Linux (sh)
./modbus_cli #{modbus.server.ip} -p #{modbus.server.port} read_device_info --level #{modbus.read_device_info.level}
Darwin (sh)
./modbus_cli_darwin #{modbus.server.ip} -p #{modbus.server.port} read_device_info --level #{modbus.read_device_info.level}
Facts:
| Name | Description | Type | Choices |
|---|---|---|---|
modbus.server.ip | The target device IP address | string | |
modbus.server.port | The target device Modbus port | int | |
modbus.read_device_info.level | Level of device information to request | int | 1-3 |
Optional Flags:
| Flag | Description | Type | Default |
|---|---|---|---|
-d, --device | Device ID to be targeted [0-255] | int | 1 |
Modbus - Write Single Coil
Modbus Function 5 (0x05): Write Single Coil
This function code is used to write a single output to either ON or OFF in a remote device.
Ability Command:
Windows (psh)
.\modbus_cli.exe #{modbus.server.ip} -p #{modbus.server.port} write_c #{modbus.write_coil.start} #{modbus.write_coil.value}
Linux (sh)
./modbus_cli #{modbus.server.ip} -p #{modbus.server.port} write_c #{modbus.write_coil.start} #{modbus.write_coil.value}
Darwin (sh)
./modbus_cli_darwin #{modbus.server.ip} -p #{modbus.server.port} write_c #{modbus.write_coil.start} #{modbus.write_coil.value}
Facts:
| Name | Description | Type | Choices |
|---|---|---|---|
modbus.server.ip | The target device IP address | string | |
modbus.server.port | The target device Modbus port | int | |
modbus.write_coil.start | The starting address to write to | int | 0-65535 |
modbus.write_coil.value | The value to be written | str | ON,OFF |
Optional Flags:
| Flag | Description | Type | Default |
|---|---|---|---|
-d, --device | Device ID to be targeted [0-255] | int | 1 |
Modbus - Write Single Register
Modbus Function 6 (0x06): Write Single Register
This function code is used to write a single holding register in a remote device.
Ability Command:
Windows (psh)
.\modbus_cli.exe #{modbus.server.ip} -p #{modbus.server.port} write_r #{modbus.write_register.start} #{modbus.write_register.value}
Linux (sh)
./modbus_cli #{modbus.server.ip} -p #{modbus.server.port} write_r #{modbus.write_register.start} #{modbus.write_register.value}
Darwin (sh)
./modbus_cli_darwin #{modbus.server.ip} -p #{modbus.server.port} write_r #{modbus.write_register.start} #{modbus.write_register.value}
Facts:
| Name | Description | Type | Choices |
|---|---|---|---|
modbus.server.ip | The target device IP address | string | |
modbus.server.port | The target device Modbus port | int | |
modbus.write_register.start | The starting address to write to | int | 0-65535 |
modbus.write_register.value | The value to be written | int | 0-65535 |
Optional Flags:
| Flag | Description | Type | Default |
|---|---|---|---|
-d, --device | Device ID to be targeted [0-255] | int | 1 |
Modbus - Write Multiple Coils
Modbus Function 15 (0x0F): Write Multiple Coils
This function code is used to force each coil in a sequence of coils to either ON or OFF in a remote device.
Ability Command:
Windows (psh)
.\modbus_cli.exe #{modbus.server.ip} -p #{modbus.server.port} write_multi_c #{modbus.write_coil.start} #{modbus.write_coil.values}
Linux (sh)
./modbus_cli #{modbus.server.ip} -p #{modbus.server.port} write_multi_c #{modbus.write_coil.start} #{modbus.write_coil.values}
Darwin (sh)
./modbus_cli_darwin #{modbus.server.ip} -p #{modbus.server.port} write_multi_c #{modbus.write_coil.start} #{modbus.write_coil.values}
Facts:
| Name | Description | Type | Choices |
|---|---|---|---|
modbus.server.ip | The target device IP address | string | |
modbus.server.port | The target device Modbus port | int | |
modbus.write_coil.start | The starting address to write to | int | 0-65535 |
modbus.write_coil.values | The values to be written | comma separated list of str | ON,OFF |
Optional Flags:
| Flag | Description | Type | Default |
|---|---|---|---|
-d, --device | Device ID to be targeted [0-255] | int | 1 |
Modbus - Write Multiple Registers
Modbus Function 16 (0x10): Write Multiple Registers
This function code is used to write a block of contiguous registers (1 to 123 registers) in a remote device.
Ability Command:
Windows (psh)
.\modbus_cli.exe #{modbus.server.ip} -p #{modbus.server.port} write_multi_r #{modbus.write_register.start} #{modbus.write_register.values}
Linux (sh)
./modbus_cli #{modbus.server.ip} -p #{modbus.server.port} write_multi_r #{modbus.write_register.start} #{modbus.write_register.values}
Darwin (sh)
./modbus_cli_darwin #{modbus.server.ip} -p #{modbus.server.port} write_multi_r #{modbus.write_register.start} #{modbus.write_register.values}
Facts:
| Name | Description | Type | Choices |
|---|---|---|---|
modbus.server.ip | The target device IP address | string | |
modbus.server.port | The target device Modbus port | int | |
modbus.write_register.start | The starting address to write to | int | 0-65535 |
modbus.write_register.values | The values to be written | comma separated list of int | 0-65535 |
Optional Flags:
| Flag | Description | Type | Default |
|---|---|---|---|
-d, --device | Device ID to be targeted [0-255] | int | 1 |
Modbus - Fuzz Coils
Procedure
Modbus Function 5 (0x05) Write Single Coil
Writes random values to random coils over specified ranges.
Ability Command:
Windows (psh)
.\modbus_cli.exe #{modbus.server.ip} -p #{modbus.server.port} fuzz_c #{modbus.fuzz_coil.start} #{modbus.fuzz_coil.end} #{modbus.fuzz_coil.count} --wait #{modbus.fuzz_coil.wait}
Linux (sh)
./modbus_cli #{modbus.server.ip} -p #{modbus.server.port} fuzz_c #{modbus.fuzz_coil.start} #{modbus.fuzz_coil.end} #{modbus.fuzz_coil.count} --wait #{modbus.fuzz_coil.wait}
Darwin (sh)
./modbus_cli_darwin #{modbus.server.ip} -p #{modbus.server.port} fuzz_c #{modbus.fuzz_coil.start} #{modbus.fuzz_coil.end} #{modbus.fuzz_coil.count} --wait #{modbus.fuzz_coil.wait}
Facts:
| Name | Description | Type | Choices |
|---|---|---|---|
modbus.server.ip | The target device IP address | string | |
modbus.server.port | The target device Modbus port | int | |
modbus.fuzz_coil.start | The start address of the fuzzing range | int | 0-65535 |
modbus.fuzz_coil.end | The end address of the fuzzing range | int | 0-65535 |
modbus.fuzz_coil.count | The number of write operations to perform | int | 0-65535 |
modbus.fuzz_coil.wait | Seconds to wait between write operations | float |
Optional Flags:
| Flag | Description | Type | Default |
|---|---|---|---|
-d, --device | Device ID to be targeted [0-255] | int | 1 |
Modbus - Fuzz Registers
Procedure
Modbus Function 6 (0x06): Write Single Register
Writes random values to random registers over specified ranges.
Ability Command:
Windows (psh)
.\modbus_cli.exe #{modbus.server.ip} -p #{modbus.server.port} fuzz_r #{modbus.fuzz_register.start} #{modbus.fuzz_register.end} #{modbus.fuzz_register.count} --min #{modbus.fuzz_register.min} --max #{modbus.fuzz_register.max} --wait #{modbus.fuzz_register.wait}
Linux (sh)
./modbus_cli #{modbus.server.ip} -p #{modbus.server.port} fuzz_r #{modbus.fuzz_register.start} #{modbus.fuzz_register.end} #{modbus.fuzz_register.count} --min #{modbus.fuzz_register.min} --max #{modbus.fuzz_register.max} --wait #{modbus.fuzz_register.wait}
Darwin (sh)
./modbus_cli_darwin #{modbus.server.ip} -p #{modbus.server.port} fuzz_r #{modbus.fuzz_register.start} #{modbus.fuzz_register.end} #{modbus.fuzz_register.count} --min #{modbus.fuzz_register.min} --max #{modbus.fuzz_register.max} --wait #{modbus.fuzz_register.wait}
Facts:
| Name | Description | Type | Choices |
|---|---|---|---|
modbus.server.ip | The target device IP address | string | |
modbus.server.port | The target device Modbus port | int | |
modbus.fuzz_register.start | The start address of the fuzzing range | int | 0-65535 |
modbus.fuzz_register.end | The end address of the fuzzing range | int | 0-65535 |
modbus.fuzz_register.count | The number of write operations to perform | int | 0-65535 |
modbus.fuzz_register.min | Minimum register write value | int | 0-65535 |
modbus.fuzz_register.max | Maximum register write value | int | 0-65535 |
modbus.fuzz_register.wait | Seconds to wait between write operations | float |
Optional Flags:
| Flag | Description | Type | Default |
|---|---|---|---|
-d, --device | Device ID to be targeted [0-255] | int | 1 |
Source Code
The source code for the Modbus plugin can be found inside this plugin's src/ directory.
Copyright Notice
Modbus® is a registered trademark of SCHNEIDER ELECTRIC USA, INC. CORPORATION DELAWARE 1415 SOUTH ROSELLE ROAD PALATINE ILLINOIS 60067
This Caldera plugin is named "Modbus" as that is a short identifier of its purpose / scope. This plugin is not produced-by Schneider Electric or The Modbus Corporation.