Diseccionando Comunicaciones Modbus TCP de OpenPLC con Wireshark

La primera fase de cualquier ataque consiste en un reconocimiento del entorno de cara a poder identificar posibles vulnerabilidades y objetivos disponibles. En nuestro caso se comienza examinando el tráfico de la red con Wireshark.

Prerequisitos (clik para extender)

Diseccionando las Comunicaciones de OpenPLC

Captura de tráfico

Para capturar el tráfico entre OpenPLC y FactoryIO, desde la máquina atacante se abre un nuevo terminal y ejecutamos el comando:

sudo wiresahrk

Con Wireshark abierto, es recomendable añadir un nuevo filtro que facilite la visualización de los datos. Para ello, se va a mostrar sólo el tráfico TCP correspondiente al puerto 502 (puerto por defecto de Modbus TCP).

Filtro en Wireshark para observar solo el tráfico Modbus TCP
Filtro en Wireshark para observar solo el tráfico Modbus TCP

Una vez configurado Wireshark y ya en modo escucha, se accede al portal web de OpenPLC y se selecciona “Start PLC”. A partir de este momento se estará viendo el trafico ModbusTCP de la red. Tras unos segundos se habrán capturado datos suficientes para poder analizar las comunicaciones de OpenPLC

Nota:

En caso de no detectar ningún tráfico, es recomendable comprobar que el adaptador de red se ha configurado correctamente para permitir el modo promiscuo.

Captura del tráfico de red con Wireshark
Captura del tráfico de red con Wireshark

Llegados a este punto, se debería tener suficiente información disponible como para poder analizar cómo se comunican OpenPLC con FactoryIO.

 

Análisis de la Captura

Con los datos de tráfico capturados, se comienza analizando tanto el bucle de comunicaciones como el contenido de los mensajes.

Análisis del Bucle de Comunicación

Análisis del bucle de comunicaciones de OpenPLC
Análisis del bucle de comunicaciones de OpenPLC

Mediante observación directa de la captura de tráfico, se puede comprobar que:

La primera comunicación entre OpenPLC y FactoryIO comienza con un 3 way handshake:

  • OpenPLC -> FactoryIO: SYN
  • FactoryIO -> OpenPLC: SYN/ACK
  • OpenPLC -> FactoryIO: ACK

A partir de ese momento, se repite cada cierto tiempo la estructura de peticiones-respuesta propia de Modbus TCP mientras el programa siga en ejecución:

  • Lectura de entradas (Read Discrete Inputs)
    • Query OpenPLC -> FactoryIO: pregunta por el estado de los sensores
    • Response FactoryIO -> OpenPLC: responde con el estado de los sensores
    • ACK OpenPLC -> FactoryIO: confirma se ha recibido la respuesta
  • Escritura de salidas (Write Multiple Coils)
    • Query OpenPLC -> FactoryIO: pide modificar los estados de los actuadores
    • Response FactoryIO -> OpenPLC: responde que los actuadores se han modificado correctamente
    • ACK OpenPLC -> FactoryIO: confirma se ha recibido la respuesta
Nota:

El tiempo entre bucles debería coincidir aproximadamente con el valor que se haya configurado como polling period en la pestaña “Settings” de OpenPLC (100 ms en nuestro caso).

En caso de estar utilizando algún registro, el bucle principal contará adicionalmente con un par de peticiones-respuesta para leer los registros y luego modificarlos.

Análisis de las Peticiones y Respuestas de Modbus TCP

Una vez comprendida la lógica general de las comunicaciones, es momento de entrar un más al detalle de cada mensaje para intentar obtener un poco más de información. Haciendo doble click en cada mensaje, es posible acceder a la visión completa del contenido del mismo:

Read Inputs

