Industrial Ninja pēdās: kā PLC tika uzlauzts Positive Hack Days 9

Industrial Ninja pēdās: kā PLC tika uzlauzts Positive Hack Days 9

Pagājušajā PHDays 9 mēs rīkojām konkursu, lai uzlauztu gāzes sūkņu iekārtu - konkursu Industriālā nindzja. Vietnē bija trīs stendi ar dažādiem drošības parametriem (bez drošības, zema drošība, augsta drošība), imitējot to pašu rūpniecisko procesu: gaiss zem spiediena tika iesūknēts balonā (un pēc tam atbrīvots).

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.

Industrial Ninja pēdās: kā PLC tika uzlauzts Positive Hack Days 9

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:

  1. Petrovičs man vakar teica, ka jūs varat ielādēt blokus no PlcSim Step7.
  2. Stendā tika izmantots Siemens Simatic S7-300 sērijas PLC.
  3. 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:

Industrial Ninja pēdās: kā PLC tika uzlauzts Positive Hack Days 9

Š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.

Industrial Ninja pēdās: kā PLC tika uzlauzts Positive Hack Days 9

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:

  1. 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).
  2. Saglabājiet PLC failā.
  3. Mēs aizvietojam iegūtā faila bloku saturu ar blokiem no satiksmes izgāztuves. Bloku sākumu nosaka paraksts.
  4. 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.

Industrial Ninja pēdās: kā PLC tika uzlauzts Positive Hack Days 9

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ā: Paziņojumu saraksts (STL) S7-300 un S7-400 programmēšanai. Šeit es prezentēšu to pašu kodu pēc apstrādes - etiķešu un mainīgo pārdēvēšanas un komentāru pievienošanas, kas apraksta darbības algoritmu un dažas STL valodas konstrukcijas. Ļaujiet man nekavējoties atzīmēt, ka attiecīgajā blokā ir virtuālā mašīna, kas izpilda kādu baitu kodu, kas atrodas DB100 blokā un kura saturu mēs zinām. Virtuālās mašīnas instrukcijas sastāv no 1 baita darbības koda un argumentu baitiem, viens baits katram argumentam. Visām aplūkotajām instrukcijām ir divi argumenti; es to vērtības komentāros norādīju kā X un Y.

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

Pievieno komentāru