Sur les traces d'Industrial Ninja : comment les PLC ont été piratés lors des Positive Hack Days 9.

Sur les traces d'Industrial Ninja : comment les PLC ont été piratés lors des Positive Hack Days 9.

Lors de la récente PHDays 9, nous avons organisé une compétition de hacking d'une station de pompage de gaz — un concours. Industrial Ninja. Sur la plateforme, il y avait trois stands avec différents niveaux de sécurité (Pas de sécurité, Faible sécurité, Haute sécurité), émulant le même processus industriel : de l'air était pompé (puis relâché) dans un ballon sous pression.

Malgré les différents niveaux de sécurité, la configuration matérielle des stands était identique : Automate programmable Siemens Simatic série S7-300 ; bouton de déflation d'urgence et instrument de mesure de pression (connectés aux entrées numériques de l'automate (DI)); vannes fonctionnant pour le gonflage et le dégonflage de l'air (connectées aux sorties numériques de l'automate (DO)) — voir l'illustration ci-dessous.

Sur les traces d'Industrial Ninja : comment les PLC ont été piratés lors des Positive Hack Days 9.

L'automate, en fonction des lectures de pression et conformément à son programme, prenait la décision de dégonfler ou de gonfler le ballon (ouvrant et fermant les vannes appropriées). Cependant, tous les stands avaient un mode de contrôle manuel, permettant de gérer l'état des vannes sans aucune restriction.

Les stands différaient par la complexité de l'activation de ce mode : sur le stand non protégé, c'était le plus simple, tandis que sur le stand Haute sécurité, c'était en conséquence plus difficile.

En deux jours, cinq des six tâches ont été résolues ; le participant ayant obtenu la première place a accumulé 233 points (il a consacré une semaine à la préparation du concours). Le podium : 1ère place — a1exdandy, 2ème — Rubikoid, 3ème — Ze.

Cependant, pendant PHDays, aucun des participants n'a réussi à surmonter les trois stands, c'est pourquoi nous avons décidé d'organiser un concours en ligne et au début de juin, nous avons publié la tâche la plus difficile. Les participants avaient un mois pour réaliser la tâche, trouver le drapeau et décrire la solution de manière détaillée et intéressante.

Ci-dessous, nous publions l'analyse de la meilleure solution de la tâche parmi celles envoyées au cours du mois, trouvée par Alexey Kovrizhnykh (a1exdandy) de la société Digital Security, qui a remporté la 1ère place lors du concours pendant PHDays. Ci-dessous, nous reprenons son texte avec nos commentaires.

Analyse initiale

Ainsi, la tâche contenait une archive avec des fichiers :

  • block_upload_traffic.pcapng
  • DB100.bin
  • hints.txt

Le fichier hints.txt contient des informations et des indices nécessaires à la résolution de la tâche. Voici son contenu :

  1. Petrovich m'a dit hier que depuis PlcSim, il est possible de télécharger des blocs dans Step7.
  2. Sur le stand, un automate Siemens Simatic série S7-300 était utilisé.
  3. PlcSim est un émulateur d'automate, permettant d'exécuter et de déboguer des programmes pour les automates Siemens S7.

Le fichier DB100.bin semble contenir un bloc de données DB100 PLC :
00000000: 0100 0102 6e02 0401 0206 0100 0101 0102  ....n...........
00000010: 1002 0501 0202 2002 0501 0206 0100 0102  ...... .........
00000020: 0102 7702 0401 0206 0100 0103 0102 0a02  ..w.............
00000030: 0501 0202 1602 0501 0206 0100 0104 0102  ................
00000040: 7502 0401 0206 0100 0105 0102 0a02 0501  u...............
00000050: 0202 1602 0501 0206 0100 0106 0102 3402  ..............4.
00000060: 0401 0206 0100 0107 0102 2602 0501 0202  ..........&.....
00000070: 4c02 0501 0206 0100 0108 0102 3302 0401  L...........3...
00000080: 0206 0100 0109 0102 0a02 0501 0202 1602  ................
00000090: 0501 0206 0100 010a 0102 3702 0401 0206  ..........7.....
000000a0: 0100 010b 0102 2202 0501 0202 4602 0501  ......".....F...
000000b0: 0206 0100 010c 0102 3302 0401 0206 0100  ........3.......
000000c0: 010d 0102 0a02 0501 0202 1602 0501 0206  ................
000000d0: 0100 010e 0102 6d02 0401 0206 0100 010f  ......m.........
000000e0: 0102 1102 0501 0202 2302 0501 0206 0100  ........#.......
000000f0: 0110 0102 3502 0401 0206 0100 0111 0102  ....5...........
00000100: 1202 0501 0202 2502 0501 0206 0100 0112  ......%.........
00000110: 0102 3302 0401 0206 0100 0113 0102 2602  ..3...........&.
00000120: 0501 0202 4c02 0501 0206 0100

