Sa mga yapak ng Industrial Ninja: kung paano na-hack ang isang PLC sa Positive Hack Days 9

Sa mga yapak ng Industrial Ninja: kung paano na-hack ang isang PLC sa Positive Hack Days 9

Noong nakaraang PHDays 9 ay nagsagawa kami ng kompetisyon sa pag-hack ng isang gas pumping plant - kompetisyon Industrial Ninja. Mayroong tatlong mga stand sa site na may iba't ibang mga parameter ng seguridad (Walang Seguridad, Mababang Seguridad, Mataas na Seguridad), na tinutulad ang parehong proseso ng industriya: ang hangin sa ilalim ng presyon ay ibinuhos sa isang lobo (at pagkatapos ay inilabas).

Sa kabila ng iba't ibang mga parameter ng kaligtasan, ang komposisyon ng hardware ng mga stand ay pareho: Siemens Simatic PLC S7-300 series; pindutan ng emergency deflation at aparato sa pagsukat ng presyon (nakakonekta sa PLC digital inputs (DI)); mga balbula na gumagana para sa inflation at deflation ng hangin (nakakonekta sa mga digital na output ng PLC (DO)) - tingnan ang figure sa ibaba.

Sa mga yapak ng Industrial Ninja: kung paano na-hack ang isang PLC sa Positive Hack Days 9

Ang PLC, depende sa mga pagbabasa ng presyon at alinsunod sa programa nito, ay gumawa ng desisyon na i-deflate o i-inflate ang bola (binuksan at isinara ang kaukulang mga balbula). Gayunpaman, ang lahat ng mga stand ay may manu-manong control mode, na naging posible upang makontrol ang mga estado ng mga balbula nang walang anumang mga paghihigpit.

Ang mga stand ay naiiba sa pagiging kumplikado ng pagpapagana ng mode na ito: sa unprotected stand ito ay pinakamadaling gawin ito, at sa High Security stand ito ay katumbas na mas mahirap.

Lima sa anim na problema ay nalutas sa loob ng dalawang araw; Ang kalahok sa unang lugar ay nakakuha ng 233 puntos (ginugol niya ang isang linggo sa paghahanda para sa kumpetisyon). Tatlong nanalo: I place - a1exdandy, II - Rubikoid, III - Ze.

Gayunpaman, sa panahon ng PHDays, wala sa mga kalahok ang nakayanan ang lahat ng tatlong paninindigan, kaya nagpasya kaming gumawa ng online na kumpetisyon at inilathala ang pinakamahirap na gawain noong unang bahagi ng Hunyo. Kailangang kumpletuhin ng mga kalahok ang gawain sa loob ng isang buwan, hanapin ang bandila, at ilarawan ang solusyon nang detalyado at sa isang kawili-wiling paraan.

Sa ibaba ng hiwa ay ini-publish namin ang isang pagsusuri ng pinakamahusay na solusyon sa gawain mula sa mga ipinadala sa buong buwan, natagpuan ito ni Alexey Kovrizhnykh (a1exdandy) mula sa kumpanya ng Digital Security, na nakakuha ng XNUMXst place sa kompetisyon sa panahon ng PHDays. Sa ibaba ay ipinakita namin ang teksto nito kasama ang aming mga komento.

Paunang pagsusuri

Kaya, ang gawain ay naglalaman ng isang archive na may mga sumusunod na file:

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

Ang hints.txt file ay naglalaman ng kinakailangang impormasyon at mga pahiwatig upang malutas ang gawain. Narito ang mga nilalaman nito:

  1. Sinabi sa akin ni Petrovich kahapon na maaari mong i-load ang mga bloke mula sa PlcSim sa Step7.
  2. Ang Siemens Simatic S7-300 series PLC ay ginamit sa stand.
  3. Ang PlcSim ay isang PLC emulator na nagbibigay-daan sa iyong magpatakbo at mag-debug ng mga programa para sa Siemens S7 PLCs.

