Last reviewed

PLC/RTU Attacks

Exploitation

Programmable Logic Controllers (PLCs) and Remote Terminal Units (RTUs) are the workhorses of industrial automation. Compromising these devices can lead to direct physical impact.

Physical Safety Risk

PLC attacks can cause physical damage, safety incidents, or loss of life. Only perform these tests in isolated lab environments or with extensive safety controls and authorization.

Common PLC Vulnerabilities

Authentication Issues

  • • No authentication on protocols
  • • Default/weak passwords
  • • Hardcoded credentials
  • • Password bypass vulnerabilities

Logic Vulnerabilities

  • • Unauthenticated program upload
  • • Logic download without verification
  • • Runtime value manipulation
  • • Stop/start control

Siemens S7 Attacks

S7 Enumeration

bash
# Nmap S7 info script
nmap -p 102 --script s7-info 192.168.1.100

# Using snap7 library
pip install python-snap7

python3 << 'EOF'
import snap7

client = snap7.client.Client()
client.connect('192.168.1.100', 0, 1)  # IP, rack, slot

# Get PLC info
info = client.get_cpu_info()
print(f"Module Type: {info.ModuleTypeName}")
print(f"Serial: {info.SerialNumber}")
print(f"Module Name: {info.ModuleName}")

# Get CPU state
state = client.get_cpu_state()
print(f"CPU State: {state}")  # Run, Stop, etc.

client.disconnect()
EOF
# Nmap S7 info script
nmap -p 102 --script s7-info 192.168.1.100

# Using snap7 library
pip install python-snap7

python3 << 'EOF'
import snap7

client = snap7.client.Client()
client.connect('192.168.1.100', 0, 1)  # IP, rack, slot

# Get PLC info
info = client.get_cpu_info()
print(f"Module Type: {info.ModuleTypeName}")
print(f"Serial: {info.SerialNumber}")
print(f"Module Name: {info.ModuleName}")

# Get CPU state
state = client.get_cpu_state()
print(f"CPU State: {state}")  # Run, Stop, etc.

client.disconnect()
EOF

S7 Authentication Bypass

bash
# S7 PLCs can have password protection
# Multiple levels: Read, Write, Read/Write

# Check if password is set
# Try connecting and performing operations

# Password bypass techniques:
# 1. S7-300/400 with older firmware have known bypasses
# 2. Some PLCs accept empty password
# 3. Password stored in project file (extract and crack)

# Using s7-brute-offline
# Extract password hash from captured traffic or project file
# Crack offline

# Replay attack
# Capture legitimate authentication
# Replay to gain access
# S7 PLCs can have password protection
# Multiple levels: Read, Write, Read/Write

# Check if password is set
# Try connecting and performing operations

# Password bypass techniques:
# 1. S7-300/400 with older firmware have known bypasses
# 2. Some PLCs accept empty password
# 3. Password stored in project file (extract and crack)

# Using s7-brute-offline
# Extract password hash from captured traffic or project file
# Crack offline

# Replay attack
# Capture legitimate authentication
# Replay to gain access

S7 Control Commands

bash
# WARNING: These commands affect PLC operation
# Only use in lab environments

python3 << 'EOF'
import snap7

client = snap7.client.Client()
client.connect('192.168.1.100', 0, 1)

# Stop the PLC (DANGEROUS)
# client.plc_stop()

# Start the PLC
# client.plc_cold_start()
# client.plc_hot_start()

# Read memory areas
# DB (Data Block)
db_data = client.db_read(1, 0, 100)  # DB1, offset 0, 100 bytes

# Read inputs
inputs = client.read_area(snap7.types.Areas.PE, 0, 0, 10)

# Read outputs  
outputs = client.read_area(snap7.types.Areas.PA, 0, 0, 10)

# Read markers (memory)
markers = client.read_area(snap7.types.Areas.MK, 0, 0, 10)

client.disconnect()
EOF
# WARNING: These commands affect PLC operation
# Only use in lab environments

python3 << 'EOF'
import snap7

client = snap7.client.Client()
client.connect('192.168.1.100', 0, 1)

# Stop the PLC (DANGEROUS)
# client.plc_stop()

# Start the PLC
# client.plc_cold_start()
# client.plc_hot_start()

# Read memory areas
# DB (Data Block)
db_data = client.db_read(1, 0, 100)  # DB1, offset 0, 100 bytes

# Read inputs
inputs = client.read_area(snap7.types.Areas.PE, 0, 0, 10)

# Read outputs  
outputs = client.read_area(snap7.types.Areas.PA, 0, 0, 10)

# Read markers (memory)
markers = client.read_area(snap7.types.Areas.MK, 0, 0, 10)

client.disconnect()
EOF

Allen-Bradley / Rockwell Attacks

EtherNet/IP Exploitation

bash
# Using pycomm3
pip install pycomm3

python3 << 'EOF'
from pycomm3 import LogixDriver

