Snort IDS between PLC and HMI (Modbus)

Created Updated

This run-through is about how to use Snort to catch sketchy Modbus/TCP traffic in a small ICS lab.

Main goals:

  1. Catch non-Modbus junk on port 502
  2. Catch Write Single Register attempts (Function Code 6)
  3. Catch illegal values being written to a PLC register (traffic light logic)

Setup (quick)

  • Snort config checked in snort.lua
  • HOME_NET set to my lab subnet
  • EXTERNAL_NET set to any
  • Rules stored in local.rules

Rule syntax check:

sudo snort -T -c /etc/snort/snort.lua

Start Snort in passive mode:

sudo snort -c /etc/snort/snort.lua -i eth0 --daq pcap --daq-mode passive -l /var/log/snort/

Passive mode keeps things safe and observable while testing.


Part 1A - Detect non-Modbus traffic on TCP/502

Rule:

alert tcp any any -> any 502 (msg:"Non-Modbus traffic on port 502 - Invalid Protocol ID"; byte_test:2,!=,0,2; sid:1000001; rev:1;)
  • In Modbus/TCP, MBAP Protocol ID (bytes 2-3) should be 0x0000
  • If it is non-zero, that payload is not valid Modbus

Test packet (crafted bad Protocol ID):

echo -e "\x00\x01\x00\x01\x00\x06\x01\x03\x00\x00\x00\x0A" | nc -w1 10.211.55.6 502

Result:

  • Built-in Snort Modbus inspector alert fired
  • Custom rule sid:1000001 also fired

That is a solid confirmation the rule is doing its job.


Part 1B - Detect Write Single Register (Function Code 6)

Rule:

alert tcp any any -> any 502 (msg:"Modbus Write Single Register - Function Code 6"; byte_test:1,=,6,7; sid:1000002; rev:1;)
  • Byte 7 of the Modbus frame is the function code
  • Value 6 means Write Single Register

Test packet:

echo -e "\x00\x01\x00\x00\x00\x06\x01\x06\x00\x01\x00\xFF" | nc -w1 10.211.55.6 502

Result:

  • sid:1000002 fired correctly

Part 1C - Same detection, cleaner rule (Modbus inspector)

Instead of raw byte offsets, I rewrote Part 1B using Snort's Modbus inspector keyword:

alert tcp any any -> any 502 (msg:"Modbus Write Single Register - Function Code 6"; modbus_func:6; sid:1000002; rev:1;)

Why this is nicer:

  • Reads like intent, not byte math
  • Easier to maintain
  • Less likely to confuse future-you during tuning

Part 2 - Detect illegal values in a PLC register

Now the practical OT defense piece: detect writes that break process logic.

For a traffic light register with legal values 0-3, alert on anything greater than 3.

Final rule (targeting register 23 / %QW23):

alert tcp any any -> any 502 (msg:"Illegal value written to Modbus register 23 - traffic light"; modbus_func:6; byte_test:2,=,23,8; byte_test:2,>,3,10; sid:1000003; rev:1;)

What this checks:

  • modbus_func:6 -> write single register only
  • byte_test:2,=,23,8 -> target register address 23
  • byte_test:2,>,3,10 -> written value must be greater than 3 to alert

Validation flow:

  1. Send legal value (for example 2) -> no alert
  2. Send illegal value (for example 99) -> alert fires

Result:

  • Snort fired only on illegal write activity
  • No alert on legal writes
  • Good signal quality for this lab scenario

Attack simulation note

I used a quick pymodbus write to push an illegal value into the PLC register.

That simulates a real ICS attack pattern: attacker injects out-of-range control values to mess with physical behavior.

Snort caught it immediately with sid:1000003.


Final ruleset

alert tcp any any -> any 502 (msg:"Non-Modbus traffic on port 502 - Invalid Protocol ID"; byte_test:2,!=,0,2; sid:1000001; rev:1;)
alert tcp any any -> any 502 (msg:"Modbus Write Single Register - Function Code 6"; modbus_func:6; sid:1000002; rev:1;)
alert tcp any any -> any 502 (msg:"Illegal value written to Modbus register 23 - traffic light"; modbus_func:6; byte_test:2,=,23,8; byte_test:2,>,3,10; sid:1000003; rev:1;)

Takeaways

  • Start passive and verify visibility first
  • Build rules in small chunks and validate each one
  • Prefer protocol-aware keywords when possible (modbus_func)
  • Write policy rules tied to process constraints, not just signatures
  • Always test both positive and negative cases (alert + no-alert)

That is how you keep detections useful and avoid noisy IDS output in ICS labs.

Comments

No comments yet.

Sign in to comment, reply, vote, or delete your own comments. Anyone can read comments.