| Tested on | Kali GNU/Linux Rolling 2026.2 (kali-rolling) |
|---|---|
| Package | python3-pymodbus 3.14.0-1mbpoll 1.5.2-2can-utils 2023.03-1tshark 4.6.6-1 |
| Applies to | Kali Linux (concepts apply to any authorized OT/ICS engagement) |
| Lab environment | Pentest lab |
| Cert prep | CompTIA PenTest+ PT0-003 — specialized systems (OT/ICS) |
| Privilege | sudo for modprobe / vcan and packet capture; normal user for Modbus client tools |
| Scope | Why OT testing differs from IT, ICS architecture, common industrial protocols overview, Modbus TCP framing and address conventions, Modbus Security (TLS/port 802), local Modbus read/write and repeated-write risk, tshark Modbus analysis, classic CAN vs CAN FD without implying CAN FD authentication, virtual CAN labs, OT safety rules and controls. Not RFID/NFC/Bluetooth labs and not production OT exploitation. |
In this article we will practice OT/ICS security testing concepts on Kali with local Modbus TCP and virtual CAN simulators only.
Operational technology (OT) security testing uses a different risk model from ordinary IT penetration testing because actions can affect physical processes, availability, and safety. You get plain-English ICS components, how Modbus TCP differs from Modbus Security, and hands-on register and frame demos that never touch production equipment.
Why OT security testing differs from IT pentesting
IT engagements often focus on confidentiality and integrity first. OT security decisions must account for availability, reliability, deterministic operation, and process safety in addition to confidentiality and integrity. A wrong write to a holding register can change a setpoint, trip a drive, or put operators into an unexpected state.
Expect these differences up front:
- Impact can be physical or process-related, not only data leakage
- Maintenance windows and operator coordination matter more than “quiet hours”
- Legacy protocols may lack cryptographic authentication or integrity protection
- Passive observation and simulation often come before active writes
- Stop conditions must be explicit (who can halt the test, and how)
If your only experience is Kali against DVWA or Metasploitable, do not treat a Modbus TCP port like a vulnerable web app. Same toolkit skills, different risk model.
OT/ICS architecture
Industrial control systems stack sensors and actuators under controllers, then expose operators and engineers through HMIs and engineering tools. Names vary by vendor, but the roles stay familiar:
| Piece | Role |
|---|---|
| Sensor / actuator | Measures or moves the process (temperature, valve, motor) |
| PLC | Runs control logic; often talks industrial protocols on the plant network |
| HMI | Operator screen for monitoring and setpoints |
| SCADA | Supervisory view across sites or process areas |
| Engineering workstation | Where logic is written, downloaded, and sometimes left with powerful access |
A simplified mental model:
Sensor/Actuator ──► PLC ──► Plant network (e.g. Modbus TCP)
│
├──► HMI / SCADA
└──► Engineering workstationAttack surface discussions usually start with who can reach the PLC protocol port, who can download logic, and whether field buses (CAN, serial Modbus) are segmented from corporate IT.
Common OT/ICS protocols
Modbus is common in labs and exams, but it is only one industrial protocol family. You do not need a lab for every row here; treat the table as orientation when you inventory a plant network.
| Protocol | Typical context |
|---|---|
| Modbus TCP | PLC/HMI industrial control over Ethernet |
| Modbus RTU | Serial Modbus on RS-485 and similar links |
| EtherNet/IP | Common industrial automation networks |
| DNP3 | Utilities and many SCADA systems |
| OPC UA | Industrial data interoperability |
| PROFINET | Industrial Ethernet automation |
| CAN | Vehicles, machines, and embedded control buses |
This guide drills into Modbus TCP and classic CAN with safe simulators. Other protocols stay awareness-level unless a later engagement needs them.
Modbus TCP security basics
Classic Modbus TCP is a request/response protocol on TCP port 502 in most deployments. A client (often an HMI, SCADA poller, or engineering tool) issues requests; a server (often a PLC or gateway) responds.
Core vocabulary:
- Client / server: the client issues requests; the server exposes coils, discrete inputs, input registers, and holding registers defined by the device or application
- Function codes: for example
0x03(Read Holding Registers) and0x06(Write Single Register) - Framing: the Modbus PDU is the function code plus function-specific data; the Modbus TCP ADU is the MBAP header plus that PDU
Vendors decide what particular addresses mean. Register 1 is not universally a temperature or setpoint. In the simulator below, register 1 is only a fake process variable for demonstration.
Classic Modbus TCP provides no built-in cryptographic authentication, confidentiality, or message-integrity protection. TCP provides reliable transport and error detection, but it does not authenticate the Modbus sender or protect against deliberate modification. If you can reach the port and craft a valid PDU, many devices still accept the write.
Modbus addressing conventions
Addressing routinely confuses readers because protocol addresses may be zero-based, human documentation often shows one-based or 4xxxx holding-register numbers, and tools expose offsets differently.
mbpoll uses one-based references by default; -0 switches to zero-based PDU addressing. Device manuals may document addresses as offsets or as 4xxxx holding-register numbers, so always confirm the vendor’s addressing convention before interpreting a register.
Modbus TCP vs Modbus Security
Traditional Modbus TCP and Modbus Security are not equivalent.
| Classic Modbus TCP | Modbus Security | |
|---|---|---|
| Typical port | 502 | 802 (IANA) |
| Transport | Plain TCP | TLS protecting Modbus application traffic |
| Identity | Often none | Mutual X.509 certificates |
| Authorization | Typically absent or implementation-specific | Role-based authorization information carried through an X.509v3 certificate extension |
| Application framing | MBAP header + Modbus PDU | Modbus application traffic protected by TLS |
| PDU | Function code + function data | Same Modbus PDU semantics |
The Modbus Organization’s Modbus/TCP Security specification wraps familiar Modbus application data in TLS and expects mutual certificate authentication. Finding port 502 open does not mean port 802 (or vendor TLS variants) is configured. Finding Modbus Security does not mean every legacy client on 502 disappeared. Devices can expose both.
Build a safe Modbus simulator on Kali
Stay on a local simulator. Do not point these commands at a plant PLC.
Install the packages used below if they are missing. On Kali, use apt (or apt-get) with sudo:
sudo apt-get install -y python3-pymodbus mbpoll can-utils tsharkConfirm the versions you care about with dpkg:
dpkg -l python3-pymodbus mbpoll can-utils tshark | awk '/^ii/{printf "%-22s %s\n",$2,$3}'can-utils 2023.03-1
mbpoll 1.5.2-2
python3-pymodbus 3.14.0-1
tshark 4.6.6-1Those packages cover the Modbus server library, a Modbus master CLI, virtual CAN tools, and packet analysis.
Create a working directory:
mkdir -p ~/ot-ics-labmkdir -p exits quietly when the path already exists. Write a minimal holding-register server that listens on loopback port 5020 (non-privileged) with register 1 initialized to 25:
cat > ~/ot-ics-lab/modbus_sim.py << 'EOF'
#!/usr/bin/env python3
from pymodbus.datastore import (
ModbusSequentialDataBlock,
ModbusServerContext,
ModbusDeviceContext,
)
from pymodbus.server import StartTcpServer
store = ModbusDeviceContext(
hr=ModbusSequentialDataBlock(1, [25] + [0] * 99),
ir=ModbusSequentialDataBlock(1, [0] * 100),
co=ModbusSequentialDataBlock(1, [0] * 100),
di=ModbusSequentialDataBlock(1, [0] * 100),
)
context = ModbusServerContext(devices=store, single=True)
print("Listening Modbus TCP on 127.0.0.1:5020 (holding reg 1 = 25)", flush=True)
StartTcpServer(context=context, address=("127.0.0.1", 5020))
EOFcat writes the file and exits with no output when it succeeds. Start the simulator in a second terminal (or background it):
python3 ~/ot-ics-lab/modbus_sim.pyListening Modbus TCP on 127.0.0.1:5020 (holding reg 1 = 25)Leave that process running for the read, write, and capture steps below.
Read and write Modbus registers
Treat holding register 1 as a fake process variable for this lab only. Read it with mbpoll as unit ID 1 on port 5020 (one-based reference, the mbpoll default):
mbpoll -a 1 -r 1 -c 1 -t 4 -p 5020 -1 127.0.0.1Protocol configuration: ModBus TCP
Slave configuration...: address = [1]
start reference = 1, count = 1
Communication.........: 127.0.0.1, port 5020, t/o 1.00 s, poll rate 1000 ms
Data type.............: 16-bit register, output (holding) register table
-- Polling slave 1...
[1]: 25The [1]: 25 line is the current fake process variable. Write a new value (42) with the same tool:
mbpoll -a 1 -r 1 -t 4 -p 5020 -1 127.0.0.1 42Protocol configuration: ModBus TCP
Slave configuration...: address = [1]
start reference = 1, count = 1
Communication.........: 127.0.0.1, port 5020, t/o 1.00 s, poll rate 1000 ms
Data type.............: 16-bit register, output (holding) register table
Written 1 references.Read again to confirm the value changed:
mbpoll -a 1 -r 1 -c 1 -t 4 -p 5020 -1 127.0.0.1-- Polling slave 1...
[1]: 42A reachable classic Modbus endpoint can accept a write that changes a value the process logic treats as truth. On a real controller, that value might drive a valve or interlock, which is why production testing needs owner coordination and a defined process state.
Repeated Modbus writes and replay risk
Sending the same mbpoll command again is not a packet-replay attack; it simply demonstrates that classic Modbus provides no application-level freshness token preventing the same operation from being requested again. A true replay test would capture and retransmit a previously observed transaction.
Run the same write once more against the simulator:
mbpoll -a 1 -r 1 -t 4 -p 5020 -1 127.0.0.1 42Written 1 references.The server accepts the duplicate request. In a plant capture scenario, an attacker who recorded a legitimate “open valve” or “raise setpoint” PDU could try to re-inject that transaction later. Defenses that matter here are authentication, integrity, sequence or session binding where the product supports them, and network controls that keep untrusted hosts off the control VLAN.
Analyze Modbus traffic with tshark
Prefer analysis over blind writes. With the simulator still listening, move into the lab directory so the pcap lands next to your simulator script:
cd ~/ot-ics-labStart a loopback capture in that directory (5020 is the lab port, not the default 502):
sudo tshark -i lo -f 'tcp port 5020' -w modbus-lab.pcaptshark keeps running until you stop it. In another terminal, generate a read while the capture is active:
mbpoll -a 1 -r 1 -c 1 -t 4 -p 5020 -1 127.0.0.1Then generate a write so the pcap includes function 0x06 as well:
mbpoll -a 1 -r 1 -t 4 -p 5020 -1 127.0.0.1 42Return to the capture terminal and stop tshark with Ctrl+C. Force the Modbus/TCP dissector onto port 5020 and filter Modbus frames:
tshark -r ~/ot-ics-lab/modbus-lab.pcap -d tcp.port==5020,mbtcp -Y mbtcpTrimmed sample from this lab’s capture (0x03 = Read Holding Registers, 0x06 = Write Single Register):
4 0.020389 127.0.0.1 → 127.0.0.1 Modbus/TCP 78 unknown: Trans: 1; Unit: 1, Func: 3: Read Holding Registers. Unable to classify as query or response.
6 0.021866 127.0.0.1 → 127.0.0.1 Modbus/TCP 77 unknown: Trans: 1; Unit: 1, Func: 3: Read Holding Registers. Unable to classify as query or response.
14 0.046729 127.0.0.1 → 127.0.0.1 Modbus/TCP 78 unknown: Trans: 1; Unit: 1, Func: 6: Write Single Register. Unable to classify as query or response.
16 0.047474 127.0.0.1 → 127.0.0.1 Modbus/TCP 78 unknown: Trans: 1; Unit: 1, Func: 6: Write Single Register. Unable to classify as query or response.The “Unable to classify as query or response” note is common on loopback captures; the function codes still teach the request shape. For GUI capture habits, use the Wireshark and tcpdump lesson. For building and dissecting packets in Python, keep Scapy practice in Python for pentesters and decode Modbus ADUs only against lab traffic.
Example MBAP + PDU hex from a read-holding request in this lab (func=0x03):
000100000006010300000001Broken down: transaction 0001, protocol 0000, length 0006, unit 01, function 03, start 0000, quantity 0001. That is MBAP plus PDU, not “PDU equals MBAP.” Analysis first; writes second, and only on authorized targets.
Stop the Modbus simulator with Ctrl+C in its terminal when you finish the Modbus sections.
CAN security basics
Controller Area Network (CAN) appears in vehicles and some industrial or machine equipment. Frames carry an identifier and a data payload. The bus is fundamentally a broadcast medium: nodes that care about an ID process the frame; others ignore it.
Classical CAN data frames carry up to 8 data bytes. CAN FD extends that to as much as 64 bytes and can use a faster data phase. Neither feature inherently authenticates the transmitter.
Classic CAN does not authenticate the sender at the protocol level. CAN FD increases payload capacity and supports faster data phases, but it does not by itself add sender authentication. Security controls must come from higher-layer protocols, secure gateways, message-authentication schemes, or application-specific protections.
Test CAN frames with vcan
Load the virtual CAN module and bring up vcan0. These steps need sudo once per boot:
sudo modprobe vcanmodprobe prints nothing when the module loads. Create the virtual interface next (skip this line if vcan0 already exists):
sudo ip link add dev vcan0 type vcanBring the interface up so cansend and candump can use it:
sudo ip link set up vcan0Confirm the interface is present:
ip -brief link show vcan0vcan0 UNKNOWN <NOARP,UP,LOWER_UP>In one terminal, dump frames with timestamps:
candump -td vcan0In another terminal, send one frame with ID 0x123 and a four-byte payload:
cansend vcan0 123#DEADBEEFcansend exits silently on success. Send the identical frame again to model the effect of retransmitting a previously observed frame:
cansend vcan0 123#DEADBEEFBecause you manually construct the second frame, this is a replay concept demonstration rather than a packet-capture and reinjection workflow. Stop candump with Ctrl+C after the second send.
Sample candump output:
(000.000000) vcan0 123 [4] DE AD BE EF
(000.003857) vcan0 123 [4] DE AD BE EFIdentical ID and payload appear twice. That illustrates broadcast delivery and why integrity or gateway filtering matters before you trust CAN data in a safety function.
OT pentest safety and rules of engagement
Carry these into every ICS-related RoE discussion:
- No uncontrolled production testing
- Agree the process state before any write or download
- Coordinate with owner/operators (and safety officers when applicable)
- Document stop conditions and who can call them
- Prefer passive monitoring and simulators before active writes
- Separate IT jump hosts from control networks unless the scope says otherwise
Authorized OT work is not limited to a test PLC. Many legitimate engagements are passive network analysis, architecture or configuration review, authenticated asset review, engineering workstation review, or tightly controlled network testing. If safe active testing cannot be supported, restrict the engagement to approved passive observation, configuration/architecture review, or simulator/test-system validation rather than improvising writes against production controllers.
OT security controls
Protocol-specific controls still sit on top of effective basics:
| Area | Practical controls |
|---|---|
| Network | Segment OT from IT; firewall Modbus/CAN gateways; deny default any-any |
| Protocol | Prefer Modbus Security (TLS/802) or vendor TLS where supported; restrict unused function codes where the device or industrial firewall supports it |
| CAN | Gateways, filtering, application-layer auth where available; physical access control |
| Operations | Asset inventory, anomaly monitoring, change control for engineering workstations |
| Physical | Cabinet locks, port blockers, controlled access to plant floor gear |
Integrity and authentication matter most where classic protocols never had them. Segmentation buys time when legacy devices cannot be upgraded quickly.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
mbpoll: Connection refused |
Simulator not listening or wrong port | Start modbus_sim.py; confirm -p 5020 and 127.0.0.1 |
pymodbus import fails in a venv |
Distro package not on that interpreter | Use /usr/bin/python3 or install python3-pymodbus for that environment |
| Wrong register value vs manual | Addressing convention mismatch | Confirm vendor docs; try mbpoll -0 for zero-based PDU addressing |
modbus-lab.pcap missing |
Capture never started or wrong directory | Run sudo tshark … -w modbus-lab.pcap before mbpoll, then stop with Ctrl+C |
ip link add … vcan fails |
Interface already exists | Skip add; run sudo ip link set up vcan0 |
candump silent |
Wrong iface or no frames | Confirm vcan0 is UP; send with cansend on the same name |
| tshark shows raw TCP only | Port not mapped to Modbus dissector | Add -d tcp.port==5020,mbtcp (or your lab port) |
Summary
OT and ICS testing is not “IT pentest with weird ports.” Availability, reliability, deterministic behavior, and process safety reshape scope, timing, and which techniques you ever run live. You practiced what is safe on Kali: a local Modbus register change, a repeated write that shows missing freshness (not a capture/replay lab), Modbus traffic analysis with tshark, and identical frames on virtual CAN.
The Modbus Security distinction matters for reports: TLS and certificates on port 802 are not the same as plaintext 502, and PDU is function code plus data while the ADU adds the MBAP header. CAN FD grows payload and speed; it does not authenticate the sender by itself. Write OT stop conditions into the RoE before you touch any real controller, and keep active writes off production unless the engagement explicitly supports them.
References
- Modbus Specifications (Modbus Organization) — includes Modbus Security protocol materials
- MODBUS/TCP Security protocol PDF — TLS, mutual X.509, port 802
- pymodbus documentation — Python Modbus client/server library
- Linux SocketCAN documentation — CAN subsystem and virtual CAN
- can-utils on GitHub —
cansend,candump, and related tools - Wireshark Modbus protocol reference
- NIST SP 800-82 Rev. 3 Guide to OT Security
Frequently Asked Questions
Why is OT pentesting different from IT pentesting?
OT work must account for availability, reliability, deterministic operation, and process safety in addition to confidentiality and integrity. A failed or careless test can stop a line or create an unsafe process state, so scope, timing, and stop conditions are tighter than a typical IT lab scan.
Why is classic Modbus considered insecure?
Many deployments still use Modbus TCP without cryptographic authentication, confidentiality, or message-integrity protection. Any host that can reach the service and speak valid function codes may read or write registers. Modbus Security adds TLS and certificates on port 802; that is a different protocol profile, not a rename of port 502.
What is the difference between a Modbus PDU and an ADU?
The PDU is the function code plus function-specific data. On Modbus TCP, the ADU is the MBAP header plus that PDU. Modbus Security protects the Modbus application traffic with TLS; it does not redefine the PDU fields.
Does sending the same mbpoll write count as a replay attack?
No. Running the same client command again shows that classic Modbus has no application-level freshness token for that operation. A true replay test captures and retransmits a previously observed network transaction.
Does CAN FD authenticate CAN frames?
No. CAN FD increases payload size (up to 64 data bytes) and can use a faster data phase. Sender authentication still requires higher-layer protocols, secure gateways, message-authentication schemes, or application-specific controls.
Can I practice OT attacks on a production PLC?
Only with explicit written authorization, a defined process state, operator coordination, and stop conditions. Prefer simulators and dedicated test systems for active writes. This article’s commands target local software only.

