Skip to the content.

Tests Python 3.10+ License: GPL v3 Version

ICSForge™ is an open-source OT/ICS security coverage validation platform designed to help defenders, SOC teams, and OT security engineers validate detection, visibility, and readiness against real-world industrial attack techniques.

ICSForge focuses on what can actually be observed on the network and generates realistic OT traffic and PCAPs in 10 industrial protocols (Modbus/TCP, DNP3, S7comm, IEC-104, OPC UA, EtherNet/IP, BACnet/IP, MQTT, IEC 61850 GOOSE, PROFINET DCP) which are aligned with 68 of 83 unique techniques in MITRE ATT&CK for ICS v18 (82% coverage) — without exploiting real systems or causing unsafe process impact — to help asset owners and defenders assess the quality of existing security countermeasures such as firewalls, OT NSM sensors and ACLs and identify hidden gaps.

ICSForge is developed with a defender-first and safe-by-design approach around a Sender-Receiver architecture and interacting with the designated sender and receiver, without the need of touching other OT devices. By default, live traffic sends are restricted to RFC 1918 / loopback addresses, and PCAP replay is restricted to private ranges unless explicitly unlocked via Tools → Send Policy.

Most ICS security tools promise coverage - ICSForge lets you prove it.

Key Numbers (v0.61.0)

Metric Value
Protocols 10 industrial protocols (Modbus/TCP, DNP3, S7comm, IEC-104, OPC UA, EtherNet/IP, BACnet/IP, MQTT, IEC 61850 GOOSE, PROFINET DCP)
Runnable Scenarios 536 standalone + 11 named attack chains = 547 total
ATT&CK for ICS Techniques Implemented 68 unique technique IDs in scenario steps
ATT&CK for ICS Matrix 83 unique technique IDs (94 total entries — 11 appear under multiple tactics)
ATT&CK for ICS Matrix Coverage 82% (68 out of 83 technique) — 35 of 68 techniques at 10/10 protocols
Detection Rules Auto-generated Suricata + Sigma rules per scenario

Architecture

┌───────────────────────┐                  ┌──────────────────────┐
│   ICSForge Sender     │  TCP/ UDP / L2   │  ICSForge Receiver   │
│                       │ ─>─>─>─>─>─>─>─> │                      │
│ • Scenario engine     │                  │ • Traffic sink       │
│ • 10 protocol builders│                  │ • Marker correlation │
│ • PCAP generation     │                  │ • Receipt logging    │
│ • Campaign playbooks  │   <── callback   │ • Coverage matrix    │
│ • Web UI (:8080)      │                  │ • Web UI (:9090)     │
└───────────────────────┘                  └──────────────────────┘
           │                                          │
           ▼                                          ▼
     ┌───────────┐                            ┌──────────────┐
     │  ATT&CK   │                            │ Correlation  │
     │  Matrix   │                            │    & Gap     │
     │  Overlay  │                            │  Visibility  │
     └───────────┘                            └──────────────┘

How correlation works: Every generated packet embeds an on-wire correlation marker (ICSFORGE_SYNTH|run_id|technique|step). When the receiver detects it, it posts a receipt back to the sender. If the receipt arrives, the packet traversed the wire. If your IDS fires on it, your detection works. Run both to prove Executed → Delivered → Detected.

Stealth mode (--no-marker / toggle in UI): generates bit-for-bit realistic traffic with no ICSForge tags in payloads. Confirmation switches to TCP ACK delivery. Use for IDS/NGFW validation where the marker would betray the test.


Quick Start

Install

git clone https://github.com/ICSforge/ICSforge.git
cd ICSforge
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
pip install -e .
chmod +x icsforge.sh

Web UI

sudo ./icsforge.sh web        				# Sender dashboard on :8080
sudo ./icsforge.sh receiver   				# Receiver dashboard on :9090
sudo ./icsforge.sh receiver --l2-iface eth0 # Receiver with Profinet Listener

Token: Generated automatically on first sender setup and shown in the setup UI. Copy it to the receiver launch command. Required for receipt integrity.

Or with Docker

