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 +

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.

CampoDetalle
Plataformas objetivoSiemens SIMATIC S5-95U, S7-300, S7-400, S7-1200 y S7-1500
BytecodeMC5 (S5) · MC7 (S7-300/S7-400) · MC7+ (S7-1200/S7-1500)
Sustrato hardwareProcesadores ARM o MIPS bajo el kernel propietario ADONIS
Vulnerabilidad centralCVE-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 cadenaCVE-2020-6507: out-of-bounds write en V8, Chrome anterior a 83.0.4103.106
Herramientas de reversingJEB Pro, rz-libmc7 (rizin), Ghidra, ROPgadget, AFL++, Wireshark
Protocolos en juegoS7comm/S7comm+ (TCP 102), Modbus TCP (502), Profinet (capa 2), OPC UA/DA (4840)
Red del laboratorioHost-Only de VirtualBox 192.168.100.0/24, aislada y segmentada
Divulgación Responsable

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:

  1. 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).
  2. 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.
  3. 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.
  4. 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 AWLBitFunciónEquivalente en Explotación
FC (First Check)0Indica 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)1Resultado 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.
STA2Estado del bit direccionado. Refleja el valor del operando consultado.Similar al flag CF (Carry Flag) — indica el estado de la última operación
OR3Indica si una operación AND previa dio 1, necesario para combinar AND con OR en cadenas lógicas.Flag auxiliar para operaciones compuestas
OV4Desbordamiento aritmético: se activa en operaciones de coma flotante o enteros con overflow.OF (Overflow Flag) en x86
OS5Overflow 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, CC16-7Códigos de condición: actualizados por operaciones aritméticas, comparaciones y desplazamientos.Similares a SF (Sign Flag) y PF (Parity Flag)
BR8Binary 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, AKKU232 bits c/uAcumuladores 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, AR232 bits c/uRegistros 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, DB216 bits c/uRegistros 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
Implicación para Explotación: Control del RLO

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.

Software PG 2000 conectado a un emulador de PLC S5 mostrando el editor de AWL
Entorno_Programacion_PG2000 — PG 2000 trabajando contra un emulador de PLC S5: aquí se escribe, compila y depura el AWL. Esa misma lógica, ya convertida en bytecode MC5/MC7, es el blanco del exploit developer.

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 S5AWL S7CondiciónEquivalente x86Uso en Exploit
SPASPAIncondicionalJMPRedirigir ejecución a shellcode
SPBSPBRLO = 1JNE / JNZSalto condicional tras evaluación
SPNSPNRLO = 0JE / JZBypass de verificaciones de seguridad
SPZSPZAKKU1 = 0JZ (tras CMP)Control post-aritmética
SPPSPPAKKU1 > 0JGValidación de contadores
SPMSPMAKKU1 < 0JLDetección de underflow
SPOSPOOV = 1JOCaptura de desbordamiento aritmético
SPSSPSOS = 1Detección de error persistente (latch)
SPRSPRBR = 1Validació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.

// Exploit conceptual: desbordamiento de pila de módulos (STUEB) // FB 100: función recursiva sin condición de parada controlada por el atacante FB 100 // Si el atacante mantiene E 2.0 activa, cada ciclo de scan llama a FB 100 // incrementando el contador de anidamiento hasta STUEB U E 2.0 SPB FB 100 // Salto recursivo controlado por entrada BE
Limitación Práctica

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.

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.

Placa base de un S7-1200 junto a un volcado hexadecimal de su firmware .upd
Firmware_Hardware_S7-1200 — Placa base de un S7-1200 con su procesador visible y el análisis hexagonal del firmware (.upd): dentro conviven el kernel ADONIS y la capa que interpreta MC7+.

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:

  1. Escapara del sandbox de la VM: escribiendo datos arbitrarios en regiones de memoria protegidas, normalmente fuera del alcance del bytecode MC7+.
  2. 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.
  3. Ejecutara código ARM/MIPS nativo: inyectando el shellcode dentro de una estructura interna del kernel ADONIS, alcanzando así privilegios de kernel.
  4. 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.
