miércoles, 6 de noviembre de 2013

Controlando la Planta Nuclear de Springfield desde la casa de Homero: Libmodbus + Python + C

De seguro alguien te dijo que existía un protocolo de comunicaciones de facto para sistemas de control Industriales, conocido como Modbus con más de 30 años de existencia y creado originalmente para implementarse en ambientes cerrados donde la confidencialidad no era una necesidad importante pues el riesgo era mínimo.

Y también conoces al Jefe de Seguridad de la Planta Nuclear de Springfield "Homero Simpson" quién durante un tiempo tuvo la necesidad de controlar desde su casa algunas de las tareas de la planta nuclear.

Figura 1: Homero Simpson - Jefe de Seguridad, Planta Nuclear

Modbus es un protocolo de comunicación serial que transmite la información en claro sin ningún mecanismo de ocultación y usando controles de redundancia cíclica para garantizar la integridad del paquete. Utiliza códigos predefinidos que permiten reconocer el tipo de paquete transmitido su origen y destino y funciona en dominios de broadcast que permiten a todos los equipos conectados recibir el paquete pero solamente el destinatario lo procesará.

Diseñaremos una Unidad Terminal Remota (RTU), haciendo uso de una Raspberry, libmodbus y una aplicación servidor creada en C, así como el paquete WiringPI para controlar los puertos GPIO usando lenguaje C. Vamos a simular la famosa planta nuclear donde usaremos los puertos GPIO para (Des)activar algún sistema en la planta gestionada por una aplicación servidor.

por otro lado, haciendo uso de PyModbus y una aplicación cliente vamos a proponer un servicio de acceso desde el cómodo hogar de Homero que se comunique con la estación servidor y realice las peticiones para controlar los servicios en la planta.

Modbus-server.c es una aplicación que habilitará el servicio Modbus en el puerto 1502 y de acuerdo a las peticiones simplemente activará o desactivará un servicio en el puerto GPIO 7 de la raspberry.

en primer lugar instalamos la librería sobre la plataforma Wheezy Raspbian de Raspberry 


sudo apt-get install libmodbus5 libmodbus-dev

Descargamos el paquete WiringPI y lo instalamos usando git:

git clone git://git.drogon.net/wiringPicd wiringPi  
git pull origin 
./build

Ahora que tenemos las herramientas para usar modbus y acceder a los puertos GPIO mediante programación en C vamos a diseñar nuestro script, basados en el código de Stéphane Raimbault:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <modbus.h>
#include <inttypes.h>
#include <wiringPi.h>
enum {
    TCP,
    RTU
};
int main(int argc, char *argv[])
{
    int socket;
    modbus_t *ctx;
    modbus_mapping_t *mb_mapping;
    int rc;
    int use_backend;
    int nPort = 1502;
    wiringPiSetup();
    pinMode (7, OUTPUT);
    use_backend = TCP;
 ...
        printf("Waiting for TCP connection on Port %i \n",nPort);
        ctx = modbus_new_tcp("127.0.0.1", nPort);
        socket = modbus_tcp_listen(ctx, 1);

        modbus_tcp_accept(ctx, &socket);
        printf("TCP connection started!\n");
    mb_mapping = modbus_mapping_new(MODBUS_MAX_READ_BITS, 0,
                                    MODBUS_MAX_READ_REGISTERS, 0);
    if (mb_mapping == NULL) {
        fprintf(stderr, "Failed to allocate the mapping: %s\n",
                modbus_strerror(errno));
        modbus_free(ctx);
        return -1;
    }
    for(;;) {
        uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
        rc = modbus_receive(ctx, query);
        if (rc >= 0) {
            printf("Received: %" PRIu8 "\n",query[9]);
            if (query[9]==10)
            {
               digitalWrite  (7,HIGH);
               printf ("--- Port 7 set to 1\n");
            }
            if (query[9]==11)
            {
               digitalWrite (7,LOW);
               printf ("--- Port 7 set to 0\n");
            }
            printf("Replying to request.\n");
            modbus_reply(ctx, query, rc, mb_mapping);
        } else {
            /* Connection closed by the client or server */
            //break;
        }
    }
    printf("Quit the loop: %s\n", modbus_strerror(errno));
    modbus_mapping_free(mb_mapping);
    close(socket);
    modbus_free(ctx);
    return 0;
}

Del lado del cliente mgpio-client.py es una aplicación que se conecta con el servidor y envía mediante el protocolo modbus las peticiones para activar o desactivar el servicio. Por supuesto tenemos una etapa previa de acceso con una petición de usuario y contraseña, pero aunque no es el objetivo de esta práctica hablaremos más adelante de las debilidades de algunos sistemas de control o PLC respecto a la seguridad de acceso.

En primer lugar instalamos PiModbus, la librería que nos permitirá usar la pila del protocolo desde código Phyton:

svn checkout http://pymodbus.googlecode.com/svn/trunk/ pymodbus-read-only
cd pymodbus-read-only
python setup.py install
Ahora creamos el código para enviar las peticiones con los valores específicos:

import os
from pymodbus.client.sync import ModbusTcpClient
...
client = ModbusTcpClient('192.168.0.7',1502)
option = 0
while not option == 3:
   os.system('clear')
   print """   Control Menu
   1. Enable System
   2. Disable System
   3. Exit
   """
   option = int(input("Select your option: "))
   rq = client.write_coil(9+option, True)
client.close()
print """Leaving the system ...
   ...
   ...
   Thanks Mr.Homer you deserve a beer"""



Como se observa, la conexión se realiza usando Modbus sobre TCP lo que permite realizar la administración sobre una red amplia como Internet, garantizando el acceso al servicio desde diferentes locaciones... haciendo más fácil la tarea de Homero y la labor de implementación de, digamos .. Carl y Lenny.

Controlando la Planta Nuclear de Springfield desde la casa de Homero: Libmodbus + Python + C 
Controlando la Planta Nuclear de Springfield desde la casa de Homero: La Prueba de Concepto
Controlando la Planta Nuclear de Springfield desde la casa de Homero: Análisis con Wireshark
Controlando la Planta Nuclear de Springfield desde la casa de Homero: Evil Flanders

No hay comentarios:

Publicar un comentario