Dans le sillage d'Industrial Ninja: comment PLC a été piraté lors de Positive Hack Days 9



À PHDays 9, nous avons organisé un concours pour le piratage d'une usine de pompage de gaz - le concours Industrial Ninja . Il y avait trois stands sur le site avec des paramètres de sécurité différents (No Security, Low Security, High Security) imitant le même processus industriel: l'air était pompé dans le ballon (puis descendait) sous pression.

Malgré divers paramètres de sécurité, le matériel des stands était le même: Siemens Simatic S7-300 PLC; bouton de purge d'urgence et dispositif de mesure de pression (connectés aux entrées numériques de l'automate (DI)); vannes de pompage et de purge (connectées aux sorties numériques de l'automate (DO)) - voir la figure ci-dessous.



Le PLC, en fonction des relevés de pression et conformément à son programme, a décidé de souffler ou de gonfler la bille (ouvrir et fermer les vannes correspondantes). Cependant, un mode de contrôle manuel était prévu sur tous les stands, ce qui permettait de contrôler l'état des vannes sans aucune restriction.

Les stands se distinguaient par la complexité de l'activation de ce mode: sur un stand non protégé c'était le plus facile à faire, mais sur le stand Haute Sécurité, en conséquence, c'était plus difficile.

En deux jours, cinq des six problèmes ont été résolus; le vainqueur de la première place a obtenu 233 points (il a passé une semaine à préparer la compétition). Trois gagnants: je place - a1exdandy, II - Rubikoid, III - Ze.

Cependant, pendant les PHDays, aucun des participants n'a pu surmonter les trois stands, nous avons donc décidé de lancer un concours en ligne et publié la tâche la plus difficile début juin. Les participants devaient terminer la tâche en un mois, trouver le drapeau, décrire la solution en détail et de manière intéressante.

Sous la coupe, nous publions une analyse de la meilleure solution à la mission envoyée au cours du mois, elle a été trouvée par Alexey Kovrizhnykh (a1exdandy) de la société Digital Security, qui a pris la 1ère place du concours lors des PHDays. Ci-dessous, nous fournissons son texte avec nos commentaires.

Analyse initiale


Donc, dans la tâche, il y avait une archive avec des fichiers:

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

Le fichier hints.txt contient les informations et les conseils nécessaires pour résoudre la tâche. Voici son contenu:

  1. Petrovich m'a dit hier que depuis PlcSim, vous pouvez télécharger des blocs à l'étape 7.
  2. Sur le stand, des automates Siemens Simatic S7-300 ont été utilisés.
  3. PlcSim est un émulateur d'automate qui vous permet d'exécuter et de déboguer des programmes pour les automates Siemens S7.

 Le fichier DB100.bin contient apparemment un bloc de données API DB100: 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 .... L ....... 

A en juger par le nom, le fichier block_upload_traffic.pcapng contient un vidage du trafic de chargement de bloc vers l'automate.

Il est à noter que cette décharge de trafic sur le site de compétition lors de la conférence était un peu plus difficile à obtenir. Pour ce faire, il était nécessaire de comprendre le script du fichier de projet pour TeslaSCADA2. De là, il a été possible de comprendre où se trouve le vidage chiffré avec RC4 et quelle clé doit être utilisée pour le déchiffrer. Des vidages de blocs de données sur le site peuvent être obtenus à l'aide du client de protocole S7. J'ai utilisé le client de démonstration du package Snap7 pour cela.

Extraire des unités de traitement du signal d'une décharge de trafic


En regardant le contenu du dump, on peut comprendre que les blocs de traitement du signal OB1, FC1, FC2 et FC3 y sont transmis:



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 ('\x1e\x00', '\x1e\x01'): # upload buf += s7_data[4:] elif s7_param == '\x1f': with open(next(names), 'wb') as f: f.write(buf) buf = '' 

Après avoir étudié les blocs reçus, vous pouvez remarquer qu'ils commencent toujours par les octets 70 70 (pp). Vous devez maintenant apprendre à les analyser. Un indice de la tâche suggère que vous devez utiliser PlcSim pour cela.

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


Tout d'abord, essayons de programmer S7-PlcSim en y chargeant plusieurs blocs avec des instructions répétitives (= Q 0.0) à l'aide du logiciel Simatic Manager, et enregistrons le résultat dans l'émulateur PLC dans le fichier example.plc. En regardant le contenu du fichier, vous pouvez facilement déterminer le début des blocs chargés par la signature 70 70, que nous avons découverte plus tôt. Avant les blocs, apparemment, la taille du bloc est écrite sous la forme d'une valeur little-endian de 4 octets.



Après avoir reçu des informations sur la structure des fichiers plc, le plan d'action suivant est apparu pour la lecture des programmes PLC S7:

  1. À l'aide de Simatic Manager, nous créons une structure de blocs dans S7-PlcSim similaire à celle que nous avons obtenue du vidage. Les tailles de bloc doivent correspondre (obtenues en remplissant les blocs avec le bon nombre d'instructions) et leurs identifiants (OB1, FC1, FC2, FC3).
  2. Enregistrez l'automate dans un fichier.
  3. Nous remplaçons le contenu des blocs dans le fichier reçu par les blocs de la décharge de trafic. Le début des blocs est déterminé par la signature.
  4. Le fichier résultant est téléchargé sur S7-PlcSim et nous examinons le contenu des blocs dans Simatic Manager.