docker compose up
# Sender UI:   http://localhost:8080
# Receiver UI: http://localhost:9090

Authentication

On first launch, ICSForge prompts you to create an admin account. All subsequent access requires login. Credentials are stored with scrypt KDF (N=16384, file mode 0600).

Always-public endpoints (no auth required): /api/health, /api/version, /api/technique_support, /api/receiver/callback.

Callback receipt endpoint (/api/receiver/callback): always requires X-ICSForge-Callback-Token once a token is configured (auto-generated on first setup).

Callback registration (/api/config/set_callback): loopback callers (127.x, same-host) are trusted without a token. Remote callers must supply the correct X-ICSForge-Callback-Token. If no token is configured, remote registration is rejected.

Network Configuration: Receiver IP vs Destination IP

ICSForge uses two related but distinct IP concepts in the sender UI:

Receiver IP (Network Settings panel) — the IP of the machine running icsforge receiver. Setting this saves the address to persistent config and attempts to register a callback URL with the receiver so it can push receipts back to the sender automatically. It also auto-fills the Destination IP field below it.

Destination IP (Configuration panel) — the IP written into generated packet headers as the target address. Auto-populated from Receiver IP when you click Save & Connect, but can be overridden independently.

Sync modes:

Mode How it works When to use
Callback (default) Receiver POSTs receipt to sender when marker detected Sender has a reachable callback address
Pull mode Sender polls receiver for receipts Sender is behind NAT or has no public address
SSE Browser receives real-time events via Server-Sent Events Campaigns page — live step-by-step progress
# Set receiver IP via CLI
icsforge-receiver --callback-url http://sender-ip:8080/api/receiver/callback

# Or via API
curl -X POST http://localhost:8080/api/config/network \
  -H 'Content-Type: application/json' \
  -d '{"receiver_ip": "192.168.1.50", "receiver_port": 9090}'

ICSForge can be run from the command line interface as well;

CLI — generate a PCAP offline

icsforge generate --name T0855__unauth_command__modbus --outdir out/
# → out/pcaps/T0855__unauth_command__<date>__<word>.pcap
#   out/events/T0855__unauth_command__<date>__<word>.jsonl

CLI — send live traffic

# Terminal 1: start receiver
python -m icsforge.receiver --no-web --bind 127.0.0.1

# Terminal 2: send traffic
icsforge send --name T0855__unauth_command__modbus \
  --dst-ip 127.0.0.1 --confirm-live-network

# With stealth mode (no correlation markers in payloads)
icsforge send --name T0855__unauth_command__modbus \
  --dst-ip 127.0.0.1 --confirm-live-network --no-marker

Protocol Coverage

Protocol Port Styles Techniques (of 68)
OPC UA TCP/4840 30 59/68
DNP3 TCP/20000 22 57/68
S7comm (Siemens) TCP/102 36 56/68
EtherNet/IP (Allen-Bradley) TCP/44818 24 55/68
Modbus/TCP TCP/502 29 54/68
BACnet/IP UDP/47808 16 54/68
MQTT TCP/1883 17 52/68
IEC-104 TCP/2404 20 51/68
PROFINET DCP L2/EtherType 0x8892 8 44/68
IEC 61850 GOOSE L2/EtherType 0x88B8 5 42/68

IEC 61850 GOOSE and PROFINET DCP are Layer 2 protocols — they require a raw socket interface (--l2-iface eth0) for live sends. PCAP and offline generation work without a network interface.

Techniques at Full Coverage (10/10 protocols)

T0800, T0801, T0802, T0803, T0804, T0806, T0811, T0813, T0814, T0815, T0819, T0821, T0822, T0826, T0827, T0828, T0829, T0830, T0831, T0832, T0835, T0836, T0840, T0843, T0846, T0848, T0855, T0856, T0858, T0859, T0861, T0866, T0868, T0869, T0877, T0881, T0882, T0885, T0886, T0888, T0889, T0891 — 35 techniques fully covered across all 10 protocols.


Scenarios

Scenarios are defined in icsforge/scenarios/scenarios.yml.

