Na poslednom PHDays 9 sme usporiadali súťaž o hacknutie čerpacej stanice plynu - súťaž
Napriek rozdielnym bezpečnostným parametrom bola hardvérová skladba stojanov rovnaká: Siemens Simatic PLC série S7-300; núdzové vypúšťacie tlačidlo a zariadenie na meranie tlaku (pripojené k digitálnym vstupom PLC (DI)); ventily slúžiace na nafukovanie a vypúšťanie vzduchu (pripojené k digitálnym výstupom PLC (DO)) - pozri obrázok nižšie.
PLC sa v závislosti od nameraných hodnôt tlaku a v súlade so svojím programom rozhodlo vyfúknuť alebo nafúknuť guľu (otvorilo a zatvorilo príslušné ventily). Všetky stojany však mali ručný režim ovládania, čo umožňovalo ovládať stavy ventilov bez akýchkoľvek obmedzení.
Stojany sa líšili zložitosťou zapínania tohto režimu: na nechránenom stánku to bolo najjednoduchšie a na stánku s vysokou bezpečnosťou to bolo zodpovedajúcim spôsobom ťažšie.
Päť zo šiestich problémov bolo vyriešených za dva dni; Prvý účastník získal 233 bodov (týždeň sa pripravoval na súťaž). Traja výhercovia: I miesto - a1exdandy, II - Rubikoid, III - Ze.
Počas PHDays sa však žiadnemu z účastníkov nepodarilo prekonať všetky tri stanovištia, preto sme sa rozhodli urobiť online súťaž a najťažšiu úlohu sme zverejnili začiatkom júna. Účastníci museli do mesiaca splniť úlohu, nájsť vlajku a detailne a zaujímavo opísať riešenie.
Pod zostrihom zverejňujeme analýzu najlepšieho riešenia úlohy zo zaslaných za mesiac, našiel ju Alexey Kovrizhnykh (a1exdandy) zo spoločnosti Digital Security, ktorý obsadil XNUMX. miesto v súťaži počas PHDays. Nižšie uvádzame jeho text s našimi komentármi.
Počiatočná analýza
Úloha teda obsahovala archív s nasledujúcimi súbormi:
- block_upload_traffic.pcapng
- DB100.bin
- hints.txt
Súbor hints.txt obsahuje potrebné informácie a rady na vyriešenie úlohy. Tu je jeho obsah:
- Petrovich mi včera povedal, že môžete načítať bloky z PlcSim do Step7.
- Na stánku bolo použité PLC Siemens série Simatic S7-300.
- PlcSim je emulátor PLC, ktorý vám umožňuje spúšťať a ladiť programy pre PLC Siemens S7.
Zdá sa, že súbor DB100.bin obsahuje dátový blok DB100 PLC: 00000000: 0100 0102 6e02 0401 0206 0100 0101 0102 ....n......... 00000010: 1002 0501 0202. ..... ......... 2002: 0501 0206 0100 0102 00000020 0102 7702 0401a0206 ..w............. 0100: 0103 0102 0 02 00000030 0501 ................ 0202: 1602 0501 0206 0100 0104 0102 00000040a7502 0401 u............... 0206: 0100 0105 0102 0 02 0501 00000050 0202............1602. 0501: 0206 0100 0106 0102 3402 4 00000060 0401 .......... : 0206 0100 0107 0102 2602a0501 0202 00000070 4 ................ 02: 0501 0206 0100 0108a 0102 3302 0401 3 ...........00000080 0206a0100: 0109 0102b 0 02 0501 0202 1602 00000090 ............F... 0501b0206: 0100 010 0102c 3702 0401 0206 7 ....000000 ........ .. 0c0100: 010d 0102 2202a0501 0202 4602 0501 000000 0 ................ 0206d0100: 010 0102e 3302 0401d0206 0100 3 000000......m. .... 0e010: 0102 0 02 0501 0202 1602 0501 0206 ........#...... 000000f0: 0100 010 0102 6 02 0401 ........ ..... 0206: 0100 010 000000 0 0102 1102 0501 0202 ......%......... 2302: 0501 0206 0100 000000 0 0110 ....... .....&. 0102: 3502 0401 0206c0100 0111 0102 5 ....L......
Ako už názov napovedá, súbor block_upload_traffic.pcapng obsahuje výpis blokovej prenosovej prevádzky do PLC.
Stojí za zmienku, že tento dopravný výpis na mieste súťaže počas konferencie bolo o niečo ťažšie získať. K tomu bolo potrebné porozumieť skriptu zo súboru projektu pre TeslaSCADA2. Z neho bolo možné pochopiť, kde sa nachádza výpis zašifrovaný pomocou RC4 a aký kľúč je potrebné použiť na jeho dešifrovanie. Výpisy dátových blokov na mieste je možné získať pomocou klienta protokolu S7. Na to som použil demo klienta z balíka Snap7.
Extrahovanie blokov spracovania signálu z výpisu premávky
Pri pohľade na obsah výpisu môžete pochopiť, že obsahuje bloky spracovania signálu OB1, FC1, FC2 a FC3:
Tieto bloky musia byť odstránené. Dá sa to urobiť napríklad pomocou nasledujúceho skriptu, ktorý predtým skonvertoval návštevnosť z formátu pcapng na 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 = ''
Po preskúmaní výsledných blokov si všimnete, že vždy začínajú bajtmi 70 70 (pp). Teraz sa musíte naučiť, ako ich analyzovať. Tip na priradenie naznačuje, že na to musíte použiť PlcSim.
Získavanie ľudsky čitateľných pokynov z blokov
Najprv si skúsme naprogramovať S7-PlcSim tak, že do neho pomocou softvéru Simatic Manager načítame niekoľko blokov s opakujúcimi sa inštrukciami (= Q 0.0) a PLC získané v emulátore uložíme do súboru example.plc. Pri pohľade na obsah súboru môžete ľahko určiť začiatok stiahnutých blokov podľa podpisu 70 70, ktorý sme objavili skôr. Pred blokmi je veľkosť bloku zjavne zapísaná ako 4-bajtová hodnota little-endian.
Keď sme dostali informácie o štruktúre súborov plc, objavil sa nasledujúci akčný plán na čítanie programov PLC S7:
- Pomocou Simatic Manager vytvoríme v S7-PlcSim štruktúru blokov podobnú tej, ktorú sme dostali z výpisu. Veľkosti blokov sa musia zhodovať (to sa dosiahne vyplnením blokov požadovaným počtom inštrukcií) a ich identifikátormi (OB1, FC1, FC2, FC3).
- Uložte PLC do súboru.
- Obsah blokov vo výslednom súbore nahradíme blokmi z výpisu prevádzky. Začiatok blokov je určený podpisom.
- Výsledný súbor načítame do S7-PlcSim a pozrieme sa na obsah blokov v Simatic Manager.
Bloky je možné nahradiť napríklad nasledujúcim kódom:
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 sa vybral možno ťažšou, no stále správnou cestou. Predpokladali sme, že účastníci budú používať program NetToPlcSim, aby PlcSim mohol komunikovať cez sieť, nahrávať bloky do PlcSim cez Snap7 a následne si tieto bloky stiahnuť ako projekt z PlcSim pomocou vývojového prostredia.
Otvorením výsledného súboru v S7-PlcSim môžete čítať prepísané bloky pomocou Simatic Manager. Hlavné funkcie ovládania zariadenia sú zaznamenané v bloku FC1. Za zmienku stojí najmä premenná #TEMP0, pri ktorej sa po zapnutí zdá, že nastavuje riadenie PLC do manuálneho režimu na základe hodnôt pamäte bitov M2.2 a M2.3. Hodnota #TEMP0 sa nastavuje funkciou FC3.
Ak chcete problém vyriešiť, musíte analyzovať funkciu FC3 a pochopiť, čo je potrebné urobiť, aby vrátila logickú hodnotu.
Bloky spracovania signálu PLC v stánku Low Security na mieste súťaže boli usporiadané podobne, no na nastavenie hodnoty premennej #TEMP0 stačilo do bloku DB1 napísať riadok my ninja way. Kontrola hodnoty v bloku bola jednoduchá a nevyžadovala si hlbokú znalosť blokového programovacieho jazyka. Je zrejmé, že na úrovni High Security bude dosiahnutie manuálneho ovládania oveľa ťažšie a je potrebné pochopiť zložitosti jazyka STL (jeden zo spôsobov, ako programovať PLC S7).
Reverzný blok FC3
Obsah bloku FC3 v reprezentácii 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
Kód je dosť zdĺhavý a niekomu, kto nepozná STL, sa môže zdať komplikovaný. Nemá zmysel analyzovať každú inštrukciu v rámci tohto článku, podrobné pokyny a možnosti jazyka STL nájdete v príslušnej príručke:
Kód po spracovaní]
# Инициализация различных переменных
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
Keď máme predstavu o pokynoch virtuálneho počítača, napíšme malý disassembler na analýzu bajtového kódu v bloku 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
Výsledkom je nasledujúci kód virtuálneho stroja:
Kód virtuálneho stroja
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)
Ako môžete vidieť, tento program jednoducho kontroluje každý znak z DB101 na zhodnosť s určitou hodnotou. Posledný riadok na absolvovanie všetkých kontrol je: n0w u 4r3 7h3 m4573r. Ak je toto vedenie umiestnené v bloku DB101, aktivuje sa manuálne ovládanie PLC a bude možné balónik vybuchnúť alebo vyfúknuť.
To je všetko! Alexey predviedol vysokú úroveň vedomostí hodnú priemyselného ninju :) Výhercom sme poslali pamätné ceny. Všetkým zúčastneným veľmi pekne ďakujeme!
Zdroj: hab.com