Pagājušajā PHDays 9 mēs rīkojām konkursu, lai uzlauztu gāzes sūkņu iekārtu - konkursu
Neskatoties uz dažādajiem drošības parametriem, stendu aparatūras sastāvs bija vienāds: Siemens Simatic PLC S7-300 sērija; avārijas deflācijas poga un spiediena mērīšanas ierīce (savienota ar PLC digitālajām ieejām (DI)); vārsti, kas darbojas gaisa piepūšanai un deflācijai (savienoti ar PLC (DO) digitālajām izejām) - skatiet attēlu zemāk.
PLC, atkarībā no spiediena rādījumiem un saskaņā ar savu programmu, pieņēma lēmumu izlaist vai piepūst bumbu (atvēra un aizvēra atbilstošos vārstus). Taču visiem stendiem bija manuāls vadības režīms, kas ļāva bez ierobežojumiem kontrolēt vārstu stāvokļus.
Stendi atšķīrās ar šī režīma ieslēgšanas sarežģītību: neaizsargātajā stendā to izdarīt bija visvieglāk, bet High Security stendā tas bija attiecīgi grūtāk.
Piecas no sešām problēmām tika atrisinātas divu dienu laikā; Pirmās vietas dalībnieks nopelnīja 233 punktus (gatavojoties sacensībām pavadīja nedēļu). Trīs uzvarētāji: I vieta - a1exdandy, II - Rubikoid, III - Ze.
Tomēr PHDays laikā neviens no dalībniekiem nespēja pārvarēt visus trīs stendus, tāpēc nolēmām izveidot tiešsaistes konkursu un jūnija sākumā publicējām grūtāko uzdevumu. Dalībniekiem mēneša laikā bija jāizpilda uzdevums, jāatrod karogs, detalizēti un interesanti jāapraksta risinājums.
Zem griezuma publicējam labākā uzdevuma risinājuma analīzi no mēneša laikā iesūtītajiem, to atradis Aleksejs Kovrizņiks (a1exdandy) no Digital Security kompānijas, kurš PHDays laikā ieņēma XNUMX.vietu konkursā. Zemāk mēs iepazīstinām ar tā tekstu ar saviem komentāriem.
Sākotnējā analīze
Tātad uzdevumā bija arhīvs ar šādiem failiem:
- block_upload_traffic.pcapng
- DB100.bin
- hints.txt
Fails hints.txt satur nepieciešamo informāciju un ieteikumus uzdevuma risināšanai. Šeit ir tā saturs:
- Petrovičs man vakar teica, ka jūs varat ielādēt blokus no PlcSim Step7.
- Stendā tika izmantots Siemens Simatic S7-300 sērijas PLC.
- PlcSim ir PLC emulators, kas ļauj palaist un atkļūdot programmas Siemens S7 PLC.
Šķiet, ka failā DB100.bin ir DB100 PLC datu bloks: 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 0103a0102 0 ......... 02 00000030............0501. 0202: 1602 0501 0206 0100 0104 0102 00000040 7502 .......&..... 0401: 0206c0100 0105 0102 0 02 0501 00000050 0202 1602 0501 0206 0100 0106 0102 3402 ........ : 4 00000060 0401 0206 0100a0107 0102 2602 0501 ................ 0202: 00000070 4 02 0501a 0206 0100 0108 0102 .......... 3302a0401: 3 00000080b 0206 0100 0109 0102 0 02 ......".....F... 0501b0202: 1602 00000090 0501c 0206 0100 010c 0102 3702 0401 ........ ... .... 0206e7: 000000 0 0100 010 0102 2202 0501 0202 ........#...... 4602f0501: 000000 0 0206 0100 010 0102 ........... ..... .....&. 3302: 0401 0206 0100c3 000000 0 010 ....L......
Kā norāda nosaukums, failā block_upload_traffic.pcapng ir ietverta PLC bloka augšupielādes trafika izplūde.
Ir vērts atzīmēt, ka šo satiksmes izgāztuvi sacensību vietā konferences laikā bija nedaudz grūtāk iegūt. Lai to izdarītu, bija jāsaprot skripts no TeslaSCADA2 projekta faila. No tā varēja saprast, kur atrodas ar RC4 šifrētā izgāztuve un kāda atslēga ir jāizmanto, lai to atšifrētu. Datu bloku izgāztuves uz vietas varēja iegūt, izmantojot S7 protokola klientu. Šim nolūkam es izmantoju demonstrācijas klientu no Snap7 pakotnes.
Signāla apstrādes bloku izvilkšana no satiksmes izgāztuves
Aplūkojot izgāztuves saturu, var saprast, ka tajā ir signāla apstrādes bloki OB1, FC1, FC2 un FC3:
Šie bloki ir jānoņem. To var izdarīt, piemēram, ar šādu skriptu, iepriekš konvertējot trafiku no pcapng formāta uz 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 = ''
Pārbaudot iegūtos blokus, jūs ievērosiet, ka tie vienmēr sākas ar baitiem 70 70 (pp). Tagad jums jāiemācās tos analizēt. Uzdevuma padoms liecina, ka šim nolūkam ir jāizmanto PlcSim.
Cilvēkam lasāmu instrukciju iegūšana no blokiem
Vispirms mēģināsim ieprogrammēt S7-PlcSim, ielādējot tajā vairākus blokus ar atkārtotām instrukcijām (= Q 0.0), izmantojot programmatūru Simatic Manager, un saglabājot emulatorā iegūto PLC failā example.plc. Apskatot faila saturu, jūs varat viegli noteikt lejupielādēto bloku sākumu pēc paraksta 70 70, ko mēs atklājām iepriekš. Acīmredzot pirms blokiem bloka lielums ir rakstīts kā 4 baitu mazā gala vērtība.
Pēc tam, kad saņēmām informāciju par plc failu struktūru, PLC S7 programmu lasīšanai parādījās šāds rīcības plāns:
- Izmantojot Simatic Manager, mēs S7-PlcSim izveidojam bloku struktūru, kas ir līdzīga tai, kuru saņēmām no izgāztuves. Bloku izmēriem jāsakrīt (tas tiek panākts, blokus aizpildot ar nepieciešamo instrukciju skaitu) un to identifikatoriem (OB1, FC1, FC2, FC3).
- Saglabājiet PLC failā.
- Mēs aizvietojam iegūtā faila bloku saturu ar blokiem no satiksmes izgāztuves. Bloku sākumu nosaka paraksts.
- Mēs ielādējam iegūto failu S7-PlcSim un skatāmies uz Simatic Manager bloku saturu.
Blokus var aizstāt, piemēram, ar šādu kodu:
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)
Aleksejs izvēlējās, iespējams, grūtāku, bet tomēr pareizo ceļu. Mēs pieņēmām, ka dalībnieki izmantos programmu NetToPlcSim, lai PlcSim varētu sazināties tīklā, augšupielādēt blokus PlcSim, izmantojot Snap7, un pēc tam lejupielādēt šos blokus kā projektu no PlcSim, izmantojot izstrādes vidi.
Atverot iegūto failu S7-PlcSim, varat nolasīt pārrakstītos blokus, izmantojot Simatic Manager. Galvenās ierīces vadības funkcijas ir ierakstītas blokā FC1. Īpaši jāatzīmē mainīgais #TEMP0, kas, ieslēdzot, iestata PLC vadību manuālajā režīmā, pamatojoties uz M2.2 un M2.3 bitu atmiņas vērtībām. #TEMP0 vērtību iestata funkcija FC3.
Lai atrisinātu problēmu, jums jāanalizē FC3 funkcija un jāsaprot, kas jādara, lai tā atgrieztu loģisku.
Līdzīgi bija izvietoti arī PLC signālu apstrādes bloki Low Security stendā sacensību vietā, taču, lai iestatītu #TEMP0 mainīgā vērtību, pietika ar rindiņas my ninja way ierakstīšanu DB1 blokā. Vērtības pārbaude blokā bija vienkārša, un tai nebija vajadzīgas dziļas zināšanas par bloku programmēšanas valodu. Acīmredzot Augstas drošības līmenī manuālas vadības sasniegšana būs daudz grūtāka un ir jāsaprot STL valodas sarežģītības (viens no S7 PLC programmēšanas veidiem).
Reverss bloks FC3
FC3 bloka saturs STL attēlojumā:
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
Kods ir diezgan garš un var šķist sarežģīts kādam, kurš nepārzina STL. Nav jēgas analizēt katru instrukciju šī raksta ietvaros; detalizētus norādījumus un STL valodas iespējas var atrast attiecīgajā rokasgrāmatā:
Kods pēc apstrādes]
# Инициализация различных переменных
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
Saņemot priekšstatu par virtuālās mašīnas instrukcijām, uzrakstīsim nelielu demontētāju, lai parsētu baitkodu blokā 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
Rezultātā mēs iegūstam šādu virtuālās mašīnas kodu:
Virtuālās mašīnas kods
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)
Kā redzat, šī programma vienkārši pārbauda katras rakstzīmes no DB101 vienlīdzību ar noteiktu vērtību. Pēdējā rinda visu pārbaužu nokārtošanai ir: n0w u 4r3 7h3 m4573r. Ja šo līniju ievieto blokā DB101, tad tiek aktivizēta manuālā PLC vadība un būs iespējams balonu eksplodēt vai iztukšot.
Tas ir viss! Aleksejs demonstrēja industriālajam nindzjas cienīgu augstu zināšanu līmeni :) Uzvarētājam nosūtījām neaizmirstamas balvas. Liels paldies visiem dalībniekiem!
Avots: www.habr.com