# Connect to ControlLogix/CompactLogix
with LogixDriver('192.168.1.100') as plc:
    
    # Get PLC info
    info = plc.get_plc_info()
    print(f"Name: {info['name']}")
    print(f"Vendor: {info['vendor']}")
    print(f"Product Type: {info['product_type']}")
    print(f"Revision: {info['revision']}")
    
    # Get all tags
    tags = plc.get_tag_list()
    for tag in tags:
        print(f"{tag['tag_name']}: {tag['data_type']}")
    
    # Read a tag
    result = plc.read('SomeTag')
    print(f"Value: {result.value}")
    
    # Write a tag (DANGEROUS)
    # plc.write('SomeTag', 100)
EOF
# Using pycomm3
pip install pycomm3

python3 << 'EOF'
from pycomm3 import LogixDriver

# Connect to ControlLogix/CompactLogix
with LogixDriver('192.168.1.100') as plc:
    
    # Get PLC info
    info = plc.get_plc_info()
    print(f"Name: {info['name']}")
    print(f"Vendor: {info['vendor']}")
    print(f"Product Type: {info['product_type']}")
    print(f"Revision: {info['revision']}")
    
    # Get all tags
    tags = plc.get_tag_list()
    for tag in tags:
        print(f"{tag['tag_name']}: {tag['data_type']}")
    
    # Read a tag
    result = plc.read('SomeTag')
    print(f"Value: {result.value}")
    
    # Write a tag (DANGEROUS)
    # plc.write('SomeTag', 100)
EOF

Modbus PLC Attacks

bash
# Modbus has NO authentication
# Any device on the network can read/write

python3 << 'EOF'
from pymodbus.client import ModbusTcpClient

client = ModbusTcpClient('192.168.1.100', port=502)
client.connect()

# Read holding registers
result = client.read_holding_registers(0, 10, slave=1)
print(f"Registers: {result.registers}")

# Read coils (digital outputs)
coils = client.read_coils(0, 10, slave=1)
print(f"Coils: {coils.bits}")

# DANGEROUS: Write operations
# Write single coil (turn output on/off)
# client.write_coil(0, True, slave=1)

# Write single register
# client.write_register(0, 1234, slave=1)

# Write multiple coils
# client.write_coils(0, [True, False, True], slave=1)

# Write multiple registers
# client.write_registers(0, [100, 200, 300], slave=1)

client.close()
EOF
# Modbus has NO authentication
# Any device on the network can read/write

python3 << 'EOF'
from pymodbus.client import ModbusTcpClient

client = ModbusTcpClient('192.168.1.100', port=502)
client.connect()

# Read holding registers
result = client.read_holding_registers(0, 10, slave=1)
print(f"Registers: {result.registers}")

# Read coils (digital outputs)
coils = client.read_coils(0, 10, slave=1)
print(f"Coils: {coils.bits}")

# DANGEROUS: Write operations
# Write single coil (turn output on/off)
# client.write_coil(0, True, slave=1)

# Write single register
# client.write_register(0, 1234, slave=1)

# Write multiple coils
# client.write_coils(0, [True, False, True], slave=1)

# Write multiple registers
# client.write_registers(0, [100, 200, 300], slave=1)

client.close()
EOF

Logic Manipulation

The most impactful attacks involve modifying the PLC logic itself. This can cause subtle malfunctions that are difficult to detect.

Attack Scenarios

Stuxnet-style Attack

Modify logic to cause physical damage while showing normal values to operators. The HMI displays everything is normal while equipment is being destroyed.

Safety System Bypass

Disable safety interlocks that prevent dangerous conditions. Example: Allow tank overfill, disable temperature limits.

Production Sabotage

Make subtle changes to process values causing quality defects. Example: Slightly modify recipe parameters in food/pharma manufacturing.

Ransomware

Lock out operators from PLCs. Encrypt or wipe PLC logic. Demand ransom for restore or decryption key.

Logic Upload/Download

bash
# Download logic from PLC (backup/analysis)
# This extracts the currently running program

# Siemens - using snap7
python3 << 'EOF'
import snap7

client = snap7.client.Client()
client.connect('192.168.1.100', 0, 1)

# Download entire project (if supported)
# Requires proper access level

# Download specific block
block_data = client.full_upload(snap7.types.block_types.OB, 1)
with open('OB1.bin', 'wb') as f:
    f.write(block_data)

client.disconnect()
EOF

# Allen-Bradley - using RSLogix or pycomm3
# Logic stored in controller, can be uploaded via software

# Schneider - using Unity Pro or network tools
# Modicon PLCs allow logic upload via proprietary protocol

# Analyze downloaded logic
# - Look for safety interlocks
# - Identify critical control logic
# - Find hardcoded values
# Download logic from PLC (backup/analysis)
# This extracts the currently running program

# Siemens - using snap7
python3 << 'EOF'
import snap7

client = snap7.client.Client()
client.connect('192.168.1.100', 0, 1)

# Download entire project (if supported)
# Requires proper access level

# Download specific block
block_data = client.full_upload(snap7.types.block_types.OB, 1)
with open('OB1.bin', 'wb') as f:
    f.write(block_data)