Petición (izquierda) y respuesta (derecha) de la función “Read Inputs”
Petición (izquierda) y respuesta (derecha) de la función “Read Inputs”
  1. Analizando la capa TCP, se pueden identificar los puertos utilizados en las comunicaciones por ambos dispositivos. Es interesante comprobar que efectivamente FactoryIO utiliza el puerto configurado (502), pero OpenPLC uno aleatorio.
  2. No hay mensaje de ACK entre la petición y la respuesta, los mensajes son secuenciales
  3. Modbus TCP utiliza un ID de transacción que es igual en la pregunta que en la respuesta
  4. El mensaje está dirigido y respondido a la RTU con identificador número 1 (en este caso la única que hay, si hubiese más RTUs este número variaría)
  5. El código de la función Modbus es el 2 (000 0010 en binario), que corresponde con los códigos estándar
  6. Se quieren leer 6 entradas (Bit Count) empezando en el 0 (Reference Number). Esto se debe a que tanto el Driver de FactoryIO como el dispositivo esclavo de OpenPLCse han definido con un total de 6 entradas. La respuesta indica el valor de cada entrada (1 encendida, 0 apagada), pero no da más información respecto a qué corresponde cada valor (puerta de seguridad, detección de caja, etc.)

Write Coils

Petición (izquierda) y respuesta (derecha) de la función “Write Coils”
Petición (izquierda) y respuesta (derecha) de la función “Write Coils”
  1. Analizando la capa TCP, se pueden identificar los puertos utilizados en las comunicaciones por ambos dispositivos. Es interesante comprobar que efectivamente FactoryIO utiliza el puerto configurado (502), pero OpenPLC uno aleatorio.
  2. No hay mensaje de ACK entre la petición y la respuesta, los mensajes son secuenciales
  3. Modbus TCP utiliza un ID de transacción que es igual en la pregunta que en la respuesta
  4. El mensaje está dirigido y respondido a la RTU con identificador número 1 (en este caso la única que hay, si hubiese más RTUs este número variaría)
  5. El código de la función Modbus es el 15 (000 111 en binario), que corresponde con los códigos estándar
  6. Se quieren escribir 5 salidas (Bit Count) empezando en la salida 0 (Reference Number). Esto se debe a que tanto el Driver de FactoryIO como el dispositivo esclavo de OpenPLC se han definido un total de 5 salidas
  7. Esas 5 salidas tendrán un valor de 00. Examinando otros mensajes, se pueden ver valores de 14,16,0c,0d, etc. Esto nos indica que el valor de las salidas se transmite en hexadecimal y para comprobar cuales están encendidas o apagadas es necesario hacer una conversión a binario.
Ejemplos de datos en las peticiones de "Write Coils"
Ejemplos de datos en las peticiones de “Write Coils”
Traducción de los datos en la petición "Write Coils" a salidas del RTU
Traducción de los datos en la petición “Write Coils” a salidas del RTU

 

Resumen de Observaciones

Tras el análisis, es posible hacer las siguientes observaciones:

  • La conexión se inicia mediante un 3 way handshake
  • Los mensajes utilizan la estructura de ACK number y Sequence number propia de las comunicaciones TCP
  • Cada respuesta es respondida por un ACK del destinatario
  • Es posible determinar para qué RTU está dirigido cada mensaje comprobando el campo de Unit Identifier del mensaje
  • Las peticiones y respuestas de Modbus utilizan un número de secuencia (Transaction Identifier) que  aumenta con cada petición (pero no con la respuesta)
  • Es posible identificar los tipos de peticiones y respuestas ya que OpenPLC utiliza los códigos de funciones estándar de Modbus
  • Es posible determinar el estado actual de los sensores analizando los datos del “payload” del mensaje
  • Es posible determinar el estado objetivo de los actuadores analizando los datos del “payload” del mensaje
  • La función “Write Coils” utiliza un mapeo de hexadecimal a binario para representar el estado objetivo de los actuadores
  • No es posible determinar a qué sensor o actuador corresponde cada estado únicamente utilizando la información del payload
  • Este reconocimiento es posible porque Modbus TCP no es un protocolo cifrado

Esta información será de mucho valor a la hora de diseñar y desplegar un método de ataque efectivo más adelante.