À en juger par le nom, le fichier block_upload_traffic.pcapng contient un dump du trafic de téléchargement de blocs sur le PLC.

Il convient de noter qu'il était un peu plus difficile d'obtenir ce dump de trafic sur le site du concours pendant la conférence. Pour cela, il était nécessaire de comprendre le script du fichier du projet pour TeslaSCADA2. De celui-ci, on pouvait comprendre où se trouvait le dump crypté par RC4 et quelle clé utiliser pour le déchiffrer. Les dumps de blocs de données sur le site pouvaient être obtenus via un client du protocole S7. J'ai utilisé le client démonstration du paquet Snap7 à cet effet.

Extraction des blocs de traitement de signal à partir du dump de trafic

En regardant le contenu du dump, on peut comprendre qu'il contient des blocs de traitement de signal OB1, FC1, FC2 et FC3 :

Sur les traces d'Industrial Ninja : comment les PLC ont été piratés lors des Positive Hack Days 9.

Il est nécessaire d'extraire ces blocs. Cela peut être fait, par exemple, avec le script suivant, après avoir converti le trafic du format pcapng en pcap :

#!/usr/bin/env python2

import struct
from scapy.all import *

packets = rdpcap('block_upload_traffic.pcap')
s7_hdr_struct = '>BBHHHHBB'
s7_hdr_sz = struct.calcsize(s7_hdr_struct)
tpkt_cotp_sz = 7
names = iter(['OB1.bin', 'FC1.bin', 'FC2.bin', 'FC3.bin'])
buf = ''

for packet in packets:
    if packet.getlayer(IP).src == '10.0.102.11':
        tpkt_cotp_s7 = str(packet.getlayer(TCP).payload)
        if len(tpkt_cotp_s7) < tpkt_cotp_sz + s7_hdr_sz:
            continue
        s7 = tpkt_cotp_s7[tpkt_cotp_sz:]
        s7_hdr = s7[:s7_hdr_sz]
        param_sz = struct.unpack(s7_hdr_struct, s7_hdr)[4]
        s7_param = s7[12:12+param_sz]
        s7_data = s7[12+param_sz:]
        if s7_param in ('x1ex00', 'x1ex01'):  # upload
            buf += s7_data[4:]
        elif s7_param == 'x1f':
            with open(next(names), 'wb') as f:
                f.write(buf)
            buf = ''

En étudiant les blocs obtenus, on peut remarquer qu'ils commencent toujours par les octets 70 70 (pp). Maintenant, il faut apprendre à les analyser. L'indice pour la tâche suggère qu'il est nécessaire d'utiliser PlcSim.

Obtention d'instructions lisibles par l'homme à partir des blocs

Pour commencer, essayons de programmer S7-PlcSim en chargeant plusieurs blocs avec des instructions répétées (= Q 0.0) à l'aide du logiciel Simatic Manager, et nous sauvegarderons le fichier obtenu dans l'émulateur PLC en tant qu'exemple.plc. En examinant le contenu du fichier, il est facile de déterminer le début des blocs chargés par la signature 70 70 que nous avons découverte précédemment. Avant les blocs, il semblerait que la taille du bloc soit enregistrée sous forme de valeur little-endian de 4 octets.

Sur les traces d'Industrial Ninja : comment les PLC ont été piratés lors des Positive Hack Days 9.