Les blocs peuvent être remplacés, par exemple, par 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) 

Aleksey a continué sur une voie peut-être plus compliquée, mais toujours dans le bon sens. Nous avons supposé que les participants utiliseraient le programme NetToPlcSim pour que PlcSim puisse communiquer sur le réseau, charger des blocs dans PlcSim via Snap7, puis télécharger ces blocs en tant que projet depuis PlcSim en utilisant l'environnement de développement.

En ouvrant le fichier résultant dans S7-PlcSim, vous pouvez lire des blocs remplacés à l'aide de Simatic Manager. Les principales fonctions de gestion des appareils sont enregistrées dans le bloc FC1. La variable # TEMP0 attire une attention particulière, lorsqu'elle est allumée, il semble que la commande PLC passe en mode manuel sur la base des valeurs de la mémoire binaire M2.2 et M2.3. # TEMP0 est défini par FC3.



Pour résoudre le problème, il est nécessaire d'analyser la fonction FC3 et de comprendre ce qui doit être fait pour qu'elle renvoie une unité logique.

Les blocs de traitement du signal PLC sur le stand Low Security sur le site de la compétition étaient disposés de la même manière, mais pour définir la valeur de la variable # TEMP0, il suffisait d'écrire la ligne mon chemin ninja sur le bloc DB1. La vérification de la valeur dans le bloc était organisée clairement et ne nécessitait pas une connaissance approfondie du langage de programmation de bloc. De toute évidence, au niveau de la haute sécurité, il sera beaucoup plus difficile de réaliser un contrôle manuel et il est nécessaire de comprendre les subtilités du langage STL (l'une des façons de programmer l'automate S7).

Reverse Block FC3