Naming convention: T08XX__<description>__<protocol>[_variant]

Types:

Type Count Description
Standalone scenarios 536 Single-technique, single-protocol runs
Named attack chains 11 Multi-step sequences modelling real campaigns

Named attack chains include:


What Techniques Are Covered

ICSForge implements 68 of 83 ATT&CK for ICS techniques at the network-observable level. The remaining 15 are correctly classified as host-only, require physical access, or generate no network-observable packets — they are documented in icsforge/data/technique_support.json with explicit rationale per technique, and also exposed via /api/technique_support.

ATT&CK matrix counts explained:


Web UI Pages

Page URL Purpose
Home / KPIs, top techniques by protocol coverage, scenario browser
Sender /sender Launch scenarios and chains; configure network; live payload preview; receiver feed
ATT&CK Matrix /matrix Interactive coverage overlay; click any runnable tile to fire traffic with full technique description
Campaigns /campaigns Multi-step campaign playbooks and named attack chains with SSE progress
Report /report Coverage report generation and inline preview; correlation gap analysis; HTML download
Tools /tools Offline PCAP generation, PCAP upload & replay, alerts ingestion, detection rule download

Key API Endpoints

Run lifecycle

Endpoint Method Description
/api/send POST Send a named scenario or chain live
/api/generate_offline POST Generate events + PCAP offline (no live traffic)
/api/runs GET List recent runs from SQLite registry
/api/run GET Receipt count + techniques for a run (?run_id=)
/api/run_detail GET Full receipt histogram for a run (?run_id=)
/api/run_full GET Complete run detail: artifacts, receipts, techniques (?run_id=)
/api/export GET Generate and download HTML report for a run (?run_id=)

All three run-detail endpoints (/api/run, /api/run_detail, /api/run_full) merge both receipt sources — the JSONL file written by the standalone receiver process and the in-memory callback receipts — using a stable content-based deduplication key (run_id, @timestamp, technique, proto, src_ip, src_port).

Scenarios and matrix

Endpoint Method Description
/api/scenarios GET All scenario names
/api/scenarios_grouped GET Scenarios grouped by ATT&CK tactic (add ?include_steps=0 for lean payload)
/api/technique/variants GET All runnable variants for a technique (?technique=T0855)
/api/technique/send POST Fire a single technique variant by ID
/api/matrix_status GET Per-technique coverage status + matrix_info counts
/api/preview GET Scenario metadata preview (?name=)
/api/preview_payload GET Live hex dump of protocol bytes for a scenario step (?name=&step=&no_marker=)

Configuration and discovery

Endpoint Method Description
/api/config/network GET/POST Sender/receiver IPs, ports, callback URL, pull mode
/api/config/receiver_ip POST Set receiver IP and attempt callback registration
/api/receiver/live GET In-memory live receipts (last 500)
/api/receiver/callback POST Receipt ingest from receiver (used by receiver process)
/api/version GET Running version, protocol count, scenario counts (no auth required)
/api/technique_support GET Full technique support metadata: implementation status and ceiling rationale (no auth required)
/api/health GET Health check (no auth required)

Detection and reporting

Endpoint Method Description
/api/detections/download GET Download Suricata + Sigma rules
/api/report/generate POST Generate coverage report HTML for a run
/api/report/download POST Download generated report as HTML file
/api/alerts/ingest POST Import Suricata EVE JSONL for correlation ({"path": "out/alerts/eve.json", "profile": "suricata_eve"})
/api/pcap/upload POST Upload a PCAP file to the server for replay
/api/pcap/replay POST Replay a PCAP against a destination IP

Note: /api/alerts/ingest requires path to be relative to the ICSForge project root (e.g. out/alerts/suricata_eve.json) — absolute paths are rejected. Returns 400 with a row-specific error for malformed alert fields.


Detection Content

ICSForge auto-generates detection rules directly from its scenario catalog:

# Via Web UI: Tools → Generate Detection Rules
# Preview:  GET  /api/detections/preview
# Download: GET  /api/detections/download

Output formats: Suricata rules (.rules) matching ICSForge correlation markers, and Sigma rules (.yml) for SIEM integration.