Après avoir obtenu des informations sur la structure des fichiers PLC, le plan suivant est apparu pour lire les programmes PLC S7 :

  1. À l'aide de Simatic Manager, créons dans S7-PlcSim une structure de blocs similaire à celle que nous avons obtenue du dump. Les tailles des blocs doivent correspondre (ce qui est réalisé en remplissant les blocs avec le nombre nécessaire d'instructions) ainsi que leurs identifiants (OB1, FC1, FC2, FC3).
  2. Sauvegardons le PLC dans un fichier.
  3. Remplaçons le contenu des blocs du fichier obtenu par les blocs du dump de trafic. Nous définissons le début des blocs par la signature.
  4. Nous chargeons le fichier obtenu dans S7-PlcSim et regardons le contenu des blocs dans Simatic Manager.

Le remplacement des blocs peut être effectué, par exemple, avec le code suivant :

with open('original.plc', 'rb') as f:
    plc = f.read()
blocks = []
for fname in ['OB1.bin', 'FC1.bin', 'FC2.bin', 'FC3.bin']:
    with open(fname, 'rb') as f:
        blocks.append(f.read())

i = plc.find(b'pp')
for block in blocks:
    plc = plc[:i] + block + plc[i+len(block):]
    i = plc.find(b'pp', i + 1)

with open('target.plc', 'wb') as f:
    f.write(plc)

Alexey a choisi un chemin, peut-être plus complexe, mais néanmoins correct. Nous avions supposé que les participants utiliseraient le programme NetToPlcSim pour pouvoir communiquer avec PlcSim sur le réseau, chargeraient les blocs dans PlcSim via Snap7, puis téléchargeraient ces blocs sous forme de projet depuis PlcSim à l'aide de l'environnement de développement.

En ouvrant le fichier obtenu dans S7-PlcSim, on peut lire les blocs réécrits à l'aide de Simatic Manager. Les principales fonctions de contrôle des dispositifs sont enregistrées dans le bloc FC1. L'attention se porte particulièrement sur la variable #TEMP0, qui, lorsqu'elle est activée, semble transférer le contrôle du PLC en mode manuel sur la base des valeurs de la mémoire binaire M2.2 et M2.3. La valeur de #TEMP0 est définie par la fonction FC3.

Sur les traces d'Industrial Ninja : comment les PLC ont été piratés lors des Positive Hack Days 9.

Pour résoudre la tâche, il est nécessaire d'analyser la fonction FC3 et de comprendre ce qu'il faut faire pour qu'elle retourne une valeur logique de un.

Les blocs de traitement des signaux PLC sur le banc Low Security au site du concours étaient configurés de manière similaire, mais pour définir la valeur de la variable #TEMP0, il suffisait d'écrire la ligne my ninja way dans le bloc DB1. La vérification de la valeur dans le bloc était claire et ne nécessitait pas de connaissances approfondies du langage de programmation des blocs. Il est évident qu’au niveau High Security, obtenir un contrôle manuel sera nettement plus compliqué et nécessitera de comprendre les subtilités du langage STL (l'un des moyens de programmer le PLC S7).

Inversion du bloc FC3

Contenu du bloc FC3 en représentation STL :

      L     B#16#0
      T     #TEMP13
      T     #TEMP15
      L     P#DBX 0.0
      T     #TEMP4
      CLR   
      =     #TEMP14
M015: L     #TEMP4
      LAR1  
      OPN   DB   100
      L     DBLG
      TAR1  
      <=D   
      JC    M016
      L     DW#16#0
      T     #TEMP0
      L     #TEMP6
      L     W#16#0
      I   
      JC    M00d
      L     P#DBX 0.0
      LAR1  