Le contenu du bloc FC3 dans la représentation STL:
  LB#16#0 T #TEMP13 T #TEMP15 LP#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 LW#16#0 <>I JC M00d LP#DBX 0.0 LAR1 M00d: LB [AR1,P#0.0] T #TEMP5 LW#16#1 ==I JC M007 L #TEMP5 LW#16#2 ==I JC M008 L #TEMP5 LW#16#3 ==I JC M00f L #TEMP5 LW#16#4 ==I JC M00e L #TEMP5 LW#16#5 ==I JC M011 L #TEMP5 LW#16#6 ==I JC M012 JU M010 M007: +AR1 P#1.0 LP#DBX 0.0 LAR2 LB [AR1,P#0.0] LC#8 *I +AR2 +AR1 P#1.0 LB [AR1,P#0.0] JL M003 JU M001 JU M002 JU M004 M003: JU M005 M001: OPN DB 101 LB [AR2,P#0.0] T #TEMP0 JU M006 M002: OPN DB 101 LB [AR2,P#0.0] T #TEMP1 JU M006 M004: OPN DB 101 LB [AR2,P#0.0] T #TEMP2 JU M006 M00f: +AR1 P#1.0 LB [AR1,P#0.0] LC#8 *IT #TEMP11 +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP7 LP#M 100.0 LAR2 L #TEMP7 LC#8 *I +AR2 TAR2 #TEMP9 TAR1 #TEMP4 OPN DB 101 LP#DBX 0.0 LAR1 L #TEMP11 +AR1 LAR2 #TEMP9 LB [AR2,P#0.0] TB [AR1,P#0.0] L #TEMP4 LAR1 JU M006 M008: +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP3 +AR1 P#1.0 LB [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 LB [AR1,P#0.0] T #TEMP7 LP#M 100.0 LAR2 L #TEMP7 LC#8 *I +AR2 TAR2 #TEMP9 +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP8 LP#M 100.0 LAR2 L #TEMP8 LC#8 *I +AR2 TAR2 #TEMP10 TAR1 #TEMP4 LAR1 #TEMP9 LAR2 #TEMP10 LB [AR1,P#0.0] LB [AR2,P#0.0] AW INVI T #TEMP12 LB [AR1,P#0.0] LB [AR2,P#0.0] OW L #TEMP12 AW TB [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 LB [AR1,P#0.0] T #TEMP7 LP#M 100.0 LAR2 L #TEMP7 LC#8 *I +AR2 TAR2 #TEMP9 +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP8 LP#M 100.0 LAR2 L #TEMP8 LC#8 *I +AR2 TAR2 #TEMP10 TAR1 #TEMP4 LAR1 #TEMP9 LAR2 #TEMP10 LB [AR1,P#0.0] LB [AR2,P#0.0] -ITB [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 LB [AR1,P#0.0] T #TEMP7 LP#M 100.0 LAR2 L #TEMP7 LC#8 *I +AR2 TAR2 #TEMP9 +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP8 LP#M 100.0 LAR2 L #TEMP8 LC#8 *I +AR2 TAR2 #TEMP10 TAR1 #TEMP4 LAR1 #TEMP9 LAR2 #TEMP10 LB [AR1,P#0.0] LB [AR2,P#0.0] ==I JCN M013 JU M014 M013: LP#DBX 0.0 LAR1 T #TEMP4 LB#16#0 T #TEMP6 JU M006 M014: L #TEMP4 LAR1 L #TEMP13 LL#1 +IT #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: LP#DBX 0.0 LAR1 L 0 T #TEMP6 TAR1 #TEMP4 M005: TAR1 #TEMP4 CLR = #TEMP16 L #TEMP13 LL#20 ==IS #TEMP16 L #TEMP15 ==IA #TEMP16 JC M017 L #TEMP13 LL#20 <IS #TEMP16 L #TEMP15 ==IA #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 pour une personne peu familière avec STL, cela peut sembler compliqué. Cela n'a aucun sens de démonter chaque instruction dans le cadre de cet article, pour des instructions détaillées et les fonctionnalités du langage STL, voir le manuel correspondant: Liste d'instructions (STL) pour la programmation S7-300 et S7-400 . Ici, je vais donner le même code après le traitement - renommer les étiquettes et les variables et ajouter des commentaires décrivant l'algorithme de travail et certaines constructions du langage STL. Je remarque tout de suite que dans le bloc considéré, une machine virtuelle est implémentée qui exécute un bytecode situé dans le bloc DB100, dont nous connaissons le contenu. Les instructions de la machine virtuelle sont 1 octet de code de fonctionnement et octets d'arguments, un octet pour chaque argument. Toutes les instructions examinées ont deux arguments, j'ai désigné leurs valeurs dans les commentaires comme X et Y.