client.disconnect()
EOF

# Allen-Bradley - using RSLogix or pycomm3
# Logic stored in controller, can be uploaded via software

# Schneider - using Unity Pro or network tools
# Modicon PLCs allow logic upload via proprietary protocol

# Analyze downloaded logic
# - Look for safety interlocks
# - Identify critical control logic
# - Find hardcoded values

Firmware Attacks

bash
# PLC firmware can be vulnerable to:
# - Buffer overflows
# - Authentication bypass
# - Backdoors
# - Malicious firmware upload

# Firmware extraction methods:
# 1. Download from vendor website
# 2. JTAG/debug interface access
# 3. Network firmware update capture

# Firmware analysis
# Use binwalk, Ghidra for analysis
binwalk plc_firmware.bin
binwalk -e plc_firmware.bin

# Look for:
# - Hardcoded credentials
# - Debug interfaces
# - Vulnerable services
# - Known CVEs

# Search for PLC CVEs
# https://www.cisa.gov/uscert/ics/advisories
# https://nvd.nist.gov - search "PLC" or vendor name
# PLC firmware can be vulnerable to:
# - Buffer overflows
# - Authentication bypass
# - Backdoors
# - Malicious firmware upload

# Firmware extraction methods:
# 1. Download from vendor website
# 2. JTAG/debug interface access
# 3. Network firmware update capture

# Firmware analysis
# Use binwalk, Ghidra for analysis
binwalk plc_firmware.bin
binwalk -e plc_firmware.bin

# Look for:
# - Hardcoded credentials
# - Debug interfaces
# - Vulnerable services
# - Known CVEs

# Search for PLC CVEs
# https://www.cisa.gov/uscert/ics/advisories
# https://nvd.nist.gov - search "PLC" or vendor name

Notable ICS Malware & Threat Landscape

Understanding real-world ICS malware informs both offensive testing scenarios and the detections a defender expects to build. These are the campaigns every OT assessor should be able to reference.

Targeted Siemens S7-300 controllers driving uranium centrifuges. Modified PLC logic to damage equipment while replaying normal values to the HMI — the archetypal "false view to the operator" attack.

Industroyer / CRASHOVERRIDE (2016) & Industroyer2 (2022)

T0855 T0814

Grid malware used against Ukraine. Modular payloads spoke IEC-101, IEC-104, IEC 61850 (MMS) and OPC DA to open breakers directly. Industroyer2 (2022) was a hardcoded IEC-104 variant retargeted at a specific substation.

The first malware to target a Safety Instrumented System — Schneider Triconex SIS controllers — via the proprietary TriStation protocol. A failed reprogram tripped the plant to a safe state, revealing the attack. See the Safety Considerations section for why SIS is off-limits.

A modular, cross-vendor ICS attack framework (CISA AA22-103A) — the current state of the art. Components target Schneider and Omron PLCs plus any CODESYS and OPC UA device, making it vendor-agnostic rather than tied to one product. Discovered before deployment.

OT:ICEFALL (2022)

Forescout's OT:ICEFALL research disclosed 56 vulnerabilities across 10+ vendors rooted in "insecure-by-design" behavior — unauthenticated protocols, no firmware signing, weak credential schemes. The lesson for assessors: many ICS weaknesses are inherent design gaps, not patchable CVEs, so map findings to compensating controls (segmentation, monitoring) rather than a patch.

Common PLC CVEs

CVE Vendor Vulnerability Impact
CVE-2019-13945 Siemens S7 Denial of Service Crash PLC
CVE-2020-15782 Siemens S7-1200 Memory Protection Bypass Code execution
CVE-2012-6435 Rockwell MicroLogix Hardcoded credentials Auth bypass
CVE-2015-1015 Schneider Modicon Hardcoded FTP password Full access
CVE-2021-22779 Schneider Modicon (Modbus/UMAS) Auth bypass (UMAS) Unauthenticated logic write
CVE-2022-1161 Rockwell Logix (ControlLogix) Boot code / firmware injection Hidden logic (PIPEDREAM-class)
CVE-2022-45788 Schneider Modicon (OT:ICEFALL) Unauth code execution Remote control
CVE-2023-3595 Rockwell 1756-EN* comms modules Out-of-bounds write (CIP) RCE / firmware manipulation

PLC Testing Checklist

Enumeration

  • ☐ Identify PLC vendor and model
  • ☐ Determine firmware version
  • ☐ Check for known CVEs
  • ☐ Enumerate available protocols

Authentication

  • ☐ Test for default credentials
  • ☐ Check if authentication is enabled
  • ☐ Test for authentication bypass
  • ☐ Check password strength

Access Control

  • ☐ Test read access to memory/registers
  • ☐ Test write access restrictions
  • ☐ Check logic upload/download controls
  • ☐ Test stop/start controls

Documentation

Always document the exact state of PLCs before testing. Record firmware versions, running programs, and current values. This enables recovery if something goes wrong.