-
Notifications
You must be signed in to change notification settings - Fork 1
Module System
This document explains the IXF module architecture, the __info__ metadata format, all 10 option types, and the core check() / run() patterns.
Modules live under industrialxpl/modules/ and are referenced by their path relative to that directory.
Slash notation (used in the shell):
scanners/ics/modbus_detect
cve/siemens/cve_2021_22681_s7_1200_hardcoded_key
cve/malware/crashoverride_industroyer
assessment/mitre_ics/t0843_program_upload
Dot notation (Python import path):
scanners.ics.modbus_detect
cve.siemens.cve_2021_22681_s7_1200_hardcoded_key
cve.malware.crashoverride_industroyer
assessment.mitre_ics.t0843_program_upload
Both notations are interchangeable in the shell. use normalizes slashes to dots internally.
| Directory | Contents |
|---|---|
exploits/protocols/ |
Protocol design abuse (Modbus, S7, DNP3, BACnet, IEC 104, OPC UA, ...) |
exploits/plc/ |
Vendor-specific PLC exploits |
exploits/scada/ |
SCADA/HMI software exploits |
exploits/mes/ |
MES/ERP exploits (Ignition, SAP, ActiveMQ, ...) |
scanners/ics/ |
Protocol-specific discovery and fingerprinting |
scanners/osint/ |
Shodan dorks, ELITEWOLF, OT Hunt |
creds/ |
Default credential testing by vendor |
cve/ |
CVE-specific PoC exploits by vendor |
cve/apt/ |
APT malware TTP replicas |
cve/malware/ |
ICS malware TTP simulation modules |
cve/malware/_native/ |
C/C++/Go/Python native malware code |
assessment/mitre_ics/ |
MITRE ATT&CK for ICS technique modules |
assessment/iec62443/ |
IEC 62443 compliance checks |
assessment/sast/ |
SAST/LLM PLC code analysis |
assessment/risk/ |
Risk scoring |
assessment/ir/ |
Incident response playbooks |
Every module is a Python file with a class named Exploit (shadowing the imported base). Here is the minimal structure:
"""Brief description."""
import socket
from industrialxpl.core.exploit import (
Exploit, OptBool, OptIP, OptPort, mute,
print_error, print_status, print_success,
DestructiveGate,
)
class Exploit(Exploit):
__info__ = {
"name": "My Module Name",
"description": "What this module does in one sentence.",
"authors": ("Your Name",),
"references": ("https://example.com/advisory",),
"devices": ("Vendor Product Model",),
"impact": "HIGH",
"exploit_type": "Buffer Overflow",
"source_poc": "https://github.com/example/poc",
"cve": "CVE-YYYY-NNNNN",
"cvss": "9.8",
"severity": "CRITICAL",
"mitre_techniques": ["T0866"],
"mitre_tactics": ["Initial Access"],
}
target = OptIP("", "Target device IP")
port = OptPort(502, "Protocol port")
simulate = OptBool(True, "Simulate (default: True)")
destructive = OptBool(False, "Enable live exploitation")
@mute
def check(self) -> bool:
"""Read-only connectivity probe."""
if not self.target:
return False
try:
s = socket.socket()
s.settimeout(5)
s.connect((self.target, self.port))
s.close()
return True
except Exception:
return False
def run(self) -> None:
"""Execute the exploit or print simulation."""
if not self.target:
print_error("Set 'target' option first.")
return
if self.simulate:
DestructiveGate.print_simulation(
description=(
"CVE-YYYY-NNNNN Vendor Product\n"
"Step 1: Connect to target\n"
"Step 2: Send crafted payload\n"
"Step 3: Achieve remote code execution"
),
mitre_techniques=["T0866"],
)
return
print_status("[CVE-YYYY] Exploiting {}:{}...".format(self.target, self.port))
# Live exploit code hereEvery module must define a __info__ dictionary. All keys are required unless marked optional.
| Key | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Human-readable module name |
description |
string | yes | One to three sentence description |
authors |
tuple[str] | yes | Author(s) |
references |
tuple[str] | yes | CVE advisories, PoC URLs, papers |
devices |
tuple[str] | yes | Target device(s) or software |
impact |
string | yes | One of: INFO, READ, LOW, MEDIUM, HIGH, CRITICAL, CATASTROPHIC
|
exploit_type |
string | yes | Short exploit category (e.g. Stack Buffer Overflow, Default Credentials) |
source_poc |
string | no | Original PoC reference URL |
cve |
string | yes | CVE ID or N/A
|
cvss |
string | yes | CVSS score string or N/A
|
severity |
string | yes | Mirrors impact or CVSS severity label |
mitre_techniques |
list[str] | yes | MITRE ATT&CK for ICS technique IDs |
mitre_tactics |
list[str] | yes | Tactic names (must match ATT&CK taxonomy) |
destructive_description |
string | no | Human text shown in DestructiveGate banner |
Impact values and their meaning:
| Value | Meaning |
|---|---|
INFO |
Passive observation, no state change |
READ |
Read-only query |
LOW |
Non-destructive write, reversible |
MEDIUM |
Parameter change, may affect operation |
HIGH |
Device restart, process stop |
CRITICAL |
Firmware/logic modification, possibly irreversible |
CATASTROPHIC |
Physical damage, safety system disabling |
Modules declare options as class-level descriptors. The shell uses these to validate set inputs and display the show options table.
Accepts an IPv4 address or hostname.
| Value | Valid? |
|---|---|
"192.168.1.100" |
Yes |
"10.0.0.1" |
Yes |
"target.local" |
Yes (alphanumeric, -, _, .) |
"" |
Yes (empty = unset) |
"999.999.999.999" |
No — invalid IPv4 |
target = OptIP("", "Target device IP")ixf > set target 192.168.1.100
[*] target => 192.168.1.100
ixf > set target not_a_hostname!
[-] Validation error for 'target': invalid IP or hostname
Accepts an integer between 1 and 65535.
| Value | Valid? |
|---|---|
502 |
Yes |
44818 |
Yes |
65535 |
Yes |
0 |
No |
65536 |
No |
"80" |
Yes (auto-converted) |
port = OptPort(502, "Modbus TCP port")Accepts any integer. Optional min_value and max_value constraints.
unit_id = OptInteger(1, "Modbus unit ID (1–247)", min_value=1, max_value=247)
timeout = OptInteger(5, "Timeout in seconds", min_value=1)
threads = OptInteger(10, "Thread count")| Value | Valid? (no constraints) |
|---|---|
0 |
Yes |
-1 |
Yes |
1000 |
Yes |
"abc" |
No |
Accepts any floating-point number.
rate_limit = OptFloat(0.5, "Request rate limit (seconds)")| Value | Valid? |
|---|---|
1.5 |
Yes |
0.0 |
Yes |
-0.3 |
Yes |
"abc" |
No |
Accepts any string value.
username = OptString("admin", "Username")
payload = OptString("", "Custom payload string")Accepts boolean values. Case-insensitive string forms are also accepted.
| Input | Interpreted as |
|---|---|
True, False
|
Direct bool |
"true", "yes", "1", "on"
|
True |
"false", "no", "0", "off"
|
False |
simulate = OptBool(True, "Simulate (default: True)")
destructive = OptBool(False, "Enable live execution")
verbose = OptBool(False, "Verbose output", advanced=True)ixf > set simulate false
ixf > set verbose yes
ixf > set destructive on
Accepts a MAC address in standard notation. Both : and - separators accepted; always normalized to lowercase colon notation.
| Value | Valid? | Normalized |
|---|---|---|
"00:11:22:33:44:55" |
Yes | 00:11:22:33:44:55 |
"00-11-22-33-44-55" |
Yes | 00:11:22:33:44:55 |
"AA:BB:CC:DD:EE:FF" |
Yes | aa:bb:cc:dd:ee:ff |
"00:11:22:33:44" |
No | — |
"not-a-mac" |
No | — |
target_mac = OptMAC("", "Target device MAC address")Accepts a wordlist path. Supports:
- Absolute path:
/tmp/passwords.txt -
file://prefix:file:///opt/lists/default_creds.txt - Relative path from
industrialxpl/resources/wordlists/:ics_common_passwords.txt
The file must exist and be readable.
wordlist = OptWordlist("", "Password wordlist")ixf > set wordlist file:///opt/wordlists/rockyou.txt
ixf > set wordlist ics_common_passwords.txt
Accepts an encoder name string (no format validation — passed to encoding pipeline).
encoder = OptEncoder("", "Output encoder (e.g. base64, hex, raw)")Some options are marked advanced=True. They are not shown by show options but appear in show advanced:
ixf (module) > show advanced
Advanced Options
────────────────────────────────────
verbose False Enable verbose output
timeout 5 Override connection timeout
Suppresses standard output during execution (useful in multi-threaded scans where check() is called concurrently).
@mute
def check(self) -> bool:
# stdout is suppressed here
...Output generated inside a @mute-decorated function goes to an internal buffer. The overall scan result is printed by the thread coordinator.
Enables a module to accept a file of targets via target=file:///path/to/targets.txt. Each line in the file is treated as a separate target.
@multi
def run(self) -> None:
# self.target is set to each line in the file
...Usage:
ixf > set target file:///opt/targets.txt
ixf > run
[multi] Target: 192.168.1.1
[multi] Target: 192.168.1.2
[multi] Target: 192.168.1.3
check() must:
- Decorate with
@mute - Return
Trueif the target appears vulnerable/present - Return
Falseif not reachable or not vulnerable - Never send exploits — only passive probes (TCP connect, banner grab)
- Handle all exceptions gracefully
@mute
def check(self) -> bool:
if not self.target:
return False
try:
s = socket.socket()
s.settimeout(5)
s.connect((self.target, self.port))
# optionally send a probe and inspect the response
s.send(b"\x00\x01\x00\x00\x00\x06\x01\x03\x00\x00\x00\x01")
banner = s.recv(64)
s.close()
return len(banner) >= 6 # Modbus response has at least 6 bytes
except Exception:
return Falserun() must:
- Validate
self.targetfirst - If
self.simulateisTrue: callDestructiveGate.print_simulation()and return - If live: implement the actual exploit logic
def run(self) -> None:
if not self.target:
print_error("Set 'target' option.")
return
if self.simulate:
DestructiveGate.print_simulation(
description=(
"Step 1: ...\n"
"Step 2: ...\n"
"Step 3: ..."
),
mitre_techniques=["T0866"],
)
return
# Live code
print_status("[CVE-YYYY] Exploiting {}:{}...".format(self.target, self.port))
# ...The get_info() method traverses the class MRO to retrieve the __info__ dictionary. It handles Python name mangling correctly (the metaclass stores __info__ as _{ClassName}__info__).
obj = import_exploit("industrialxpl.modules.cve.siemens.cve_2021_22681_s7_1200_hardcoded_key")()
info = obj.get_info()
print(info["name"]) # CVE-2021-22681 Siemens S7-1200/1500 PLC
print(info["impact"]) # CRITICALAfter adding a new module, verify it loads without errors:
python -c "
from industrialxpl.core.exploit.utils import index_modules, import_exploit
mods = index_modules()
errs = []
for m in mods:
try:
import_exploit('industrialxpl.modules.' + m)()
except Exception as e:
errs.append((m, str(e)))
print(f'{len(mods)} modules | {len(errs)} errors')
if errs:
for m, e in errs: print(f' ERR {m}: {e}')
"Previous: Shell Reference | Next: SafeMode / DestructiveMode