Ang DB100.bin file ay lilitaw na naglalaman ng DB100 PLC data block: 00000000: 0100 0102 6e02 0401 0206 0100 0101 0102 ....n......... 00000010: 1002 0501 0202e2002 0501 0206 0100 0102 00000020 ....n......... 0102: 7702 0401 0206: 0100 0103 0102 0 02 . ..... ......... 00000030: 0501 0202 1602 0501 0206 0100 0104 0102a00000040 ..w............. 7502: 0401 0206 0100 0105 0102 0 ................ 02: 0501 00000050 0202 1602 0501 0206 0100a0106 0102 u............... 3402: 4 00000060 0401 0206 0100 0107 0102 2602............0501. . : 0202 00000070 4 02 0501a0206 0100 0108 0102 ................ 3302: 0401 3 00000080 0206a 0100 0109 0102 0 .........02. 0501a0202: 1602 00000090b 0501 0206 0100 010 0102 3702 ......".....F... 0401b0206: 7 000000 0c 0100 010 0102 ........2202 0501 ........ .. 0202c4602: 0501d 000000 0a0206 0100 010 0102 3302 0401 ................ 0206d0100: 3 000000e 0 010d0102 0 02 0501 .... .... 0202e1602: 0501 0206 000000 0 0100 010 0102 6 ........#...... 02f0401: 0206 0100 010 000000 0 0102 ..... ..... 1102: 0501 0202 2302 0501 0206 0100 000000 0 ......%......... 0110: 0102 3502 0401 0206 0100 0111 .. .. .....&. 0102: 5 00000100 1202c0501 0202 2502 0501 ....L......

Gaya ng ipinahihiwatig ng pangalan, ang block_upload_traffic.pcapng file ay naglalaman ng dump ng block upload na trapiko sa PLC.

Kapansin-pansin na ang pagtatambak ng trapiko na ito sa lugar ng kumpetisyon sa panahon ng kumperensya ay medyo mas mahirap makuha. Upang gawin ito, kinakailangan na maunawaan ang script mula sa file ng proyekto para sa TeslaSCADA2. Mula dito, posible na maunawaan kung saan matatagpuan ang dump na naka-encrypt gamit ang RC4 at kung anong key ang kailangang gamitin upang i-decrypt ito. Maaaring makuha ang mga dump ng data block sa site gamit ang S7 protocol client. Para dito ginamit ko ang demo client mula sa Snap7 package.

Pagkuha ng mga bloke sa pagpoproseso ng signal mula sa isang tambakan ng trapiko

Sa pagtingin sa mga nilalaman ng dump, mauunawaan mo na naglalaman ito ng mga bloke sa pagpoproseso ng signal OB1, FC1, FC2 at FC3:

Sa mga yapak ng Industrial Ninja: kung paano na-hack ang isang PLC sa Positive Hack Days 9

Dapat alisin ang mga bloke na ito. Magagawa ito, halimbawa, gamit ang sumusunod na script, na dati nang na-convert ang trapiko mula sa pcapng format sa 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 = ''

Matapos suriin ang mga nagresultang bloke, mapapansin mo na palaging nagsisimula ang mga ito sa mga byte 70 70 (pp). Ngayon ay kailangan mong matutunan kung paano pag-aralan ang mga ito. Iminumungkahi ng pahiwatig ng pagtatalaga na kailangan mong gumamit ng PlcSim para dito.

Pagkuha ng mga tagubiling nababasa ng tao mula sa mga bloke

Una, subukan nating i-program ang S7-PlcSim sa pamamagitan ng pag-load ng ilang block na may paulit-ulit na mga tagubilin (= Q 0.0) dito gamit ang software ng Simatic Manager, at i-save ang PLC na nakuha sa emulator sa example.plc file. Sa pamamagitan ng pagtingin sa mga nilalaman ng file, madali mong matukoy ang simula ng na-download na mga bloke sa pamamagitan ng lagda 70 70, na natuklasan namin nang mas maaga. Bago ang mga bloke, tila, ang laki ng bloke ay nakasulat bilang isang 4-byte na maliit na-endian na halaga.

Sa mga yapak ng Industrial Ninja: kung paano na-hack ang isang PLC sa Positive Hack Days 9

Pagkatapos naming matanggap ang impormasyon tungkol sa istruktura ng mga plc file, lumitaw ang sumusunod na plano ng pagkilos para sa pagbabasa ng mga programa ng PLC S7:

  1. Gamit ang Simatic Manager, gumawa kami ng block structure sa S7-PlcSim na katulad ng natanggap namin mula sa dump. Ang mga sukat ng block ay dapat na tumugma (ito ay nakakamit sa pamamagitan ng pagpuno sa mga bloke ng kinakailangang bilang ng mga tagubilin) ​​at ang kanilang mga identifier (OB1, FC1, FC2, FC3).
  2. I-save ang PLC sa isang file.
  3. Pinapalitan namin ang mga nilalaman ng mga bloke sa nagresultang file ng mga bloke mula sa traffic dump. Ang simula ng mga bloke ay tinutukoy ng lagda.
  4. Nilo-load namin ang resultang file sa S7-PlcSim at tinitingnan ang mga nilalaman ng mga bloke sa Simatic Manager.

Maaaring palitan ang mga bloke, halimbawa, ng sumusunod na code:

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)

