Del bytecode AWL al silicio ARM: exploit development contra PLCs Siemens
Explotar un autómata programable no tiene nada que ver con tirar abajo un binario de escritorio: el objetivo vive dentro de una máquina virtual (MC5/MC7/MC7+) que interpreta AWL sobre un procesador ARM o MIPS gobernado por el kernel propietario ADONIS. Este documento une mi trabajo previo de reversing de PLCs, mi laboratorio clásico de exploit development y las herramientas modernas (JEB, rz-libmc7) en una sola metodología: entender el bytecode, escribirle shellcode, buscarle gadgets ROP y replicar el sandbox escape de CVE-2020-15782 sobre un laboratorio ICS propio.
/índice_del_laboratorio_ot +
- Resumen técnico
- Prefacio: por qué explotar un PLC no es como explotar un PC
- AWL: el ensamblador de los autómatas
- Debajo del bytecode: ARM/MIPS y el kernel ADONIS
- Del AWL al shellcode
- ROP sobre MC7/MC7+: gadgets dentro de la VM
- Superficie de ataque en una planta real
- Casos reales: TRITON, Havex y CVE-2020-15782
- Guía de laboratorio ICS: hardware, simuladores y red OT
- Workshop 1: primer shellcode AWL
- Workshop 2: ROP con bloques MC7
- Workshop 3: fuzzing de Modbus TCP y S7comm
- Workshop 4: cadena completa con sandbox escape
- Integración con la infraestructura ofuscada
- Automatización del laboratorio
- Roadmap de la serie
- Conclusión: el estado del arte en explotación de PLCs
- Referencias
00. Resumen Técnico
Antes de entrar en materia, fijo el terreno de juego: qué plataformas ataco, qué bytecode interpretan, qué hay por debajo de esa máquina virtual y qué vulnerabilidades documentadas sirven como caso de estudio. Todo lo que sigue se apoya exclusivamente en investigación pública y en pruebas realizadas sobre equipos propios o simuladores.
| Campo | Detalle |
|---|---|
| Plataformas objetivo | Siemens SIMATIC S5-95U, S7-300, S7-400, S7-1200 y S7-1500 |
| Bytecode | MC5 (S5) · MC7 (S7-300/S7-400) · MC7+ (S7-1200/S7-1500) |
| Sustrato hardware | Procesadores ARM o MIPS bajo el kernel propietario ADONIS |
| Vulnerabilidad central | CVE-2020-15782 (CVSS 8.1, CWE-119): escape del sandbox de la VM en S7-1200/1500, parcheado por Siemens en 2021 |
| Acceso inicial de la cadena | CVE-2020-6507: out-of-bounds write en V8, Chrome anterior a 83.0.4103.106 |
| Herramientas de reversing | JEB Pro, rz-libmc7 (rizin), Ghidra, ROPgadget, AFL++, Wireshark |
| Protocolos en juego | S7comm/S7comm+ (TCP 102), Modbus TCP (502), Profinet (capa 2), OPC UA/DA (4840) |
| Red del laboratorio | Host-Only de VirtualBox 192.168.100.0/24, aislada y segmentada |
Nada de lo descrito aquí se aplica fuera de laboratorios controlados o sobre fallos ya corregidos y publicados (Siemens parcheó CVE-2020-15782 en 2021). Mi interés es doble: académico y de concienciación, porque las infraestructuras críticas siguen necesitando hardening urgente.
01. Prefacio: por qué explotar un PLC no es como explotar un PC
Trasladar la disciplina del exploit development al mundo de la tecnología operativa exige desaprender varias cosas. Un autómata programable no ejecuta tu binario favorito: corre bytecode propietario en una máquina virtual construida a medida —MC5 en la familia S5, MC7 en los S7-300/S7-400 y MC7+ en los S7-1200/S7-1500—, y esa VM vive encima de un procesador ARM o MIPS gestionado por el kernel ADONIS. Cuando Claroty Team82 publicó su carrera hacia la ejecución nativa de código en PLCs (CVE-2020-15782), quedó demostrado que el sandbox de esa VM no era un muro infranqueable: se podía escribir shellcode directamente en zonas protegidas de memoria.
Este artículo nace de la convergencia de cuatro líneas de trabajo que vengo desarrollando en el portafolio:
- El reversing de AWL y de las arquitecturas MC5/MC7 que documenté en PLC Siemens S5/S7: arquitectura MC7 y AWL: la anatomía de la VM, sus registros (RLO, AKKU1, AKKU2), las instrucciones de salto y el formato de los bloques de programa. Ese análisis gana ahora profundidad con la documentación oficial de JEB Decompiler (Nicolas Falliere, PNF Software) sobre el formato binario de bloques S7, las convenciones __FC_CC/__FB_CC y los tipos MC7 (POINTER, ANY, S5TIME, DATE_AND_TIME).
- Mi laboratorio clásico de exploit development, recogido en Windows Internals, ROP y Shellcode: cadenas ROP en 64 bits, análisis de CVEs de Chrome V8 y un entorno con VirtualBox, fakedns e INetSim. La metodología de búsqueda de gadgets con ROPgadget viaja intacta hacia los binarios ARM/MIPS del firmware de los PLCs.
- El ecosistema de herramientas para bytecode MC7: rz-libmc7 (plugin de rizin que desensambla MC7 de S7-300/S7-400) y JEB Pro, capaz de decompilar ese mismo bytecode a pseudo-C de forma nativa. Entre ambos es posible analizar bytecode sin depender de tener el hierro delante.
- La infraestructura ofuscada que describí en WireGuard + udp2raw + Proxy Residencial: la base de operaciones blindada desde la que operar sin exponer la IP de origen, imprescindible cuando se toca hardware real conectado a Internet.
El resultado es esta guía: parte del AWL como lenguaje de entrada, lo traduce a conceptos de explotación binaria, monta talleres prácticos con simuladores y hardware, disecciona CVE-2020-15782 como estudio central y deja una metodología replicable para quien quiera meterse en la seguridad ofensiva de sistemas de control industrial.
02. AWL: el ensamblador de los autómatas
AWL —Anweisungsliste en alemán, STL (Statement List) en la documentación anglosajona de Siemens— juega en el mundo de los PLCs exactamente el papel que el ensamblador juega en un ordenador convencional: nemónicos de bajo nivel que operan sobre registros y operandos. Para quien escribe exploits, eso significa control fino del flujo de ejecución del autómata, igual que ASM se lo da sobre un x86 o un ARM.
Hay un detalle que lo cambia todo: da igual si la planta se programó en Ladder (LAD), FBD, SCL o el propio AWL, porque STEP5, STEP7 y TIA Portal acaban compilando todo al mismo bytecode MC5/MC7/MC7+ que la VM del PLC interpreta. Dicho de otro modo, el verdadero objetivo del exploit developer es el bytecode: si conoces la codificación de las instrucciones, puedes inyectarlo directamente sin pasar nunca por el compilador.
2.1 El modelo de registros como superficie de explotación
Los registros del autómata cumplen aquí la misma función que los registros de CPU en la explotación tradicional: son el primer objetivo que hay que entender para torcer el flujo del programa.
| Registro AWL | Bit | Función | Equivalente en Explotación |
|---|---|---|---|
| FC (First Check) | 0 | Indica si la instrucción es la primera de una cadena lógica. Si FC=0, la consulta se almacena directamente en RLO sin evaluar el estado anterior. | Similar al flag ZF (Zero Flag) en x86 — controla el inicio de cadenas lógicas |
| RLO (VKE) | 1 | Resultado de operación lógica: acumulador principal de operaciones booleanas. Cada operación lógica (U, UN, O, ON, X, XN) sobrescribe este registro. | EAX/RAX en x86 — acumulador principal. Controlarlo es controlar las decisiones del programa. |
| STA | 2 | Estado del bit direccionado. Refleja el valor del operando consultado. | Similar al flag CF (Carry Flag) — indica el estado de la última operación |
| OR | 3 | Indica si una operación AND previa dio 1, necesario para combinar AND con OR en cadenas lógicas. | Flag auxiliar para operaciones compuestas |
| OV | 4 | Desbordamiento aritmético: se activa en operaciones de coma flotante o enteros con overflow. | OF (Overflow Flag) en x86 |
| OS | 5 | Overflow Stored: latch del bit OV. Persiste hasta que se ejecuta una instrucción de fin de bloque o SPS. | Indicador de error almacenado (persistente) |
| CC0, CC1 | 6-7 | Códigos de condición: actualizados por operaciones aritméticas, comparaciones y desplazamientos. | Similares a SF (Sign Flag) y PF (Parity Flag) |
| BR | 8 | Binary Result: permite interpretar el resultado de una operación de palabra como resultado binario e integrarlo en la cadena lógica. | Usado por SFC/SFB como indicador de éxito/error |
| AKKU1, AKKU2 | 32 bits c/u | Acumuladores para operaciones aritméticas y de carga/transferencia. AKKU1 es el principal; AKKU2 se usa en comparaciones y operaciones binarias. | Equivalentes a EAX:EBX en x86 — fundamentales para operaciones de 32 bits |
| AR1, AR2 | 32 bits c/u | Registros de direccionamiento: contienen punteros MC7 de 4 bytes (área + offset + bit). Usados para direccionamiento indirecto intra-área e inter-área. | Equivalentes a ESI:EDI en x86 — controlan el acceso a memoria |
| DB1, DB2 | 16 bits c/u | Registros de bloque de datos: DB1 apunta al bloque de datos global (DB), DB2 al bloque de datos de instancia (DI). No son directamente accesibles. | Similares a los registros de segmento DS:ES en x86 real mode |
El RLO funciona como acumulador booleano central: cualquier operación lógica (U, UN, O, ON, X, XN) lo reescribe. Si consigues dictar el flujo de instrucciones AWL —por ejemplo inyectando bytecode MC7 vía S7comm (TCP 102) o Modbus TCP (502), ambos sin autenticación— puedes manipularlo para provocar evaluaciones booleanas que el programa original jamás contempló. Es literalmente el equivalente a controlar EAX en x86: dominas el acumulador, dominan las decisiones.
2.2 Instrucciones de salto como primitivas de control de flujo
Los saltos AWL son la materia prima de cualquier exploit para autómatas, y su comportamiento calca el de los JMP, JE, JNE del ensamblador x86:
| AWL S5 | AWL S7 | Condición | Equivalente x86 | Uso en Exploit |
|---|---|---|---|---|
| SPA | SPA | Incondicional | JMP | Redirigir ejecución a shellcode |
| SPB | SPB | RLO = 1 | JNE / JNZ | Salto condicional tras evaluación |
| SPN | SPN | RLO = 0 | JE / JZ | Bypass de verificaciones de seguridad |
| SPZ | SPZ | AKKU1 = 0 | JZ (tras CMP) | Control post-aritmética |
| SPP | SPP | AKKU1 > 0 | JG | Validación de contadores |
| SPM | SPM | AKKU1 < 0 | JL | Detección de underflow |
| SPO | SPO | OV = 1 | JO | Captura de desbordamiento aritmético |
| SPS | SPS | OS = 1 | — | Detección de error persistente (latch) |
| SPR | SPR | BR = 1 | — | Validación de operaciones aritméticas sin error |
Probando sobre un S5-95U físico comprobé algo importante: SPA y SPB son los únicos saltos que responden de forma consistente en ese equipo concreto. En los S7-300/S7-400, en cambio, todos los saltos documentados funcionan. Para S5 no es un detalle menor: obliga a construir cualquier exploit usando solo el salto incondicional y el condicionado por RLO.
2.3 El desbordamiento de pila de módulos (STUEB) como vector de ataque
AWL admite como máximo 16 niveles de anidamiento en llamadas entre módulos (UC, CC). Pasarse tiene nombre propio: desbordamiento de pila de módulos (STUEB). Como vector ofensivo es un clásico: llamadas recursivas sin freno pueden llevar el PLC a comportamiento indefinido, con la pila de ejecución corrupta y el flujo del programa potencialmente secuestrado.
No logré observar experimentalmente qué ocurre tras el STUEB en mis pruebas con el S5-95U: el simulador PC Simu descartaba la instrucción de transferencia que toma el contenido de AKKU1 y lo envía a memoria. Validar este vector exige hardware real o un emulador más fiel; queda anotado como tarea pendiente en el roadmap de la Sección 15.
03. Debajo del bytecode: ARM/MIPS y el kernel ADONIS
Los PLCs modernos de Siemens —los S7-1200 y S7-1500 en particular— no interpretan AWL sobre el silicio desnudo. Encima de procesadores ARM o MIPS corre el kernel propietario ADONIS, y una capa de firmware hace de máquina virtual para el bytecode MC7 o MC7+. El trabajo de Claroty Team82 demostró que esa VM se puede agujerear: CVE-2020-15782 permitía salir del sandbox y plantar shellcode ARM/MIPS en regiones de memoria reservadas del kernel.
3.1 ¿VM o microprogramación? El debate que condiciona toda la estrategia
Que MC5/MC7/MC7+ sea una máquina virtual o una arquitectura microprogramada no es discusión académica ociosa: cada respuesta sugiere una táctica de explotación distinta.
- Si hablamos de una VM: el exploit vive dentro del dialecto del bytecode. Solo hay instrucciones de las que expone el intérprete, y escapar exige vulnerar al intérprete —precisamente lo que Claroty consiguió con CVE-2020-15782—.
- Si hablamos de microprogramación: bajo el modelo de Matloff y Franklin, una Máquina A (la CPU física ARM/MIPS) ejecuta un microprograma (firmware) que la disfraza de Máquina B (la que entiende MC7). Ahí el procesador real queda expuesto a través del firmware, y un exploit que consiga ejecutar instrucciones ARM/MIPS nativas se queda con el hardware completo.
La evidencia apunta a un modelo híbrido: MC7/MC7+ actúa como capa de abstracción que traduce bytecodes a instrucciones ARM/MIPS, pero esa capa corre confinada (sandbox) en espacio de usuario sobre ADONIS. La vulnerabilidad de Claroty rompió justo esa frontera: escritura fuera del sandbox más el parcheo en memoria de un opcode de la VM que, al ser invocado por el sistema operativo, desviaba la ejecución hacia el shellcode del atacante.
3.2 CVE-2020-15782: el caso de estudio definitivo
CVE-2020-15782 (CVSS 8.1, CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer) afectaba a los SIMATIC S7-1200 y S7-1500. Descubierta por Tal Keren en Claroty Team82 y corregida por Siemens en 2021, dejaba que un atacante remoto con acceso de red al puerto TCP 102 (S7comm) y sin credenciales alguna:
- Escapara del sandbox de la VM: escribiendo datos arbitrarios en regiones de memoria protegidas, normalmente fuera del alcance del bytecode MC7+.
- Pacheara un opcode de la VM: alterando una instrucción de la máquina virtual en memoria para que, al ejecutarla el sistema operativo, el flujo saltara al código del atacante.
- Ejecutara código ARM/MIPS nativo: inyectando el shellcode dentro de una estructura interna del kernel ADONIS, alcanzando así privilegios de kernel.
- Persistiera sin ser visto: porque el código residente a nivel de kernel es invisible tanto para el sistema operativo como para las herramientas de diagnóstico habituales.
CVE-2020-15782 protagoniza este artículo por una razón sencilla: demuestra que la VM es vulnerable y que la barrera tiene grietas. De ahí sale la estrategia en dos pisos para PLCs modernos: (1) la capa de bytecode MC7/MC7+ como vía de entrada y (2) la capa nativa ARM/MIPS para el escape y la ejecución arbitraria. El Workshop 4 replica esa cadena completa en entorno controlado.
3.3 Del código fuente al bytecode: el flujo de compilación
Saber cómo AWL, SCL, LAD o FBD terminan convertidos en bytecode es pan de cada día para el exploit developer. La cadena es esta:
La lectura ofensiva del diagrama es directa: un exploit basado en bytecode ignora por completo el lenguaje fuente original. Conociendo la codificación, el shellcode se puede escribir directamente en MC7 sin que ningún compilador intervenga. Esa codificación está documentada solo parcialmente, gracias a:
- JEB Decompiler (PNF Software): el artículo de Nicolas Falliere detalla que la instrucción L (carga) usa opcodes distintos según el tipo de inmediato (0x30 para dec16, 0x38 para dec32, 0x28 para hex8, etc.) y que la instrucción T (transferencia) corresponde al opcode 0x7E. La mayoría de instrucciones MC7, operandos incluidos, miden como mucho 4 bytes.
- rz-libmc7 (rizinorg): plugin libre para rizin que implementa un desensamblador de bytecode MC7, con soporte para aritmética (+D, -D, *D, /D, MOD), operaciones lógicas y acceso a memoria.
3.4 ARM frente a x86: qué cambia para el exploit developer
Pasarse de explotar x86 a hacerlo sobre ARM implica ajustar varias piezas del oficio, empezando por cómo se escribe shellcode y cómo se encadenan gadgets:
| Característica | x86/AMD64 | ARM | Impacto en Explotación |
|---|---|---|---|
| Conjunto de instrucciones | CISC (instrucciones complejas, longitud variable) | RISC (instrucciones simples, longitud fija de 32 bits en ARM, 16/32 en Thumb) | Shellcode más largo en ARM, pero más predecible y sin bytes prohibidos por diseño |
| Convención de llamada | Argumentos en pila (x86) o RCX,RDX,R8,R9 (AMD64 Windows) | Argumentos en R0-R3, retorno en R0. Stack para argumentos adicionales | Los gadgets ROP deben manipular registros diferentes. Pop {r0, pc} es el gadget universal de ARM |
| Ejecución condicional | Solo saltos condicionales (Jcc) | Casi todas las instrucciones pueden ser condicionales (sufijos -eq, -ne, -gt, etc.) | Mayor densidad de gadgets útiles. Instrucciones como MOVEQ, ADDNE pueden ser gadgets |
| PC como registro | RIP/EIP no es directamente accesible como registro de propósito general | R15 (PC) es de propósito general. Se puede leer y escribir como cualquier registro | Lectura/escritura directa del contador de programa facilita el control de flujo |
| Endianness | Little-endian | Bi-endian (típicamente little-endian en PLCs Siemens) | Shellcode debe verificar endianness del target. Los firmwares .upd de Siemens son little-endian |
| Modos de ejecución | Ring 0 (kernel) / Ring 3 (usuario) | EL0 (usuario) / EL1 (kernel) / EL2 (hypervisor) / EL3 (secure monitor) | El sandbox de MC7+ corre en EL0. El kernel ADONIS en EL1. El sandbox escape busca escalar de EL0 a EL1 |
Para escribir shellcode ARM hacen falta buenas chuletas. Además del clásico Felix Cloutier para x86, uso el ARM Instruction Set Reference Guide y x64.syscall.sh para syscalls en Linux/ARM. En MIPS, la referencia canónica es el MIPS Architecture Reference Manual.
04. Del AWL al shellcode
Escribir shellcode para un PLC plantea problemas que no existen en un sistema de propósito general. Allí el premio habitual es un /bin/sh o una reverse shell; aquí el shellcode tiene que hablar con el proceso físico: abrir o cerrar válvulas, retocar setpoints, falsear sensores o directamente paralizar la CPU. Y todo ello respetando las reglas de la VM: registros contados, áreas de memoria segmentadas (I, Q, M, L, DB, DI, T, C) y un repertorio de instrucciones diseñado para control industrial, no para computación general.
4.1 Shellcode AWL mínimo: forzar una salida digital
El shellcode más simple posible fuerza una salida sin consultar la lógica original. Su pariente x86 sería el típico exit(0): nada espectacular, pero basta para probar que la ejecución es nuestra.
Este fragmento no mira ninguna entrada. Inyectado en un bloque de programa (PB/FC) o de función (FB) con la ejecución redirigida hacia él, enciende las salidas aunque el programa original se resista. Es la prueba de concepto mínima: si esto corre, el PLC es nuestro.
4.2 Puerta trasera condicional con contador
Un escalón más arriba está la backdoor condicional: el comportamiento del PLC solo cambia ante una señal concreta. El ejemplo siguiente cuenta pulsos en una entrada y dispara la acción en el tercer pulso, usando contadores como mecanismo de activación:
4.3 Herramientas para el reversing de bytecode MC7
El arsenal para analizar bytecode MC7 ha madurado bastante. Estas son las tres piezas centrales:
| Herramienta | Desarrollador | Funcionalidad | Licencia | Target |
|---|---|---|---|---|
| JEB Pro | PNF Software | Desensamblador y decompilador de MC7 a pseudo-C. Adquisición de bloques desde STEP7. Soporte para formatos de bloque binario (interno LE y red BE). Recuperación de interfaces (IN/OUT/IN_OUT/STATIC/TEMP). Análisis de tipos de datos (POINTER, ANY, S5TIME, DATE_AND_TIME, ARRAY, STRING). | Comercial (demo disponible) | S7-300, S7-400 |
| rz-libmc7 | rizinorg (wargio, Jegeva) | Plugin para rizin que implementa desensamblador de MC7 bytecode. Decodifica instrucciones aritméticas, lógicas, de salto y de acceso a memoria. | Open Source (GitHub) | S7-300, S7-400 |
| Ghidra | NSA | Framework de ingeniería inversa con soporte para múltiples arquitecturas. Requiere desarrollo de extensiones para MC7, pero ofrece soporte nativo para ARM y MIPS (análisis de firmware). | Open Source | Firmware ARM/MIPS de PLCs |
En la práctica, combinar rz-libmc7 para el desensamblado rápido con JEB Pro para la decompilación de alto nivel cubre todo el ciclo de análisis de bytecode. Cuando lo que quiero es el firmware ARM/MIPS de debajo, Ghidra gana por goleada, sobre todo con los scripts de análisis de firmware que ayudan a localizar la capa que traduce MC7 a instrucciones nativas.
El byte nulo (0x00), pesadilla del shellcode x86, apenas molesta en MC7: el formato de instrucción es de longitud fija o semi-fija (máximo 4 bytes con operando), así que no existen "bytes prohibidos" al uso. Lo que sí pasa es que ciertas combinaciones de opcodes pueden ser rechazadas por el firmware si no casan con instrucciones válidas. Ofuscar en MC7 consiste en camuflar la lógica del shellcode —instrucciones redundantes, saltos indirectos, codificación condicional—, no en esquivar filtros de bytes.
05. ROP sobre MC7/MC7+: gadgets dentro de la VM
Return-Oriented Programming es la respuesta estándar frente a DEP/NX/XN. En un PLC, donde el firmware puede marcar regiones de memoria como no ejecutables, ROP permite componer la ejecución usando únicamente código que ya existe en el binario.
5.1 Adaptar ROP al mundo MC7
Traducido a MC7, un gadget ROP viene a ser una secuencia de instrucciones AWL rematada por BE (fin de bloque), BEB (fin condicional con RLO=1) o BEA (fin incondicional). Esas tres cumplen el papel del RET en x86: devuelven el control al llamante. Una cadena ROP en MC7 encadenaría varios bloques FB o FC que acaben en BE, apoyándose en la pila de módulos para dirigir el flujo.
El problema práctico es evidente: no existe un debugger paso a paso para PLCs que muestre el estado real de los registros mientras se ejecuta. Los entornos de programación modernos han abandonado el AWL en favor de lenguajes de alto nivel, sin ofrecer vista del código de bajo nivel. Eso deja la búsqueda automatizada de gadgets —en x86 resuelta con ROPgadget o ropper— prácticamente huérfana.
5.2 Estrategia híbrida: ROP en el ARM/MIPS de debajo
Como el firmware corre sobre ARM o MIPS, la vía con más futuro —y la que siguió Claroty en CVE-2020-15782— es cazar gadgets ROP en el binario ARM/MIPS del firmware, no en el bytecode MC7. El proceso:
- Extraer el firmware del PLC: vía actualizaciones .upd, ingeniería inversa del protocolo de descarga o acceso físico (JTAG/UART). Dentro vienen el kernel ADONIS y todas las bibliotecas del sistema.
- Analizar el binario con herramientas estándar: ROPgadget, ropper o Ghidra con scripting en Python.
- Montar la cadena ROP que, aliada con la vulnerabilidad de sandbox escape, ejecute código arbitrario en el procesador de verdad.
Sacar el firmware de un PLC real suele exigir acceso físico al aparato y, con frecuencia, oficio de hardware hacking (JTAG, UART, extracción de chips). Es una valla alta para la investigación independiente. Por eso el laboratorio de la Sección 8 arranca con simuladores y firmwares públicos antes de dar el salto al hierro.
06. Superficie de ataque en una planta real
El PLC no está solo. Protocolos de comunicación, estaciones de ingeniería, pantallas HMI y sistemas SCADA completan el perímetro, y mapearlos bien es el primer paso para encontrar vectores de explotación.
| Componente | Protocolo/Puerto | Vector de Ataque | Impacto | Caso Documentado |
|---|---|---|---|---|
| PLC Siemens S7-300/400 | S7comm (TCP 102) | Inyección de comandos STOP/RUN, descarga/inyección de firmware | Parada de planta, modificación de lógica de control | Stuxnet (2010) |
| PLC Siemens S7-1200/1500 | S7comm+ (TCP 102) | Sandbox escape, escritura en memoria protegida, ejecución de código nativo ARM/MIPS | Control total del PLC a nivel de kernel | CVE-2020-15782 (Claroty, 2021) |
| Modbus TCP | TCP 502 | Escritura en coils/registers sin autenticación por diseño | Manipulación de E/S, falseo de sensores, activación/desactivación de actuadores | Múltiples incidentes no atribuidos |
| Profinet | Ethernet industrial (capa 2) | Inyección de tramas, denegación de servicio en tiempo real | Interrupción de comunicación determinista entre PLCs | Investigaciones académicas |
| OPC UA/DA | TCP 4840, DCOM (135, 445) | Enumeración de tags, lectura/escritura remota de variables de proceso | Fuga de información del proceso industrial, modificación de setpoints | Havex RAT (2013-2014) |
| HMI Magelis/Harmony | HTTP/HTTPS, VNC (5900) | Contraseñas por defecto, vulnerabilidades web, exposición de pantalla de operador | Control visual del operador comprometido, comandos no autorizados | Auditorías de seguridad |
| Estación de Ingeniería (TIA Portal) | SMB (445), RDP (3389), WinRM (5985) | Compromiso del equipo que programa todos los PLCs de la planta | Acceso total a toda la red OT, modificación de lógica en todos los PLCs | Patrón de ataque recurrente |
| Schneider Modicon Quantum | Modbus TCP (502), Serial Modbus Driver | Paralización de CPU sin autenticación, buffer overflow en driver serial | Denegación de servicio, potencial ejecución de código | ICS-ALERT-12-020-03B (CISA) |
Modbus TCP merece párrafo propio porque es el paradigma del diseño ingenuo: un protocolo sin autenticación de serie. Si el paquete llega a la IP del PLC por el puerto 502, el PLC obedece. Ni usuario, ni contraseña, ni token. La única defensa posible es segmentar la red. Y sin embargo, los trabajos de grado universitarios que revisé en el análisis de infraestructuras venezolanas documentan configuraciones donde la red OT convive sin segmentación seria con la red corporativa.
07. Casos reales: TRITON, Havex y CVE-2020-15782
Estudiar ataques reales contra ICS enseña cosas que ninguna teoría da: vectores de entrada, técnicas de persistencia y, sobre todo, intenciones. Los tres casos que siguen no son hipótesis mías: son incidentes documentados por agencias gubernamentales y empresas de seguridad.
7.1 TRITON (2017): atacar el sistema de seguridad
TRITON (también llamado TRISIS) golpeó los controladores de seguridad Triconex de Schneider Electric en una petroquímica de Oriente Medio. Su singularidad es brutal: no iba a por el control de producción, sino por el Sistema Instrumentado de Seguridad (SIS), la última línea de defensa que debe detener la planta cuando el proceso se sale de rango. Reprogramando la lógica del SIS, los atacantes tenían el camino abierto a una catástrofe física.
Lección para el exploit developer: el objetivo más jugoso no siempre es el PLC de producción. Los sistemas de seguridad, poco monitorizados y casi sin actualizaciones, son blancos ideales. Un shellcode que retoque la lógica de seguridad puede dormir indefinidamente hasta que las condiciones del proceso activen el escenario de peligro… momento en el que el SIS, ya comprometido, no hará nada.
7.2 Havex RAT (2013-2014): reconocimiento a través de OPC
Havex aportó una táctica que redefinió el reconocimiento en ICS: instalado en la red corporativa (vector inicial: phishing por email), usaba el estándar OPC para inventariar dispositivos industriales. Se conectaba a servidores OPC vía DCOM y recolectaba CLSID, nombre del servidor, ID del programa, versión de OPC, datos del proveedor, estado de ejecución, número de grupos y ancho de banda del servidor.
Lección para el exploit developer: reconocer la red no requiere tocar el PLC. Los protocolos estándar tipo OPC son una mina de información arquitectónica, y un exploit que incluya módulo de enumeración OPC puede cartografiar toda la red OT antes del golpe principal.
7.3 CVE-2020-15782 (2021): el sandbox escape que lo cambió todo
El hallazgo de Tal Keren en Claroty Team82 partió en dos la historia de la explotación de PLCs. Quedó demostrado que:
- La VM no es infranqueable: un buffer overflow en una operación puntual de la VM bastó para escribir fuera del sandbox.
- El firmware es accesible: invirtiendo el protocolo S7comm+ y el formato de actualización .upd se extrae y analiza el firmware completo.
- La ejecución nativa es posible: parcheando en memoria un opcode de la VM, el shellcode ARM/MIPS inyectado corre en cuanto el sistema invoca esa instrucción.
- La detección es casi imposible: el código a nivel de kernel no aparece ni en las herramientas de diagnóstico del PLC ni en TIA Portal.
Siemens corrigió el fallo en 2021 con actualizaciones de firmware para S7-1200 y S7-1500. Aun así, el caso dejó claro que la arquitectura sandbox + VM + kernel de los PLCs modernos cae ante ataques sofisticados. Para el defensor la receta no cambia: firmware siempre al día, red OT segmentada y vigilancia del tráfico S7comm buscando anomalías.
08. Guía de laboratorio ICS: hardware, simuladores y red OT
Levantar un laboratorio ICS funcional es el paso más importante —y el más caro— para practicar exploit development en OT. Propongo una arquitectura escalable: empieza en software gratuito y crece hasta hardware real, siempre operando detrás de la infraestructura ofuscada documentada en WireGuard + udp2raw.
8.1 Arquitectura del laboratorio
8.2 Componentes y costos
| Componente | Software/Hardware | Propósito en el Laboratorio | Costo Aprox. (USD) |
|---|---|---|---|
| Nodo A (VPS) | Ubuntu 22.04 LTS + WireGuard + udp2raw | Puerta de enlace ofuscada. Único punto de entrada desde Internet. | ~$6/mes (VPS mínimo 1GB RAM) |
| Nodo B (Hipervisor) | PC con 32GB RAM + VirtualBox 7.x | Host de todas las VMs del laboratorio. Corre redsocks + dnscrypt-proxy + dnsmasq. | Hardware propio (~$800-1500) |
| PLC Simulado MC5 | PG 2000 + PC Simu | Target legacy para exploits AWL en S5. Pruebas de shellcode básico y STUEB. | Gratuito (abandonware) |
| PLC Simulado MC7 | WinSPS-S7 V6 | Target principal para exploits AWL/MC7 en S7-300. Compilación y desensamblado de bytecode. | Gratuito (demo funcional) |
| PLC Real S5-95U | Siemens S5-95U + módulos E/S + cable AS511 | Validación en hardware real de exploits AWL. Pruebas de STUEB y corrupción de pila de módulos. | ~$200-500 (eBay, usado) |
| PLC Real S7-300 | Siemens S7-300 (CPU 314/315) + MPI-USB adapter | Target para exploits MC7. Extracción de firmware vía S7comm. Análisis con JEB y rz-libmc7. | ~$300-800 (eBay, usado) |
| PLC Real S7-1200 | Siemens S7-1200 (CPU 1214C o similar) | Target para sandbox escape (CVE-2020-15782). Extracción de firmware .upd. Análisis de kernel ADONIS. | ~$400-900 (eBay, usado) |
| Estación Ingeniería | Windows 10 LTSC + TIA Portal V17 + STEP5 | Software de programación oficial de Siemens. Necesario para compilar y descargar código a PLCs reales. | Licencia TIA Portal (consultar Siemens) |
| Kali Linux | Kali 2024.x + toolchains ARM/MIPS | Estación de ataque ofensivo. Corre ROPgadget, AFL++, Wireshark, Metasploit, rz-libmc7. | Gratuito |
| REMnux | REMnux 7 + INetSim + fakedns | Simulación de servidores C2 falsos. Redirección DNS para entornos de prueba. | Gratuito |
| QEMU ARM/MIPS | QEMU system-arm / system-mips | Emulación del procesador del PLC para pruebas de shellcode nativo sin hardware real. | Gratuito |
8.3 Red OT segmentada
La red OT tiene que quedar completamente aislada del host y de Internet. En VirtualBox se consigue con una red Host-Only dedicada:
La Kali no debe salir a Internet directamente desde la red OT. Todo el tráfico de salida va por el túnel ofuscado hacia el Nodo A: iptables en el Nodo B redirige lo de Kali a través de redsocks y el proxy residencial, tal y como documenta la infraestructura de túnel ofuscado. Y sobre todo: nunca se pruebe nada ofensivo contra PLCs conectados a Internet sin autorización explícita y sin esa capa de ofuscación.
09. Workshop 1: primer shellcode AWL
Objetivo
Escribir, compilar e inyectar un shellcode AWL mínimo que fuerce una salida digital sin pedirle permiso a la lógica original. El taller valida la primitiva madre de todo exploit en PLC: dominar el RLO y escribir en salidas.
Entorno
- PLC simulado: WinSPS-S7 V6 (MC7) o PG 2000 + PC Simu (MC5).
- Editor AWL: cualquiera de texto; la compilación con STEP5, STEP7 o el propio WinSPS-S7.
- Análisis: rz-libmc7 para verificar el bytecode generado y JEB Pro (demo) para decompilarlo a pseudo-C.
- Target: un programa original sencillo (algo como U E 2.0 ; = A 3.0) que modificaremos para colarle el shellcode.
Procedimiento
- Escribir el shellcode: crear un bloque nuevo (PB 200 en S5, FC 200 en S7) con el shellcode de la Sección 4.1.
- Compilar: generar el programa con STEP5/WinSPS-S7, comprobar que no hay errores de sintaxis y exportar el bloque compilado en binario (Formato 1 LE o Formato 2 BE según la documentación de JEB).
- Analizar el bytecode: abrir el bloque con rz-libmc7 y verificar la correspondencia instrucción AWL ↔ opcode MC7. Documentar los opcodes para el futuro.
- Cargar en el PLC: transferir el programa al PLC simulado.
- Torcer el OB1: insertar una llamada incondicional al PB/FC 200 al principio del ciclo de scan:
// OB1 modificado: shellcode se ejecuta en cada ciclo de scan SPA PB 200 // S5: Salto incondicional al shellcode // UC FC 200 // S7: Llamada incondicional a función // ... lógica original del programa (nunca se ejecuta) ... BE
- Verificar: arrancar el PLC en modo RUN. Las salidas A 3.0, A 3.1 y A 3.2 deben encenderse al instante, sin tocar ninguna entrada física.
Análisis de Resultados
El truco funciona porque SET clava el RLO a 1 sin evaluar nada. Es el gemelo del mov eax, 1; mov [output], eax; ret de cualquier exploit x86. La diferencia sustancial es que aquí "encender una salida" tiene consecuencias físicas de verdad: puede abrir una válvula, arrancar un motor o desarmar un sistema de seguridad.
Extensión del taller: usar rz-libmc7 para retocar directamente el bytecode del bloque compilado —sin pasar por el compilador— y comprobar que el PLC ejecuta el bytecode alterado. Así se demuestra que la inyección directa de bytecode es viable de punta a punta.
10. Workshop 2: ROP con bloques MC7
Objetivo
Construir una cadena ROP a partir de bloques de función que ya existen en el firmware, ejecutando acciones sin inyectar ni un byte de código nuevo. El taller demuestra el bypass de protecciones de memoria que impiden ejecutar en regiones de datos.
Entorno
- PLC simulado: WinSPS-S7 V6 con firmware que incluya funciones reutilizables (bibliotecas IEC estándar).
- Herramientas: JEB Pro para localizar gadgets en los bloques del firmware, rz-libmc7 para verificar la secuencia de bytecode y una calculadora de offsets para armar la cadena.
Procedimiento
- Identificar gadgets: buscar con JEB Pro bloques de función (FB) del firmware que hagan operaciones útiles y terminen en BE. Las bibliotecas IEC estándar (FC3, FC4, FC5…) son un criadero de gadgets.
// Gadget FB 50: Carga constante en AKKU1 y transfiere a salida // (Este es un ejemplo conceptual; los gadgets reales dependen del firmware) FB 50 (existente en el firmware del PLC, identificado con JEB) L KH 00FF // Carga 0x00FF en AKKU1 (word hexadecimal) T AW 0 // Transfiere al word de salida AW 0 (A 0.0 - A 0.7) BE // "Ret": fin de bloque, vuelve al llamante
- Encadenar: montar la cadena en el OB1 modificado:
// Cadena ROP: FB 50 → FB 51 → FB 52 // La ejecución fluye: gadget1 → gadget2 → gadget3 → programa original UC FB 50 // Gadget 1: fuerza A 0.0-0.7 a 0xFF UC FB 51 // Gadget 2: fuerza A 1.0-1.7 a 0xFF UC FB 52 // Gadget 3: escribe en marca de memoria M 100.0 (persistencia) BE // Fin del OB1 modificado
- Ejecutar y verificar: las salidas deben activarse sin que el programa original contenga instrucción alguna que lo haga. Con rz-libmc7 se comprueba que el bytecode refleja la secuencia esperada de UC (llamadas incondicionales).
Este taller es conceptual en la capa MC7: la búsqueda automatizada de gadgets en bytecode no tiene herramientas maduras al nivel de ROPgadget para ELF/PE. Identificar gadgets es trabajo manual, leyendo el AWL de cada FB existente con JEB Pro. Ahora bien, la estrategia híbrida —buscarlos en el binario ARM/MIPS del firmware con ROPgadget, como expliqué en la Sección 5.2— sí es plenamente viable y fue el camino de Claroty en CVE-2020-15782.
11. Workshop 3: fuzzing de Modbus TCP y S7comm
Objetivo
AFL++ o un fuzzer casero contra la implementación de Modbus TCP (502) y S7comm (TCP 102) de un PLC simulado, para cazar crashes y analizar después si son explotables.
Entorno
- PLC simulado: WinSPS-S7 V6 con Modbus TCP expuesto en 192.168.100.50:502 y S7comm en 192.168.100.50:102.
- Kali Linux: 192.168.100.10 con AFL++, Wireshark (dissectors de Modbus y S7comm) y herramientas de red.
- Wireshark: capturar tráfico legítimo que servirá de semillas al fuzzer.
Procedimiento
- Capturar tráfico legítimo: con Wireshark en Kali, registrar una sesión Modbus TCP y otra S7comm entre la estación de ingeniería (192.168.100.20) y el PLC simulado (192.168.100.50). Interesa identificar lecturas de coils, escrituras de registros y comandos STOP/RUN.
- Extraer semillas: exportar los paquetes capturados como binario para alimentar el fuzzer.
- Configurar AFL++ con harness en Python:
// Harness de fuzzing para Modbus TCP (Python + sockets) // Compatible con AFL++ mediante entrada stdin import socket import sys # Leer input mutado de AFL++ data = sys.stdin.buffer.read() # Conectar al PLC simulado (Modbus TCP) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(2.0) try: sock.connect(('192.168.100.50', 502)) sock.send(data) response = sock.recv(1024) sock.close() except Exception as e: # Crash detectado: AFL++ registra este input como interesante sys.exit(1) # Señal de crash para AFL++
- Lanzar AFL++: arrancar el fuzzer con las semillas extraídas y esperar crashes, vigilando reinicios inesperados o cambios de estado del PLC simulado.
- Triaje de crashes: reproducir cada crash manualmente con un script Python dedicado, analizar con Wireshark la respuesta (o el silencio) del PLC y clasificar el fallo: buffer overflow, null pointer dereference, use-after-free, etc.
Resultados Esperados
Contra un PLC simulado sin hardening, lo razonable es toparse con crashes de estos tipos:
- Longitud de paquete excesiva: buffer overflow en el parser Modbus o S7comm.
- Function codes inválidos: manejo incorrecto de códigos de función no implementados en Modbus.
- Direcciones de registro fuera de rango: lectura/escritura más allá del mapa de memoria Modbus, tocando potencialmente regiones protegidas (primo hermano de CVE-2020-15782, pero en la capa de protocolo).
12. Workshop 4: cadena completa con sandbox escape
Objetivo
Ensayar una cadena de explotación inspirada en CVE-2020-15782 que (1) comprometa una estación de ingeniería mediante un drive-by en Chrome (CVE-2020-6507), (2) desde ahí pise la red OT y descubra un S7-1200, (3) explote el sandbox escape para escribir shellcode ARM en memoria protegida del kernel ADONIS y (4) ejecute código nativo que altere el proceso físico.
Entorno
- Windows 10: 192.168.100.20 con Chrome <83 (vulnerable a CVE-2020-6507) y TIA Portal instalado.
- Kali Linux: 192.168.100.10 como servidor del exploit CVE-2020-6507 y centro de comando post-explotación.
- REMnux: 192.168.100.40 con INetSim + fakedns para redirección DNS e Internet falsa.
- PLC S7-1200 simulado: 192.168.100.30 en QEMU ARM con firmware vulnerable a CVE-2020-15782 (anterior al parche de 2021).
- PLC S7-1200 físico: 192.168.100.80 (opcional, para validar en hardware real).
Procedimiento
- Preparar el servidor de exploit CVE-2020-6507: servir desde Kali la página HTML que explota el out-of-bounds write de V8. El JavaScript del exploit (documentado en mi laboratorio de exploit development) ejecuta shellcode en el contexto del navegador.
- Redirección DNS con fakedns: configurar en REMnux fakedns para que cualquier dominio que pregunte el Windows 10 acabe resolviéndose a la IP de Kali (192.168.100.10).
- Lanzar el drive-by: desde el Windows 10 vulnerable, navegar a cualquier URL; fakedns manda el tráfico a Kali, que sirve el exploit de CVE-2020-6507. El shellcode del navegador abre una reverse shell hacia Kali (192.168.100.10:4444).
- Reconocimiento post-explotación: desde esa reverse shell en Windows 10:
- Enumerar la red OT (192.168.100.0/24) con herramientas nativas de Windows (ping sweep, netstat, arp).
- Localizar el S7-1200 en 192.168.100.30:102 (puerto S7comm abierto).
- Leer la versión de firmware del PLC mediante consultas S7comm.
- Explotar CVE-2020-15782: desde la reverse shell, lanzar un script Python que:
- Se conecte al PLC por S7comm (TCP 102).
- Envíe la secuencia de paquetes que dispara el desbordamiento de búfer en la operación vulnerable de la VM.
- Escriba el shellcode ARM en una región protegida del kernel ADONIS.
- Parchee un opcode de la VM para que su invocación salte al shellcode.
- Verificar: el shellcode ARM ya corriendo en el PLC debe alterar el proceso físico. En el laboratorio se comprueba con un LED en la salida A 3.0 que cambia de estado: prueba de que el ataque nacido en el navegador llegó hasta el procesador ARM del autómata y movió una salida.
El taller presupone un firmware S7-1200 vulnerable (anterior al parche de 2021). Siemens lo corrigió vía actualización de firmware. Para laboratorio valen dos caminos: comprar un S7-1200 de segunda mano con firmware antiguo, o emular con QEMU una imagen vulnerable. Nunca intentar explotar esta vulnerabilidad contra PLCs en producción sin autorización explícita del propietario.
Ojo también con CVE-2020-6507: solo es explotable remotamente con el sandbox de Chrome deshabilitado. En el laboratorio basta lanzar Chrome con --no-sandbox para validar la cadena completa, o encadenar un sandbox escape adicional si se dispone de uno.
13. Integración con la infraestructura ofuscada
Todos los talleres anteriores asumen algo: que quien opera el laboratorio no quiere enseñar su IP real al hacer pruebas contra PLCs conectados a Internet —descargar firmwares, consultar repositorios de vulnerabilidades o hablar con equipos remotos autorizados—. Para eso está exactamente la infraestructura de WireGuard + udp2raw + Proxy Residencial:
- Nodo A (VPS público): única puerta de entrada desde Internet, con WireGuard + udp2raw en modo servidor (faketcp:443, ofuscación XOR).
- Nodo B (Laboratorio local): conecta al Nodo A vía udp2raw; todo su tráfico de salida pasa por redsocks → SOCKS5 residencial, tapando la IP real.
- Kali Linux (VM en Nodo B): iptables captura su tráfico hacia Internet y lo manda por el proxy residencial; cualquier escaneo, exploit o conexión reversa hacia objetivos externos luce la IP del proxy, no la del laboratorio.
- Red OT interna (192.168.100.0/24): tráfico directo, sin proxy, para minimizar latencia al hablar con PLCs simulados y reales.
La decisión de diseño que sostiene todo esto: la red OT interna (192.168.100.0/24) jamás pasa por el proxy. Solo el tráfico que abandona el laboratorio rumbo a Internet se ofusca. Así, los exploits que conversan dentro de la red OT disfrutan de latencia mínima, mientras que cualquier comunicación hacia fuera —descargas de herramientas, consultas a repositorios de CVE, canal C2— viaja blindada por el túnel ofuscado y el proxy residencial.
14. Automatización del laboratorio
Siguiendo la filosofía de infraestructura como código (IaC) del artículo del túnel ofuscado, el laboratorio ICS entero cabe en un script Bash que se encarga de:
- Verificar dependencias: VirtualBox 7.x, QEMU (system-arm, system-mips), Python 3.x con pwntools, AFL++, ROPgadget, rz-libmc7, JEB Pro (opcional).
- Crear las VMs: importando appliances preconfiguradas de Kali, Windows 10, REMnux y el PLC simulado.
- Configurar la red OT: interfaz Host-Only vboxnet0 e IPs estáticas asignadas a cada VM.
- Instalar las herramientas de reversing: clonar y compilar rz-libmc7 desde GitHub, bajar JEB Pro (demo) e instalar los dissectors de Wireshark para Modbus y S7comm.
- Levantar el túnel ofuscado: WireGuard + udp2raw en Nodo B, con redsocks + dnscrypt-proxy + dnsmasq para la salida blindada.
- Comprobar conectividad: ping bidireccional entre todas las VMs, handshake Modbus TCP con el PLC simulado, handshake S7comm y resolución DNS sobre HTTPS.
- Generar claves SSH y configurar el acceso: administración remota del laboratorio a través del túnel ofuscado.
Este script es la evolución natural del despliegue de 90 segundos del túnel ofuscado, extendido con el entorno ICS completo. La meta: que cualquier investigador clone el repositorio, lance ./deploy_ics_lab.sh --mode full y tenga el laboratorio OT funcionando en minutos, listo para los cuatro talleres.
15. Roadmap de la serie
Este artículo abre una serie de posts prácticos que irán profundizando en cada taller. Lo que tengo planificado:
| # | Post | Contenido | Dependencias | Estado |
|---|---|---|---|---|
| 1 | Shellcode AWL/MC7 en la Práctica | Desarrollo de 5 shellcodes con complejidad creciente: encender LED, puerta trasera con contador, bypass de verificación de seguridad, falso sensor, y denegación de servicio del ciclo de scan. Incluye análisis del bytecode generado con rz-libmc7 y JEB. | PLC simulado (WinSPS-S7), rz-libmc7, JEB Pro demo | En progreso |
| 2 | Reversing de Firmware ARM/MIPS en PLCs Siemens | Extracción de firmware .upd de un S7-1200, análisis con Ghidra + scripts de firmware, identificación de la capa de traducción MC7+→ARM, y documentación de la estructura del kernel ADONIS. | PLC S7-1200 físico, Ghidra, scripts de extracción de firmware | Planificado |
| 3 | ROP sobre ARM en Firmware de PLC: Replicando CVE-2020-15782 | Búsqueda automatizada de gadgets ARM en firmware S7-1200 con ROPgadget, construcción de cadena ROP funcional, y demostración de sandbox escape en entorno controlado. | Firmware extraído, ROPgadget, QEMU ARM, PLC S7-1200 vulnerable | Planificado |
| 4 | Fuzzing de Protocolos Industriales: Resultados y Críticas | Resultados del fuzzing de Modbus TCP, S7comm y Profinet sobre PLCs simulados y reales. Documentación de crashes encontrados, análisis de explotabilidad, y recomendaciones de hardening. | AFL++, PLC simulado/real, Wireshark | Planificado |
| 5 | Cadena de Explotación Completa OT: Del Drive-by al Proceso Físico | Ejecución completa del Workshop 4 con hardware real. Documentación de cada etapa de la kill chain con tiempos, herramientas y obstáculos encontrados. | Laboratorio ICS completo, CVE-2020-6507, CVE-2020-15782 | Planificado |
| 6 | Automatización del Laboratorio ICS: Infraestructura como Código | Script completo de despliegue, playbook de Ansible, y guía de hardening del laboratorio. Integración con CI/CD para pruebas automatizadas de exploits. | Todos los componentes del laboratorio | Planificado |
16. Conclusión: el estado del arte en explotación de PLCs
El exploit development en OT ha crecido mucho desde los tiempos de Stuxnet. La publicación de CVE-2020-15782 por Claroty Team82 en 2021 marcó el antes y el después: incluso los PLCs modernos, con su sandbox y su kernel propietario, caen ante ataques que combinan buffer overflow en la VM, escape del sandbox y ejecución de código nativo ARM/MIPS.
Las herramientas también han madurado. JEB Pro decompila MC7 a pseudo-C entendiendo los formatos binarios de Siemens. rz-libmc7 aporta desensamblado open-source del bytecode. Ghidra cubre el firmware ARM/MIPS de debajo. Y veteranas como ROPgadget y AFL++ encajan perfectamente en el mundo OT cuando se configuran como toca.
Aun así, la barrera de entrada sigue siendo real. Comprar hardware —S7-1200, S7-300, módulos de E/S, cables de programación— cuesta dinero; extraer firmware puede exigir habilidades de hardware hacking; y validar exploits sobre equipo real arriesga aparatos caros. Con esta guía y la serie que inaugura intento rebajar esa valla proponiendo una metodología que arranca gratis y escala por fases.
Al final, los principios son los mismos que en la explotación binaria clásica: conocer la arquitectura, encontrar primitivas de control de flujo, encadenar ROP y esquivar mitigaciones. Lo que muta es el escenario: en OT un exploit exitoso no termina en una shell remota sino en un proceso físico manipulado, con consecuencias que pueden ser catastróficas. Esa responsabilidad —del atacante y del defensor por igual— es lo que hace de este campo uno de los más duros y más relevantes de la seguridad actual.
Al publicar esto, el laboratorio ICS está en fase de montaje: talleres 1 y 2 validados sobre PLC simulado (WinSPS-S7 V6) y analizados con rz-libmc7; taller 3 con AFL++ en configuración y harness listo para Modbus TCP; taller 4 pendiente de conseguir un S7-1200 con firmware vulnerable a CVE-2020-15782 (parcheada en 2021) y de un sandbox escape para Chrome (CVE-2020-6507 exige --no-sandbox). El script de automatización sigue en desarrollo y el avance se contará en los posts del roadmap.
/¿Quieres seguir bajando por la madriguera del OT?
Esta pieza es la bisagra entre el reversing de PLCs, el exploit development clásico y la infraestructura que sostiene todo el trabajo ofensivo del portafolio. Si te quedaste con ganas de más, estas tres lecturas completan el círculo.
17. Referencias
- PLC Siemens S5/S7: Arquitectura MC7, AWL y la Deficiencia Estructural de Logs (tty503.com)
- Laboratorio de Exploit Development: Windows Internals, ROP y Shellcode (tty503.com)
- Infraestructura Ofuscada: WireGuard + udp2raw + Proxy Residencial (tty503.com)
- analystty: Automatizando el Triage de Malware PE (tty503.com)
- Pentesting a una Web API Financiera: IDOR y Fuga de Datos (tty503.com)
- PNF Software — JEB: PLC Decompiler (Soporte para Simatic S7)
- JEB Decompiler Blog — Reversing Simatic S7 PLC Programs (Nicolas Falliere, 2022)
- PNF Software — Simatic S7 STL Opcodes (Referencia rápida de instrucciones MC7)
- rizinorg/rz-libmc7 — Library to disassemble MC7 bytecode for Siemens PLC SIMATIC S7-300 and S7-400
- Claroty Team82 — The Race to Native Code Execution in PLCs (CVE-2020-15782, Tal Keren, 2021)
- SEC Consult — Reverse Engineering Architecture & Pinout PLC
- Matloff & Franklin — Introducción a la Microprogramación
- UTN — Arquitectura de Computadoras: Unidad 6 — Microprogramación
- Habr — Ingeniería inversa en PLC Siemens S7-300
- RUB-SysSec — Siemens S7 Bootloader (ejecución no invasiva de código en PLCs)
- TIB AV-Portal — Estructura en memoria de MC7
- ROP Emporium — Learn Return-Oriented Programming
- pwn.college — Cybersecurity Education Platform
- Felix Cloutier — x86 and AMD64 Instruction Reference
- x64.syscall.sh — Linux System Call Table
- ARM Instruction Set Reference Guide
- CISA — ICS Alert: Schneider Electric Modicon Quantum PLC Vulnerabilities
- Malpedia — Havex RAT
- DEVCORE — Streaming Vulnerabilities from Windows Kernel (Angelboy, 2024)
- MITRE ATT&CK — Enterprise Matrix (Tácticas y Técnicas)
- ired.team — Finding all RWX Protected Memory Regions
- Siemens — Manual de transición STEP5 a STEP7
- Siemens — Simatic Statement List (STL) for S7-300 and S7-400 Programming — Function Manual
- Siemens — SSA-434534: Vulnerabilidad de bypass de protección de memoria en SIMATIC S7-1200 y S7-1500 (CVE-2020-15782)