Esquema del escape de sandbox en la memoria de un PLC S7-1200/1500 por CVE-2020-15782
Esquema_Sandbox_CVE-2020-15782 — A la izquierda, la VM de MC7+ confinada en espacio de usuario; la flecha roja marca la escritura que rebasa el sandbox; a la derecha, el shellcode ARM/MIPS ya instalado en las regiones protegidas del kernel ADONIS.
Implicación para el Laboratorio

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:

Flujo de compilación del código lógico de usuario hacia bytecode MC7/MC7+
Flujo_Compilacion_PLC — Todos los lenguajes (STL/AWL, LD, SCL, FBD) desembocan en el mismo bytecode MC7/MC7+, que luego ejecuta la CPU del autómata. Inyectando en la etapa final se salta la compilación por completo.

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:

Rizin desensamblando un binario MC7 con el plugin rz-libmc7
Analisis_Rizin_Binario — Rizin con rz-libmc7 recorriendo el bytecode de un binario MC7: instrucciones desensambladas y navegación directa sobre el código.
Listado de instrucciones de bajo nivel de un PLC con su correspondencia entre nemónicos y bytes
Desensamblado_Codigo_PLC — Listado de bajo nivel de un programa de PLC: la correlación entre nemónicos AWL y bytes MC7 es exactamente el objeto de estudio del reversing de bytecode.

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ísticax86/AMD64ARMImpacto en Explotación
Conjunto de instruccionesCISC (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 llamadaArgumentos en pila (x86) o RCX,RDX,R8,R9 (AMD64 Windows)Argumentos en R0-R3, retorno en R0. Stack para argumentos adicionalesLos gadgets ROP deben manipular registros diferentes. Pop {r0, pc} es el gadget universal de ARM
Ejecución condicionalSolo 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 registroRIP/EIP no es directamente accesible como registro de propósito generalR15 (PC) es de propósito general. Se puede leer y escribir como cualquier registroLectura/escritura directa del contador de programa facilita el control de flujo
EndiannessLittle-endianBi-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ónRing 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
Referencia de Instrucciones ARM para Explotación

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.

// Shellcode AWL #1: Forzar salida A 3.0 independientemente de la lógica // Longitud: 6 instrucciones. Objetivo: encender LED en byte 3, bit 0 // Equivalente en explotación: mov eax, 1; mov [output], eax; ret // ------------------------------------------------------------ SET // RLO = 1 (fuerza true sin depender de entradas) S A 3.0 // Setea la salida A 3.0 a 1 (LED encendido) SET // RLO = 1 S A 3.1 // Setea la salida A 3.1 a 1 (segundo LED) SET // RLO = 1 S A 3.2 // Setea la salida A 3.2 a 1 (tercer LED) BE // Fin del bloque (equivalente a ret en x86)

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:

// Shellcode AWL #2: Puerta trasera condicional con contador // Si E 2.0 se activa 3 veces, forzar A 3.0 (bypass de seguridad) // ------------------------------------------------------------ U E 2.0 // Verifica entrada de activación ZV Z 10 // Incrementa contador Z10 L Z 10 // Carga valor del contador en AKKU1 L KZ 3 // Carga constante 3 en AKKU2 != F // ¿AKKU1 != 3? (comparación de enteros 16 bits) SPB SKIP // Si no es 3, saltar a SKIP SET // RLO = 1 S A 3.0 // Activar salida (puerta trasera ejecutada) L KZ 0 // Resetear contador para evitar detección S Z 10 // Cargar 0 en Z10 SKIP: BE // Continuar ejecución normal

4.3 Herramientas para el reversing de bytecode MC7

El arsenal para analizar bytecode MC7 ha madurado bastante. Estas son las tres piezas centrales:

HerramientaDesarrolladorFuncionalidadLicenciaTarget
JEB ProPNF SoftwareDesensamblador 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-libmc7rizinorg (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
GhidraNSAFramework 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 SourceFirmware 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.

Documento técnico con la estructura de memoria de un S7-300 y la ubicación del bloque de contraseñas
Estructura_Datos_SDB — Documentación técnica de la estructura de memoria de un S7-300 señalando dónde vive el bloque de contraseñas: saber dónde duermen las credenciales es clave para entender cómo podrían extraerse o modificarse con exploits.
Bytes Prohibidos en MC7

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.

// Gadget ROP #1 en AWL: pop RLO; set salida; ret // FB 200: Equivalente a "pop eax; ret" en x86 FB 200 U M 100.0 // "Pop" del stack lógico: carga M 100.0 en RLO = A 3.0 // Escribe RLO en salida (side effect controlado) BE // "Ret": vuelve al llamante

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:

  1. 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.
  2. Analizar el binario con herramientas estándar: ROPgadget, ropper o Ghidra con scripting en Python.
  3. Montar la cadena ROP que, aliada con la vulnerabilidad de sandbox escape, ejecute código arbitrario en el procesador de verdad.
// Búsqueda de gadgets ARM en firmware de PLC Siemens S7-1200 // Uso de ROPgadget sobre un volcado de firmware .upd $ ROPgadget --binary s7_1200_firmware.upd --arch ARM --thumb // Gadgets encontrados (ejemplos representativos): // 0x00014a7c: pop {r0, pc} → Control de R0 (argumento 1) + salto // 0x00023b18: mov r0, r4; pop {r4, pc} → Movimiento entre registros + ret // 0x000105e0: str r0, [r1]; pop {r4, pc} → Escritura en memoria (útil para parchar) // 0x0000c894: bx lr → Retorno simple (útil para encadenar gadgets)
Barrera de Entrada

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.

ComponenteProtocolo/PuertoVector de AtaqueImpactoCaso Documentado
PLC Siemens S7-300/400S7comm (TCP 102)Inyección de comandos STOP/RUN, descarga/inyección de firmwareParada de planta, modificación de lógica de controlStuxnet (2010)
PLC Siemens S7-1200/1500S7comm+ (TCP 102)Sandbox escape, escritura en memoria protegida, ejecución de código nativo ARM/MIPSControl total del PLC a nivel de kernelCVE-2020-15782 (Claroty, 2021)
Modbus TCPTCP 502Escritura en coils/registers sin autenticación por diseñoManipulación de E/S, falseo de sensores, activación/desactivación de actuadoresMúltiples incidentes no atribuidos
ProfinetEthernet industrial (capa 2)Inyección de tramas, denegación de servicio en tiempo realInterrupción de comunicación determinista entre PLCsInvestigaciones académicas
OPC UA/DATCP 4840, DCOM (135, 445)Enumeración de tags, lectura/escritura remota de variables de procesoFuga de información del proceso industrial, modificación de setpointsHavex RAT (2013-2014)
HMI Magelis/HarmonyHTTP/HTTPS, VNC (5900)Contraseñas por defecto, vulnerabilidades web, exposición de pantalla de operadorControl visual del operador comprometido, comandos no autorizadosAuditorí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 plantaAcceso total a toda la red OT, modificación de lógica en todos los PLCsPatrón de ataque recurrente
Schneider Modicon QuantumModbus TCP (502), Serial Modbus DriverParalización de CPU sin autenticación, buffer overflow en driver serialDenegación de servicio, potencial ejecución de códigoICS-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:

Estado Actual de CVE-2020-15782

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

// Topología del laboratorio ICS (4 niveles de profundidad) // ------------------------------------------------------------ // Nivel 0: Internet → [Nodo A: VPS Puerta de Enlace] // ↓ WireGuard + udp2raw (faketcp:443, XOR) // Nivel 1: [Nodo B: Hipervisor del Laboratorio ICS] // ↓ redsocks + SOCKS5 residencial (salida a Internet) // Nivel 2: Red OT Virtual (192.168.100.0/24, Host-Only VirtualBox) // ├── VM Kali Linux .10 (estación de ataque) // ├── VM Windows 10 .20 (estación de ingeniería + TIA Portal) // ├── VM QEMU ARM/MIPS .30 (emulador de PLC S7-1200) // ├── VM REMnux .40 (INetSim + fakedns) // └── PLC Simulado .50 (WinSPS-S7 V6 / PC Simu) // // Nivel 3: Hardware Físico (opcional, expansión del laboratorio) // ├── Siemens S5-95U + módulos E/S (MC5, CPU 16-bit) // ├── Siemens S7-300 + MPI adapter (MC7, CPU 32-bit) // └── Siemens S7-1200 (MC7+, ARM, kernel ADONIS)

8.2 Componentes y costos

ComponenteSoftware/HardwarePropósito en el LaboratorioCosto Aprox. (USD)
Nodo A (VPS)Ubuntu 22.04 LTS + WireGuard + udp2rawPuerta 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.xHost de todas las VMs del laboratorio. Corre redsocks + dnscrypt-proxy + dnsmasq.Hardware propio (~$800-1500)
PLC Simulado MC5PG 2000 + PC SimuTarget legacy para exploits AWL en S5. Pruebas de shellcode básico y STUEB.Gratuito (abandonware)
PLC Simulado MC7WinSPS-S7 V6Target principal para exploits AWL/MC7 en S7-300. Compilación y desensamblado de bytecode.Gratuito (demo funcional)
PLC Real S5-95USiemens S5-95U + módulos E/S + cable AS511Validació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-300Siemens S7-300 (CPU 314/315) + MPI-USB adapterTarget para exploits MC7. Extracción de firmware vía S7comm. Análisis con JEB y rz-libmc7.~$300-800 (eBay, usado)
PLC Real S7-1200Siemens 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íaWindows 10 LTSC + TIA Portal V17 + STEP5Software de programación oficial de Siemens. Necesario para compilar y descargar código a PLCs reales.Licencia TIA Portal (consultar Siemens)
Kali LinuxKali 2024.x + toolchains ARM/MIPSEstación de ataque ofensivo. Corre ROPgadget, AFL++, Wireshark, Metasploit, rz-libmc7.Gratuito
REMnuxREMnux 7 + INetSim + fakednsSimulación de servidores C2 falsos. Redirección DNS para entornos de prueba.Gratuito
QEMU ARM/MIPSQEMU system-arm / system-mipsEmulación del procesador del PLC para pruebas de shellcode nativo sin hardware real.Gratuito
Jerarquía de bloques organizacionales OB, FC y FB en sistemas SIMATIC S7
Estructura_Bloques_Siemens — Jerarquía de bloques organizacionales (OB), funciones (FC) y bloques de función (FB) en SIMATIC S7. Dominar esta jerarquía define dónde inyectar: los OB son puntos de entrada y los FC/FB son código ejecutable.
Jerarquía de bloques de memoria del PLC y su relación con el sistema operativo
Arquitectura_Memoria_PLC — Jerarquía de bloques de memoria (OB, DB, FC, FB) y su diálogo con el sistema operativo del autómata. La segmentación de memoria (I, Q, M, L, DB, DI) es una rarezza de los PLCs que el exploit developer tiene que aprender a navegar.

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:

# Configuración de red OT en VirtualBox (host) VBoxManage hostonlyif create VBoxManage hostonlyif ipconfig vboxnet0 --ip 192.168.100.1 --netmask 255.255.255.0 # Asignar a cada VM la interfaz Host-Only # VM Kali: 192.168.100.10 (estación de ataque) # VM Windows 10: 192.168.100.20 (estación de ingeniería + TIA Portal) # VM QEMU ARM: 192.168.100.30 (emulador de PLC S7-1200) # VM REMnux: 192.168.100.40 (INetSim + fakedns) # VM PLC Sim: 192.168.100.50 (WinSPS-S7 V6) # PLC S5-95U: 192.168.100.60 (hardware real, si aplica) # PLC S7-300: 192.168.100.70 (hardware real, si aplica) # PLC S7-1200: 192.168.100.80 (hardware real, si aplica)
Aislamiento Real: Crítico para Pruebas con Hardware Real

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

Procedimiento

  1. Escribir el shellcode: crear un bloque nuevo (PB 200 en S5, FC 200 en S7) con el shellcode de la Sección 4.1.
  2. 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).
  3. Analizar el bytecode: abrir el bloque con rz-libmc7 y verificar la correspondencia instrucción AWL ↔ opcode MC7. Documentar los opcodes para el futuro.
  4. Cargar en el PLC: transferir el programa al PLC simulado.
  5. 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
  6. 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

Procedimiento

  1. 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
  2. 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
  3. 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).