M00d: L     B [AR1,P#0.0]
      T     #TEMP5
      L     W#16#1
      ==I   
      JC    M007
      L     #TEMP5
      L     W#16#2
      ==I   
      JC    M008
      L     #TEMP5
      L     W#16#3
      ==I   
      JC    M00f
      L     #TEMP5
      L     W#16#4
      ==I   
      JC    M00e
      L     #TEMP5
      L     W#16#5
      ==I   
      JC    M011
      L     #TEMP5
      L     W#16#6
      ==I   
      JC    M012
      JU    M010
M007: +AR1  P#1.0
      L     P#DBX 0.0
      LAR2  
      L     B [AR1,P#0.0]
      L     C#8
      *I    
      +AR2  
      +AR1  P#1.0
      L     B [AR1,P#0.0]
      JL    M003
      JU    M001
      JU    M002
      JU    M004
M003: JU    M005
M001: OPN   DB   101
      L     B [AR2,P#0.0]
      T     #TEMP0
      JU    M006
M002: OPN   DB   101
      L     B [AR2,P#0.0]
      T     #TEMP1
      JU    M006
M004: OPN   DB   101
      L     B [AR2,P#0.0]
      T     #TEMP2
      JU    M006
M00f: +AR1  P#1.0
      L     B [AR1,P#0.0]
      L     C#8
      *I    
      T     #TEMP11
      +AR1  P#1.0
      L     B [AR1,P#0.0]
      T     #TEMP7
      L     P#M 100.0
      LAR2  
      L     #TEMP7
      L     C#8
      *I    
      +AR2  
      TAR2  #TEMP9
      TAR1  #TEMP4
      OPN   DB   101
      L     P#DBX 0.0
      LAR1  
      L     #TEMP11
      +AR1  
      LAR2  #TEMP9
      L     B [AR2,P#0.0]
      T     B [AR1,P#0.0]
      L     #TEMP4
      LAR1  
      JU    M006
M008: +AR1  P#1.0
      L     B [AR1,P#0.0]
      T     #TEMP3
      +AR1  P#1.0
      L     B [AR1,P#0.0]
      JL    M009
      JU    M00b
      JU    M00a
      JU    M00c
M009: JU    M005
M00b: L     #TEMP3
      T     #TEMP0
      JU    M006
M00a: L     #TEMP3
      T     #TEMP1
      JU    M006
M00c: L     #TEMP3
      T     #TEMP2
      JU    M006
M00e: +AR1  P#1.0
      L     B [AR1,P#0.0]
      T     #TEMP7
      L     P#M 100.0
      LAR2  
      L     #TEMP7
      L     C#8
      *I    
      +AR2  
      TAR2  #TEMP9
      +AR1  P#1.0
      L     B [AR1,P#0.0]
      T     #TEMP8
      L     P#M 100.0
      LAR2  
      L     #TEMP8
      L     C#8
      *I    
      +AR2  
      TAR2  #TEMP10
      TAR1  #TEMP4
      LAR1  #TEMP9
      LAR2  #TEMP10
      L     B [AR1,P#0.0]
      L     B [AR2,P#0.0]
      AW    
      INVI  
      T     #TEMP12
      L     B [AR1,P#0.0]
      L     B [AR2,P#0.0]
      OW    
      L     #TEMP12
      AW    
      T     B [AR1,P#0.0]
      L     DW#16#0
      T     #TEMP0
      L     MB   101
      T     #TEMP1
      L     MB   102
      T     #TEMP2
      L     #TEMP4
      LAR1  
      JU    M006
M011: +AR1  P#1.0
      L     B [AR1,P#0.0]
      T     #TEMP7
      L     P#M 100.0
      LAR2  
      L     #TEMP7
      L     C#8
      *I    
      +AR2  
      TAR2  #TEMP9
      +AR1  P#1.0
      L     B [AR1,P#0.0]
      T     #TEMP8
      L     P#M 100.0
      LAR2  
      L     #TEMP8
      L     C#8
      *I    
      +AR2  
      TAR2  #TEMP10
      TAR1  #TEMP4
      LAR1  #TEMP9
      LAR2  #TEMP10
      L     B [AR1,P#0.0]
      L     B [AR2,P#0.0]
      -I    
      T     B [AR1,P#0.0]
      L     DW#16#0
      T     #TEMP0
      L     MB   101
      T     #TEMP1
      L     MB   102
      T     #TEMP2
      L     #TEMP4
      LAR1  
      JU    M006
M012: L     #TEMP15
      INC   1
      T     #TEMP15
      +AR1  P#1.0
      L     B [AR1,P#0.0]
      T     #TEMP7
      L     P#M 100.0
      LAR2  
      L     #TEMP7
      L     C#8
      *I    
      +AR2  
      TAR2  #TEMP9
      +AR1  P#1.0
      L     B [AR1,P#0.0]
      T     #TEMP8
      L     P#M 100.0
      LAR2  
      L     #TEMP8
      L     C#8
      *I    
      +AR2  
      TAR2  #TEMP10
      TAR1  #TEMP4
      LAR1  #TEMP9
      LAR2  #TEMP10
      L     B [AR1,P#0.0]
      L     B [AR2,P#0.0]
      ==I   
      JCN   M013
      JU    M014
M013: L     P#DBX 0.0
      LAR1  
      T     #TEMP4
      L     B#16#0
      T     #TEMP6
      JU    M006
M014: L     #TEMP4
      LAR1  
      L     #TEMP13
      L     L#1
      +I    
      T     #TEMP13
      JU    M006
M006: L     #TEMP0
      T     MB   100
      L     #TEMP1
      T     MB   101
      L     #TEMP2
      T     MB   102
      +AR1  P#1.0
      L     #TEMP6
      +     1
      T     #TEMP6
      JU    M005
M010: L     P#DBX 0.0
      LAR1  
      L     0
      T     #TEMP6
      TAR1  #TEMP4
M005: TAR1  #TEMP4
      CLR   
      =     #TEMP16
      L     #TEMP13
      L     L#20
      ==I   
      S     #TEMP16
      L     #TEMP15
      ==I   
      A     #TEMP16
      JC    M017
      L     #TEMP13
      L     L#20
      <I    
      S     #TEMP16
      L     #TEMP15
      ==I   
      A     #TEMP16
      JC    M018
      JU    M019
M017: SET   
      =     #TEMP14
      JU    M016
M018: CLR   
      =     #TEMP14
      JU    M016
M019: CLR   
      O     #TEMP14
      =     #RET_VAL
      JU    M015
M016: CLR   
      O     #TEMP14
      =     #RET_VAL

Le code est assez volumineux et peut sembler complexe à une personne qui ne connaît pas le STL. Il n'est pas pertinent de décomposer chaque instruction dans le cadre de cet article. Pour des instructions détaillées et les fonctionnalités du langage STL, vous pouvez vous référer au manuel correspondant : Liste des déclarations (STL) pour la programmation S7-300 et S7-400. Ici, je vais présenter le même code après traitement — renommage des étiquettes et des variables et ajout de commentaires décrivant l'algorithme de fonctionnement ainsi que certaines constructions du langage STL. Je souligne d'emblée que le bloc examiné implémente une machine virtuelle qui exécute un certain bytecode situé dans le bloc DB100, dont nous connaissons le contenu. Les instructions de la machine virtuelle se composent d'un octet de code opérationnel et d'octets d'arguments, un octet pour chaque argument. Toutes les instructions examinées ont deux arguments, leurs valeurs dans les commentaires sont désignées par X et Y.

Code après traitement]

# Инициализация различных переменных
      L     B#16#0
      T     #CHECK_N        # Счетчик успешно пройденных проверок
      T     #COUNTER_N      # Счетчик общего количества проверок
      L     P#DBX 0.0
      T     #POINTER        # Указатель на текущую инструкцию
      CLR   
      =     #PRE_RET_VAL

# Основной цикл работы интерпретатора байт-кода
LOOP: L     #POINTER
      LAR1  
      OPN   DB   100
      L     DBLG
      TAR1  
      <=D                   # Проверка выхода указателя за пределы программы
      JC    FINISH
      L     DW#16#0
      T     #REG0
      L     #TEMP6
      L     W#16#0
      <>I   
      JC    M00d
      L     P#DBX 0.0
      LAR1  

# Конструкция switch - case для обработки различных опкодов
M00d: L     B [AR1,P#0.0]
      T     #OPCODE
      L     W#16#1
      ==I   
      JC    OPCODE_1
      L     #OPCODE
      L     W#16#2
      ==I   
      JC    OPCODE_2
      L     #OPCODE
      L     W#16#3
      ==I   
      JC    OPCODE_3
      L     #OPCODE
      L     W#16#4
      ==I   
      JC    OPCODE_4
      L     #OPCODE
      L     W#16#5
      ==I   
      JC    OPCODE_5
      L     #OPCODE
      L     W#16#6
      ==I   
      JC    OPCODE_6
      JU    OPCODE_OTHER

# Обработчик опкода 01: загрузка значения из DB101[X] в регистр Y
# OP01(X, Y): REG[Y] = DB101[X]
OPCODE_1: +AR1  P#1.0
      L     P#DBX 0.0
      LAR2  
      L     B [AR1,P#0.0]   # Загрузка аргумента X (индекс в DB101)
      L     C#8
      *I    
      +AR2  
      +AR1  P#1.0
      L     B [AR1,P#0.0]   # Загрузка аргумента Y (индекс регистра)
      JL    M003            # Аналог switch - case на основе значения Y
      JU    M001            # для выбора необходимого регистра для записи.
      JU    M002            # Подобные конструкции используются и в других
      JU    M004            # операциях ниже для аналогичных целей
M003: JU    LOOPEND
M001: OPN   DB   101
      L     B [AR2,P#0.0]
      T     #REG0           # Запись значения DB101[X] в REG[0]
      JU    PRE_LOOPEND
M002: OPN   DB   101
      L     B [AR2,P#0.0]
      T     #REG1           # Запись значения DB101[X] в REG[1]
      JU    PRE_LOOPEND
M004: OPN   DB   101
      L     B [AR2,P#0.0]
      T     #REG2           # Запись значения DB101[X] в REG[2]
      JU    PRE_LOOPEND

# Обработчик опкода 02: загрузка значения X в регистр Y
# OP02(X, Y): REG[Y] = X
OPCODE_2: +AR1  P#1.0
      L     B [AR1,P#0.0]
      T     #TEMP3
      +AR1  P#1.0
      L     B [AR1,P#0.0]
      JL    M009
      JU    M00b
      JU    M00a
      JU    M00c
M009: JU    LOOPEND
M00b: L     #TEMP3
      T     #REG0
      JU    PRE_LOOPEND
M00a: L     #TEMP3
      T     #REG1
      JU    PRE_LOOPEND
M00c: L     #TEMP3
      T     #REG2
      JU    PRE_LOOPEND

# Опкод 03 не используется в программе, поэтому пропустим его
...

# Обработчик опкода 04: сравнение регистров X и Y
# OP04(X, Y): REG[0] = 0; REG[X] = (REG[X] == REG[Y])
OPCODE_4: +AR1  P#1.0
      L     B [AR1,P#0.0]
      T     #TEMP7          # первый аргумент - X
      L     P#M 100.0
      LAR2  
      L     #TEMP7
      L     C#8
      *I    
      +AR2  
      TAR2  #TEMP9          # REG[X]
      +AR1  P#1.0
      L     B [AR1,P#0.0]
      T     #TEMP8
      L     P#M 100.0
      LAR2  
      L     #TEMP8
      L     C#8
      *I    
      +AR2  
      TAR2  #TEMP10         # REG[Y]
      TAR1  #POINTER
      LAR1  #TEMP9          # REG[X]
      LAR2  #TEMP10         # REG[Y]
      L     B [AR1,P#0.0]
      L     B [AR2,P#0.0]
      AW    
      INVI  
      T     #TEMP12         # ~(REG[Y] & REG[X])
      L     B [AR1,P#0.0]
      L     B [AR2,P#0.0]
      OW    
      L     #TEMP12
      AW                    # (~(REG[Y] & REG[X])) & (REG[Y] | REG[X]) - аналог проверки на равенство
      T     B [AR1,P#0.0]
      L     DW#16#0
      T     #REG0
      L     MB   101
      T     #REG1
      L     MB   102
      T     #REG2
      L     #POINTER
      LAR1  
      JU    PRE_LOOPEND

# Обработчик опкода 05: вычитание регистра Y из X
# OP05(X, Y): REG[0] = 0; REG[X] = REG[X] - REG[Y]
OPCODE_5: +AR1  P#1.0
      L     B [AR1,P#0.0]
      T     #TEMP7
      L     P#M 100.0
      LAR2  
      L     #TEMP7
      L     C#8
      *I    
      +AR2  
      TAR2  #TEMP9          # REG[X]
      +AR1  P#1.0
      L     B [AR1,P#0.0]
      T     #TEMP8
      L     P#M 100.0
      LAR2  
      L     #TEMP8
      L     C#8
      *I    
      +AR2  
      TAR2  #TEMP10         # REG[Y]
      TAR1  #POINTER
      LAR1  #TEMP9
      LAR2  #TEMP10
      L     B [AR1,P#0.0]
      L     B [AR2,P#0.0]
      -I                    # ACCU1 = ACCU2 - ACCU1, REG[X] - REG[Y]
      T     B [AR1,P#0.0]
      L     DW#16#0
      T     #REG0
      L     MB   101
      T     #REG1
      L     MB   102
      T     #REG2
      L     #POINTER
      LAR1  
      JU    PRE_LOOPEND

# Обработчик опкода 06: инкремент #CHECK_N при равенстве регистров X и Y
# OP06(X, Y): #CHECK_N += (1 if REG[X] == REG[Y] else 0)
OPCODE_6: L     #COUNTER_N
      INC   1
      T     #COUNTER_N
      +AR1  P#1.0
      L     B [AR1,P#0.0]
      T     #TEMP7          #  REG[X]     
      L     P#M 100.0
      LAR2  
      L     #TEMP7
      L     C#8
      *I    
      +AR2  
      TAR2  #TEMP9          #  REG[X]  
      +AR1  P#1.0
      L     B [AR1,P#0.0]
      T     #TEMP8
      L     P#M 100.0
      LAR2  
      L     #TEMP8
      L     C#8
      *I    
      +AR2  
      TAR2  #TEMP10         # REG[Y]
      TAR1  #POINTER
      LAR1  #TEMP9          # REG[Y]
      LAR2  #TEMP10         # REG[X]
      L     B [AR1,P#0.0]
      L     B [AR2,P#0.0]
      ==I   
      JCN   M013
      JU    M014
M013: L     P#DBX 0.0
      LAR1  
      T     #POINTER
      L     B#16#0
      T     #TEMP6
      JU    PRE_LOOPEND
M014: L     #POINTER
      LAR1  
# Инкремент значения #CHECK_N
      L     #CHECK_N
      L     L#1
      +I    
      T     #CHECK_N
      JU    PRE_LOOPEND

PRE_LOOPEND: L     #REG0
      T     MB   100
      L     #REG1
      T     MB   101
      L     #REG2
      T     MB   102
      +AR1  P#1.0
      L     #TEMP6
      +     1
      T     #TEMP6
      JU    LOOPEND

OPCODE_OTHER: L     P#DBX 0.0
      LAR1  
      L     0
      T     #TEMP6
      TAR1  #POINTER

LOOPEND: TAR1  #POINTER
      CLR   
      =     #TEMP16
      L     #CHECK_N
      L     L#20
      ==I   
      S     #TEMP16
      L     #COUNTER_N
      ==I   
      A     #TEMP16
# Все проверки пройдены, если #CHECK_N == #COUNTER_N == 20
      JC    GOOD
      L     #CHECK_N
      L     L#20
      <I    
      S     #TEMP16
      L     #COUNTER_N
      ==I   
      A     #TEMP16
      JC    FAIL
      JU    M019
GOOD: SET   
      =     #PRE_RET_VAL
      JU    FINISH
FAIL: CLR   
      =     #PRE_RET_VAL
      JU    FINISH
M019: CLR   
      O     #PRE_RET_VAL
      =     #RET_VAL
      JU    LOOP
FINISH: CLR   
      O     #PRE_RET_VAL
      =     #RET_VAL

Après avoir pris connaissance des instructions de la machine virtuelle, écrivons un petit désassembleur pour analyser le bytecode dans le bloc DB100 :

import string
alph = string.ascii_letters + string.digits

with open('DB100.bin', 'rb') as f:
    m = f.read()

pc = 0

while pc < len(m):
    op = m[pc]
    if op == 1:
        print('R{} = DB101[{}]'.format(m[pc + 2], m[pc + 1]))
        pc += 3
    elif op == 2:
        c = chr(m[pc + 1])
        c = c if c in alph else '?'
        print('R{} = {:02x} ({})'.format(m[pc + 2], m[pc + 1], c))
        pc += 3
    elif op == 4:
        print('R0 = 0; R{} = (R{} == R{})'.format(
            m[pc + 1], m[pc + 1], m[pc + 2]))
        pc += 3
    elif op == 5:
        print('R0 = 0; R{} = R{} - R{}'.format(
            m[pc + 1], m[pc + 1], m[pc + 2]))
        pc += 3
    elif op == 6:
        print('CHECK (R{} == R{})n'.format(
            m[pc + 1], m[pc + 2]))
        pc += 3
    else:
        print('unk opcode {}'.format(op))
        break

Nous obtiendrons le code suivant de la machine virtuelle :

Code de la machine virtuelle

R1 = DB101[0]
R2 = 6e (n)
R0 = 0; R1 = (R1 == R2)
CHECK (R1 == R0)

R1 = DB101[1]
R2 = 10 (?)
R0 = 0; R1 = R1 - R2
R2 = 20 (?)
R0 = 0; R1 = R1 - R2
CHECK (R1 == R0)

R1 = DB101[2]
R2 = 77 (w)
R0 = 0; R1 = (R1 == R2)
CHECK (R1 == R0)

R1 = DB101[3]
R2 = 0a (?)
R0 = 0; R1 = R1 - R2
R2 = 16 (?)
R0 = 0; R1 = R1 - R2
CHECK (R1 == R0)

R1 = DB101[4]
R2 = 75 (u)
R0 = 0; R1 = (R1 == R2)
CHECK (R1 == R0)

R1 = DB101[5]
R2 = 0a (?)
R0 = 0; R1 = R1 - R2
R2 = 16 (?)
R0 = 0; R1 = R1 - R2
CHECK (R1 == R0)

R1 = DB101[6]
R2 = 34 (4)
R0 = 0; R1 = (R1 == R2)
CHECK (R1 == R0)

R1 = DB101[7]
R2 = 26 (?)
R0 = 0; R1 = R1 - R2
R2 = 4c (L)
R0 = 0; R1 = R1 - R2
CHECK (R1 == R0)

R1 = DB101[8]
R2 = 33 (3)
R0 = 0; R1 = (R1 == R2)
CHECK (R1 == R0)

R1 = DB101[9]
R2 = 0a (?)
R0 = 0; R1 = R1 - R2
R2 = 16 (?)
R0 = 0; R1 = R1 - R2
CHECK (R1 == R0)

R1 = DB101[10]
R2 = 37 (7)
R0 = 0; R1 = (R1 == R2)
CHECK (R1 == R0)

R1 = DB101[11]
R2 = 22 (?)
R0 = 0; R1 = R1 - R2
R2 = 46 (F)
R0 = 0; R1 = R1 - R2
CHECK (R1 == R0)

R1 = DB101[12]
R2 = 33 (3)
R0 = 0; R1 = (R1 == R2)
CHECK (R1 == R0)

R1 = DB101[13]
R2 = 0a (?)
R0 = 0; R1 = R1 - R2
R2 = 16 (?)
R0 = 0; R1 = R1 - R2
CHECK (R1 == R0)

R1 = DB101[14]
R2 = 6d (m)
R0 = 0; R1 = (R1 == R2)
CHECK (R1 == R0)

R1 = DB101[15]
R2 = 11 (?)
R0 = 0; R1 = R1 - R2
R2 = 23 (?)
R0 = 0; R1 = R1 - R2
CHECK (R1 == R0)

R1 = DB101[16]
R2 = 35 (5)
R0 = 0; R1 = (R1 == R2)
CHECK (R1 == R0)

R1 = DB101[17]
R2 = 12 (?)
R0 = 0; R1 = R1 - R2
R2 = 25 (?)
R0 = 0; R1 = R1 - R2
CHECK (R1 == R0)

R1 = DB101[18]
R2 = 33 (3)
R0 = 0; R1 = (R1 == R2)
CHECK (R1 == R0)

R1 = DB101[19]
R2 = 26 (?)
R0 = 0; R1 = R1 - R2
R2 = 4c (L)
R0 = 0; R1 = R1 - R2
CHECK (R1 == R0)

Comme on peut le voir, ce programme vérifie simplement chaque caractère de DB101 par rapport à une certaine valeur. La chaîne finale pour réussir tous les tests est : n0w u 4r3 7h3 m4573r. Si cette chaîne est placée dans le bloc DB101, cela activera le contrôle manuel du PLC, permettant d'exploser ou de faire éclater un ballon.

Voilà ! Alexey a démontré un niveau de compétence élevé, digne d'un ninja industriel 🙂 Nous avons envoyé des prix commémoratifs au gagnant. Un grand merci à tous les participants !

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster