502/tcp - Pentesting Modbus Protocol
Basic Information
Modicon introduced Modbus in 1979 for communication among industrial devices. Current specifications use client/server terminology, although many tools and deployed serial systems still say master/slave.[3]
Default Modbus/TCP port: 502. The separate Modbus Security profile wraps Modbus in TLS with X.509 authentication and uses port 802; do not assume every Modbus deployment is necessarily plaintext.[3][4]
PORT STATE SERVICE
502/tcp open modbus
Modbus TCP carries a seven-byte MBAP header followed by the Modbus PDU:[3]
- Transaction ID: matches requests and responses
- Protocol ID: normally
0x0000 - Length: remaining bytes in the frame
- Unit ID: important when a Modbus/TCP gateway forwards traffic to serial/RTU devices
- Function Code + Data: the actual read/write/diagnostic operation
The Unit ID is frequently ignored by native Modbus/TCP devices, but it becomes critical when the target is a TCP-to-RTU gateway. In that case, you usually need to enumerate multiple unit/slave IDs to reach the device behind the bridge.
Legacy Modbus/TCP traffic on port 502 is typically plaintext and unauthenticated, so passive captures often reveal:
- The in-use unit/slave IDs
- Which function codes the process actually accepts
- The register map being polled by the HMI/SCADA server
- Whether writes are rare enough that replaying one stands out immediately
Enumeration
Automatic
nmap -sV --script modbus-discover -p 502 <IP>
nmap --script modbus-discover --script-args='modbus-discover.aggressive=true' -p 502 <IP>
msf> use auxiliary/scanner/scada/modbusdetect
msf> use auxiliary/scanner/scada/modbus_findunitid
modbus-discover is useful because it tries to enumerate legal slave IDs and extract device identification data such as vendor and firmware strings.[1]
Passive Recon
If you can sniff traffic, extract the parameters you need before sending anything intrusive:
tshark -r modbus.pcap -Y modbus \
-T fields -e ip.src -e ip.dst -e modbus.unit_id \
-e modbus.func_code -e modbus.reference_num -e modbus.word_cnt
Useful Wireshark display filters:
modbus
modbus.func_code == 3
modbus.func_code == 16
modbus.exception_code
Manual Enumeration with PyModbus
python3 -m pip install pymodbus
from pymodbus.client import ModbusTcpClient
host = "10.10.10.10"
unit = 1
client = ModbusTcpClient(host, port=502)
client.connect()
print(client.read_coils(address=0, count=16, device_id=unit))
print(client.read_holding_registers(address=0, count=10, device_id=unit))
print(client.read_device_information(device_id=unit))
client.close()
The current PyModbus client uses zero-based protocol addresses and names the unit argument device_id; older releases used slave or unit, so match the example to the installed version.[5]
Notes:
- Addressing is a common pitfall: many operators document holding register
400001, while libraries expect the zero-based offset (0). - When crossing a gateway, keep the same TCP endpoint and iterate the Unit ID.
- Some servers expose only a subset of function codes and return Modbus exception responses for unsupported ones.
Offensive Tooling
git clone https://github.com/TacticalGator/modbuster
cd modbuster && pipx install .
modbuster getfunctions <IP>
modbuster read -s 1 <IP> 400001 10
modbuster diag -s 1 <IP>
Other useful frameworks/tools:[6]
smod: function-code enumeration, UID brute force, fuzzing modulesscapy.contrib.modbus: packet crafting for unsupported or custom workflows
Interesting Function Codes
The following standardized function codes and the 0x2B/0x0E device-identification MEI are defined in the Modbus application protocol specification.[3]
Read / Recon
0x01/0x02: read coils / discrete inputs0x03/0x04: read holding / input registers0x11: report server ID0x2B/0x0E: Read Device Identification (vendor, product code, revision, optional objects)0x08: diagnostics, especially interesting on serial devices and some gateways
Write / Impact
These are the first function codes to validate carefully during authorized testing because they can directly alter process state:
0x05: write single coil0x06: write single holding register0x0F: write multiple coils0x10: write multiple holding registers0x16: mask write register0x17: read/write multiple registers
Less Common but Worth Testing
These are not universally implemented, but when present they are valuable because defenders often monitor them less:
0x14: read file record0x15: write file record0x18: read FIFO queue
A practical workflow is:
- Find the valid Unit ID.
- Verify read-only access with
0x01-0x04. - Pull device identification with
0x2B/0x0E. - Enumerate supported function codes.
- Only then validate whether write functions are accepted.
Scapy Packet Crafting
Scapy ships Modbus packet definitions for common read/write primitives and Read Device Identification.[6]
from scapy.contrib.modbus import ModbusADURequest, ModbusPDU2B0EReadDeviceIdentificationRequest
from scapy.all import sr1
pkt = ModbusADURequest(transId=1, unitId=1) / ModbusPDU2B0EReadDeviceIdentificationRequest()
resp = sr1(pkt, timeout=2)
if resp:
resp.show()
This is useful when you need to:
- Replay a packet seen in a PCAP with only minimal edits
- Test uncommon function codes not exposed cleanly by higher-level clients
- Validate how a target reacts to malformed length/function combinations during lab work
What Usually Leads to Impact
In real assessments, attackers rarely start with memory corruption. The usual path is:
- Sniff or enumerate the correct Unit ID and register ranges
- Learn the meaning of the values from traffic patterns or vendor docs
- Replay or slightly modify legitimate write requests
- Abuse weak process assumptions such as “this register is only written by the HMI”
Recent Modbus research and datasets keep emphasizing the same attacker behaviors: query flooding, false data injection, brute-force writes, replay, and malformed length/frame handling. Those are usually more realistic test cases than hunting for a single vendor-specific CVE.[2]
References
- [1] Nmap NSE: modbus-discover
- [2] MOSTO: A toolkit to facilitate security auditing of ICS devices using Modbus/TCP
- [3] Modbus Organization - Specifications and implementation guides
- [4] Modbus/TCP Security Protocol Specification
- [5] PyModbus client documentation
- [6] Scapy Modbus contrib-layer documentation