Kinuha ni Alexey ang isang marahil mas mahirap, ngunit tamang landas pa rin. Ipinapalagay namin na gagamitin ng mga kalahok ang programang NetToPlcSim para makapag-usap ang PlcSim sa network, mag-upload ng mga block sa PlcSim sa pamamagitan ng Snap7, at pagkatapos ay i-download ang mga block na ito bilang isang proyekto mula sa PlcSim gamit ang development environment.

Sa pamamagitan ng pagbubukas ng resultang file sa S7-PlcSim, maaari mong basahin ang mga na-overwrit na bloke gamit ang Simatic Manager. Ang mga pangunahing function ng control ng device ay naitala sa block FC1. Ang partikular na tala ay ang variable na #TEMP0, na kapag naka-on ay lilitaw upang itakda ang kontrol ng PLC sa manual mode batay sa mga halaga ng memorya ng M2.2 at M2.3 bit. Ang halaga ng #TEMP0 ay itinakda ng function na FC3.

Sa mga yapak ng Industrial Ninja: kung paano na-hack ang isang PLC sa Positive Hack Days 9

Upang malutas ang problema, kailangan mong pag-aralan ang function ng FC3 at maunawaan kung ano ang kailangang gawin upang ito ay magbalik ng isang lohikal.

Ang mga bloke sa pagpoproseso ng signal ng PLC sa Low Security stand sa lugar ng kumpetisyon ay isinaayos sa katulad na paraan, ngunit upang itakda ang halaga ng variable na #TEMP0, sapat na upang isulat ang linyang my ninja na paraan sa DB1 block. Ang pagsuri sa halaga sa isang bloke ay diretso at hindi nangangailangan ng malalim na kaalaman sa block programming language. Malinaw, sa antas ng Mataas na Seguridad, ang pagkamit ng manu-manong kontrol ay magiging mas mahirap at ito ay kinakailangan upang maunawaan ang mga intricacies ng wika ng STL (isa sa mga paraan upang i-program ang S7 PLC).

Baliktarin ang block FC3

Mga nilalaman ng FC3 block sa representasyon ng 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

Ang code ay medyo mahaba at maaaring mukhang kumplikado sa isang taong hindi pamilyar sa STL. Walang punto sa pagsusuri sa bawat pagtuturo sa loob ng balangkas ng artikulong ito; ang mga detalyadong tagubilin at kakayahan ng wikang STL ay matatagpuan sa kaukulang manwal: Listahan ng Pahayag (STL) para sa S7-300 at S7-400 Programming. Dito ipapakita ko ang parehong code pagkatapos ng pagproseso - pagpapalit ng pangalan ng mga label at variable at pagdaragdag ng mga komento na naglalarawan sa algorithm ng operasyon at ilang mga konstruksyon ng wika ng STL. Hayaan akong agad na tandaan na ang bloke na pinag-uusapan ay naglalaman ng isang virtual machine na nagpapatupad ng ilang bytecode na matatagpuan sa DB100 block, ang mga nilalaman na alam natin. Ang mga tagubilin sa virtual machine ay binubuo ng 1 byte ng operating code at byte ng mga argumento, isang byte para sa bawat argument. Ang lahat ng isinasaalang-alang na mga tagubilin ay may dalawang argumento; Itinalaga ko ang kanilang mga halaga sa mga komento bilang X at Y.

Code pagkatapos ng pagproseso]

# Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΡ Ρ€Π°Π·Π»ΠΈΡ‡Π½Ρ‹Ρ… ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ…
      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

Ang pagkakaroon ng ideya ng mga tagubilin sa virtual machine, sumulat tayo ng isang maliit na disassembler upang i-parse ang bytecode sa DB100 block:

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

Bilang resulta, nakukuha namin ang sumusunod na virtual machine code:

Virtual machine code

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)

Gaya ng nakikita mo, sinusuri lamang ng program na ito ang bawat karakter mula sa DB101 para sa pagkakapantay-pantay sa isang tiyak na halaga. Ang huling linya para sa pagpasa sa lahat ng mga tseke ay: n0w u 4r3 7h3 m4573r. Kung ang linyang ito ay inilagay sa block DB101, ang manu-manong kontrol ng PLC ay isaaktibo at posibleng sumabog o ma-deflate ang lobo.


Iyon lang! Nagpakita si Alexey ng mataas na antas ng kaalaman na karapat-dapat sa isang pang-industriyang ninja :) Nagpadala kami ng mga di malilimutang premyo sa nanalo. Maraming salamat sa lahat ng kalahok!

Pinagmulan: www.habr.com

Magdagdag ng komento