Stealth Mode

Standard mode embeds ICSFORGE_SYNTH|run_id|technique|step bytes in every packet payload for precise end-to-end correlation. Stealth mode removes all markers:

# CLI
icsforge send --name T0855__unauth_command__modbus \
  --dst-ip 192.168.1.50 --confirm-live-network --no-marker

# Web UI: toggle "Stealth mode" before clicking Run Live

In stealth mode:

Use stealth mode for IDS/NGFW validation exercises where the marker would trivially identify the test traffic.


Security Model


Development

pip install -e ".[dev]"
pytest                          # run tests
pytest -q tests/test_e2e_pipeline.py -p no:cov  # E2E pipeline tests
ruff check icsforge/ tests/     # lint (should be clean)
python scripts/smoke_test.py    # 35/35 quick sanity check

Testing

tests/
├── test_web_api.py         — API endpoint tests
├── test_auth.py            — Auth flow and rate limiting
├── test_sse_campaigns.py   — Campaign SSE streaming
└── test_e2e_pipeline.py    — End-to-end pipeline integration (7 tests)

What ICSForge Is Not

ICSForge is defender-first, safe by design, and honest about what each technique requires — explicit about which techniques cannot be simulated over the network and why.


Protocol Notes

Layer 2 protocols (IEC 61850 GOOSE, PROFINET DCP) require a raw network interface and the same L2 broadcast domain (no routers between sender and receiver):

# Receiver — start with L2 listener
sudo ./icsforge.sh receiver --l2-iface eth0

# Sender — set Interface (L2) field in the UI, or:
sudo ./icsforge.sh web  # then set iface=eth0 in sender UI

Both PROFINET DCP (01:0E:CF:00:00:00) and GOOSE (01:0C:CD:01:00:00) listeners start automatically with --l2-iface. Raw socket access requires root or CAP_NET_RAW. Offline PCAP generation works without an interface.

MQTT generates all packet types including CONNECT (with credentials and Will message), PUBLISH, SUBSCRIBE, UNSUBSCRIBE, PINGREQ, and DISCONNECT. All 17 styles produce spec-valid frames with correct remaining_length encoding. Requires a broker at the destination IP (default port 1883).

DNP3 implements correct per-block CRC per IEEE 1815-2012 §8.2 — the transport layer payload is split into 16-byte blocks, each followed by its own 2-byte CRC, matching what real DNP3 outstations expect at the link layer.

Source MAC addresses use registered OT vendor OUIs per protocol (Siemens for S7comm, Rockwell for EtherNet/IP, Schneider for Modbus, GE/SEL for DNP3, ABB for IEC-104, etc.) — no locally-administered bit, passes OT NSM vendor lookups cleanly.

TCP source port is stable within a scenario run — derived deterministically from md5(src_ip + dst_ip + run_id) in the ephemeral range 49152–65534. All frames in a run share one source port, matching real ICS master-RTU persistent connections.

OPC UA sessions are coherent within a scenario run — all MSG frames share the same sc_id and security_token, with monotonically incrementing sequence_number and request_id per packet.


Screenshots

Authentication Setup

Authentication Setup

Sender Landing Page - Dark Theme

Sender Landing Dark

Sender Landing Page - Light Theme

Sender Landing Light

Sender Dashboard

Sender Dashboard

Campaigns Dashboard

Campaigns Dashboard

ATT&CK for ICS Matrix

ATT&CK Matrix

ATT&CK for ICS Matrix with Run Scenarios Overlay

ATT&CK Matrix

Coverage Report

Coverage Report

Tools for PCAP Generation and Replay

Tools

Receiver - Live Traffic View

Receiver Live View

Receiver - ATT&CK for ICS Matrix - Light Theme

Receiver ATT&CK for ICS Matrix

Receiver - ATT&CK for ICS Matrix - Dark Theme

Receiver ATT&CK for ICS Matrix

License

GPLv3 - see LICENSE


ICSForge™ • OT/ICS Cybersecurity Coverage Validation Platform • icsforge.nl