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.

CollectionDiscoveryImpair Process Control
Point & Tag IdentificationRemote System Information DiscoveryBrute Force I/O
Automated CollectionModify 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

NameTacticTechniqueTechnique ID
Modbus - Read CoilsCollectionPoint & Tag IdentificationT0861
Modbus - Read Discrete InputsCollectionPoint & Tag IdentificationT0861
Modbus - Read Holding RegistersCollectionPoint & Tag IdentificationT0861
Modbus - Read Input RegistersCollectionPoint & Tag IdentificationT0861
Modbus - Scan DeviceCollectionAutomated CollectionT0802

Discovery Abilities

NameTacticTechniqueTechnique ID
Modbus - Read Device InformationDiscoveryRemote System Information DiscoveryT0888

Impair Process Control Abilities

NameTacticTechniqueTechnique ID
Modbus - Write Single CoilImpair Process ControlModify ParameterT0836
Modbus - Write Single RegisterImpair Process ControlModify ParameterT0836
Modbus - Write Multiple CoilsImpair Process ControlBrute Force I/OT0806
Modbus - Write Multiple RegistersImpair Process ControlBrute Force I/OT0806
Modbus - Fuzz CoilsImpair Process ControlBrute Force I/OT0806
Modbus - Fuzz RegistersImpair Process ControlBrute Force I/OT0806

Architecture

This section describes the main components of the plugin and how they interface.

Block Diagram

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:

LinuxWindowsDarwin
OS VersionUbuntu 22.04Windows 2022macOS v14
Python Version3.103.103.10

Libraries

The following libraries were used to build the Modbus payloads:

LibraryVersionLicense
pymodbus3.9.2BSD

Usage

This section describes how to initially deploy and execute the abilities present within the Modbus plugin.

Deployment

  1. 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.

  2. 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.

  3. 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.

  4. 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:

NameDescriptionTypeChoices
modbus.server.ipThe target device IP addressstring
modbus.server.portThe target device Modbus portint
modbus.read_coil.startThe starting address to read fromint0-65535
modbus.read_coil.countThe number of items to readint1-2000

Optional Flags:

FlagDescriptionTypeDefault
-d, --deviceDevice ID to be targeted [0-255]int1

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:

NameDescriptionTypeChoices
modbus.server.ipThe target device IP addressstring
modbus.server.portThe target device Modbus portint
modbus.read_discrete.startThe starting address to read fromint0-65535
modbus.read_discrete.countThe number of items to readint1-2000

Optional Flags:

FlagDescriptionTypeDefault
-d, --deviceDevice ID to be targeted [0-255]int1

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:

NameDescriptionTypeChoices
modbus.server.ipThe target device IP addressstring
modbus.server.portThe target device Modbus portint
modbus.read_holding.startThe starting address to read fromint0-65535
modbus.read_holding.countThe number of items to readint1-125

Optional Flags:

FlagDescriptionTypeDefault
-d, --deviceDevice ID to be targeted [0-255]int1

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:

NameDescriptionTypeChoices
modbus.server.ipThe target device IP addressstring
modbus.server.portThe target device Modbus portint
modbus.read_input.startThe starting address to read fromint0-65535
modbus.read_input.countThe number of items to readint1-125

Optional Flags:

FlagDescriptionTypeDefault
-d, --deviceDevice ID to be targeted [0-255]int1

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:

NameDescriptionTypeChoices
modbus.server.ipThe target device IP addressstring
modbus.server.portThe target device Modbus portint

Optional Flags:

FlagDescriptionTypeDefault
-d, --deviceDevice ID to be targeted [0-255]int1

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:

NameDescriptionTypeChoices
modbus.server.ipThe target device IP addressstring
modbus.server.portThe target device Modbus portint
modbus.read_device_info.levelLevel of device information to requestint1-3

Optional Flags:

FlagDescriptionTypeDefault
-d, --deviceDevice ID to be targeted [0-255]int1

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:

NameDescriptionTypeChoices
modbus.server.ipThe target device IP addressstring
modbus.server.portThe target device Modbus portint
modbus.write_coil.startThe starting address to write toint0-65535
modbus.write_coil.valueThe value to be writtenstrON,OFF

Optional Flags:

FlagDescriptionTypeDefault
-d, --deviceDevice ID to be targeted [0-255]int1

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:

NameDescriptionTypeChoices
modbus.server.ipThe target device IP addressstring
modbus.server.portThe target device Modbus portint
modbus.write_register.startThe starting address to write toint0-65535
modbus.write_register.valueThe value to be writtenint0-65535

Optional Flags:

FlagDescriptionTypeDefault
-d, --deviceDevice ID to be targeted [0-255]int1

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:

NameDescriptionTypeChoices
modbus.server.ipThe target device IP addressstring
modbus.server.portThe target device Modbus portint
modbus.write_coil.startThe starting address to write toint0-65535
modbus.write_coil.valuesThe values to be writtencomma separated list of strON,OFF

Optional Flags:

FlagDescriptionTypeDefault
-d, --deviceDevice ID to be targeted [0-255]int1

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:

NameDescriptionTypeChoices
modbus.server.ipThe target device IP addressstring
modbus.server.portThe target device Modbus portint
modbus.write_register.startThe starting address to write toint0-65535
modbus.write_register.valuesThe values to be writtencomma separated list of int0-65535

Optional Flags:

FlagDescriptionTypeDefault
-d, --deviceDevice ID to be targeted [0-255]int1

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:

NameDescriptionTypeChoices
modbus.server.ipThe target device IP addressstring
modbus.server.portThe target device Modbus portint
modbus.fuzz_coil.startThe start address of the fuzzing rangeint0-65535
modbus.fuzz_coil.endThe end address of the fuzzing rangeint0-65535
modbus.fuzz_coil.countThe number of write operations to performint0-65535
modbus.fuzz_coil.waitSeconds to wait between write operationsfloat

Optional Flags:

FlagDescriptionTypeDefault
-d, --deviceDevice ID to be targeted [0-255]int1

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:

NameDescriptionTypeChoices
modbus.server.ipThe target device IP addressstring
modbus.server.portThe target device Modbus portint
modbus.fuzz_register.startThe start address of the fuzzing rangeint0-65535
modbus.fuzz_register.endThe end address of the fuzzing rangeint0-65535
modbus.fuzz_register.countThe number of write operations to performint0-65535
modbus.fuzz_register.minMinimum register write valueint0-65535
modbus.fuzz_register.maxMaximum register write valueint0-65535
modbus.fuzz_register.waitSeconds to wait between write operationsfloat

Optional Flags:

FlagDescriptionTypeDefault
-d, --deviceDevice ID to be targeted [0-255]int1

Source Code

The source code for the Modbus plugin can be found inside this plugin's src/ directory.

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.