Code de post-traitement
]
 #    LB#16#0 T #CHECK_N #     T #COUNTER_N #     LP#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 LW#16#0 <>I JC M00d LP#DBX 0.0 LAR1 #  switch - case     M00d: LB [AR1,P#0.0] T #OPCODE LW#16#1 ==I JC OPCODE_1 L #OPCODE LW#16#2 ==I JC OPCODE_2 L #OPCODE LW#16#3 ==I JC OPCODE_3 L #OPCODE LW#16#4 ==I JC OPCODE_4 L #OPCODE LW#16#5 ==I JC OPCODE_5 L #OPCODE LW#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 LP#DBX 0.0 LAR2 LB [AR1,P#0.0] #   X (  DB101) LC#8 *I +AR2 +AR1 P#1.0 LB [AR1,P#0.0] #   Y ( ) JL M003 #  switch - case    Y JU M001 #      . JU M002 #       JU M004 #      M003: JU LOOPEND M001: OPN DB 101 LB [AR2,P#0.0] T #REG0 #   DB101[X]  REG[0] JU PRE_LOOPEND M002: OPN DB 101 LB [AR2,P#0.0] T #REG1 #   DB101[X]  REG[1] JU PRE_LOOPEND M004: OPN DB 101 LB [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 LB [AR1,P#0.0] T #TEMP3 +AR1 P#1.0 LB [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 LB [AR1,P#0.0] T #TEMP7 #   - X LP#M 100.0 LAR2 L #TEMP7 LC#8 *I +AR2 TAR2 #TEMP9 # REG[X] +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP8 LP#M 100.0 LAR2 L #TEMP8 LC#8 *I +AR2 TAR2 #TEMP10 # REG[Y] TAR1 #POINTER LAR1 #TEMP9 # REG[X] LAR2 #TEMP10 # REG[Y] LB [AR1,P#0.0] LB [AR2,P#0.0] AW INVI T #TEMP12 # ~(REG[Y] & REG[X]) LB [AR1,P#0.0] LB [AR2,P#0.0] OW L #TEMP12 AW # (~(REG[Y] & REG[X])) & (REG[Y] | REG[X]) -     TB [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 LB [AR1,P#0.0] T #TEMP7 LP#M 100.0 LAR2 L #TEMP7 LC#8 *I +AR2 TAR2 #TEMP9 # REG[X] +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP8 LP#M 100.0 LAR2 L #TEMP8 LC#8 *I +AR2 TAR2 #TEMP10 # REG[Y] TAR1 #POINTER LAR1 #TEMP9 LAR2 #TEMP10 LB [AR1,P#0.0] LB [AR2,P#0.0] -I # ACCU1 = ACCU2 - ACCU1, REG[X] - REG[Y] TB [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 LB [AR1,P#0.0] T #TEMP7 # REG[X] LP#M 100.0 LAR2 L #TEMP7 LC#8 *I +AR2 TAR2 #TEMP9 # REG[X] +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP8 LP#M 100.0 LAR2 L #TEMP8 LC#8 *I +AR2 TAR2 #TEMP10 # REG[Y] TAR1 #POINTER LAR1 #TEMP9 # REG[Y] LAR2 #TEMP10 # REG[X] LB [AR1,P#0.0] LB [AR2,P#0.0] ==I JCN M013 JU M014 M013: LP#DBX 0.0 LAR1 T #POINTER LB#16#0 T #TEMP6 JU PRE_LOOPEND M014: L #POINTER LAR1 #   #CHECK_N L #CHECK_N LL#1 +IT #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: LP#DBX 0.0 LAR1 L 0 T #TEMP6 TAR1 #POINTER LOOPEND: TAR1 #POINTER CLR = #TEMP16 L #CHECK_N LL#20 ==IS #TEMP16 L #COUNTER_N ==IA #TEMP16 #   ,  #CHECK_N == #COUNTER_N == 20 JC GOOD L #CHECK_N LL#20 <IS #TEMP16 L #COUNTER_N ==IA #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 

Ayant une idée des instructions de la machine virtuelle, nous allons écrire 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 

Par conséquent, nous obtenons le code de machine virtuelle suivant:

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 vous pouvez le voir, ce programme vérifie simplement chaque symbole de DB101 pour une égalité à une certaine valeur. La dernière ligne pour passer tous les contrôles: n0w u 4r3 7h3 m4573r. Si cette ligne est placée dans le bloc DB101, la commande manuelle de l'automate est activée et il sera possible de gonfler ou de dégonfler le ballon.

C'est tout! Alexey a démontré un haut niveau de connaissances digne d'un ninja industriel :) Nous avons envoyé des prix mémorables au gagnant. Un grand merci à tous les participants!

Source: https://habr.com/ru/post/fr460615/


All Articles