Limitación de la Búsqueda de Gadgets en MC7

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

Procedimiento

  1. 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.
  2. Extraer semillas: exportar los paquetes capturados como binario para alimentar el fuzzer.
  3. 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++
  4. Lanzar AFL++: arrancar el fuzzer con las semillas extraídas y esperar crashes, vigilando reinicios inesperados o cambios de estado del PLC simulado.
  5. 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:

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

Procedimiento

  1. 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.
  2. 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).
  3. 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).
  4. 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.
  5. 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.
  6. 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.
Notas sobre la Replicación de CVE-2020-15782

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:

// Flujo de tráfico durante un exploit completo (Workshop 4) // ------------------------------------------------------------ // [Kali] → (tráfico de exploit C2) → [iptables REDSOCKS_TCP] → redsocks → SOCKS5 → Internet (IP residencial) // [Kali] → (escaneo de red OT interna) → 192.168.100.0/24 (tráfico directo, sin proxy) // [Windows 10] ← (drive-by CVE-2020-6507) ← [Kali:80] (servido localmente en red OT) // [Windows 10] → (reverse shell) → [Kali:4444] (conexión directa en red OT) // [Windows 10] → (S7comm explotación) → [PLC S7-1200:102] (tráfico directo en red OT) // [PLC S7-1200] → (shellcode ARM modifica salidas) → [Proceso Físico]

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:

  1. Verificar dependencias: VirtualBox 7.x, QEMU (system-arm, system-mips), Python 3.x con pwntools, AFL++, ROPgadget, rz-libmc7, JEB Pro (opcional).
  2. Crear las VMs: importando appliances preconfiguradas de Kali, Windows 10, REMnux y el PLC simulado.
  3. Configurar la red OT: interfaz Host-Only vboxnet0 e IPs estáticas asignadas a cada VM.
  4. 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.
  5. Levantar el túnel ofuscado: WireGuard + udp2raw en Nodo B, con redsocks + dnscrypt-proxy + dnsmasq para la salida blindada.
  6. Comprobar conectividad: ping bidireccional entre todas las VMs, handshake Modbus TCP con el PLC simulado, handshake S7comm y resolución DNS sobre HTTPS.
  7. 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:

#PostContenidoDependenciasEstado
1Shellcode AWL/MC7 en la PrácticaDesarrollo 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 demoEn progreso
2Reversing de Firmware ARM/MIPS en PLCs SiemensExtracció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 firmwarePlanificado
3ROP sobre ARM en Firmware de PLC: Replicando CVE-2020-15782Bú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 vulnerablePlanificado
4Fuzzing de Protocolos Industriales: Resultados y CríticasResultados 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, WiresharkPlanificado
5Cadena de Explotación Completa OT: Del Drive-by al Proceso FísicoEjecució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-15782Planificado
6Automatización del Laboratorio ICS: Infraestructura como CódigoScript 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 laboratorioPlanificado

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.

Estado del Laboratorio (Junio 2026)

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