Në gjurmët e Industrial Ninja: si u hackuan PLC në Positive Hack Days 9

Në gjurmët e Industrial Ninja: si u hackuan PLC në Positive Hack Days 9

Në PHDays 9 që kaloi, ne organizuam një garë për thyerjen e një fabrike të pompimit të gazit – një konkurs Industrial Ninja. Në terren ishin tre stenda me parametra të ndryshëm sigurie (No Security, Low Security, High Security), duke imituar një proces industrial të njëjtë: një balonë u mbush (dhe pastaj u zbraz) me ajër nën presion.

Megjithëse parametrat e sigurisë ishin të ndryshëm, përbërja harduerike e stendave ishte e njëjtë: PLC Siemens Simatic të serisë S7-300; butoni i emergjencës dhe instrumenti për matjen e presionit (të lidhur me hyrjet digjitale të PLC-së (DI)); valvulat që funksionojnë për mbushjen dhe zbrazjen e ajrit (të lidhura me daljet digjitale të PLC-së (DO)) – shihni ilustrimin më poshtë.

Në gjurmët e Industrial Ninja: si u hackuan PLC në Positive Hack Days 9

PLC-ja, në varësi të leximeve të presionit dhe sipas programit të saj, merrte vendimin për të zbrazur ose mbushur balonën (hapte dhe mbyllte valvulat përkatëse). Megjithatë, të gjitha stendat kishin një mod të menaxhimit manual, i cili lejonte kontrollin e gjendjeve të valvulave pa asnjë kufizim.

Stendat dalloheshin nga komplekset e aktivizimit të këtij modu: në stendën pa mbrojtje, ishte më e lehtë ta realizoje, ndërsa në stendën me siguri të lartë, përkatësisht, ishte më e vështirë.

Në dy ditë u zgjidhën pesë nga gjashtë detyrat; pjesëmarrësi që fitoi vendin e parë arriti 233 pikë (ai kaloi një javë për t'u përgatitur për konkursin). Treshja e fituesve: Vendi I – a1exdandy, II – Rubikoid, III – Ze.

Megjithatë, gjatë PHDays, askush nga pjesëmarrësit nuk mundi të kalonte të tri stendat, prandaj ne vendosëm të organizojmë një konkurs në internet dhe në fillim të qershorit publikuam detyrën më të vështirë. Pjesëmarrësit duhej të përfundonin detyrën brenda një muaji, të gjenin flamurin dhe të përshkruanin zgjidhjen në mënyrë të detajuar dhe interesante.

Në vazhdim po publikojmë analizën e zgjidhjes më të mirë të detyrës nga ato të dërguara brenda muajit, të cilën e gjeti Alexey Kovrizhnykh (a1exdandy) nga kompania Digital Security, i cili fitoi vendin I në konkursin gjatë PHDays. Më poshtë ofrojmë tekstin e tij me komentet tona.

Analiza fillestare

Pra, në detyrë kishte një arkiv me skedarë:

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

Skedari hints.txt përmban informacionin dhe sugjerimet e nevojshme për zgjidhjen e detyrës. Ja përmbajtja e tij:

  1. Petroviç më tregoi dje se nga PlcSim mund të shkarkohen blloqe në Step7.
  2. Në stendë u përdor PLC Siemens Simatic i serisë S7-300.
  3. PlcSim është një emulator PLC që lejon ekzekutimin dhe debugging-un e programeve për PLC Siemens S7.

Skedari DB100.bin, sipas dukjes, përmban bllokun e të dhënave DB100 të PLC-së:
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...........&.

Sipas emrit, skedari block_upload_traffic.pcapng përmban një dump të trafikut të ngarkesës së blloqeve në PLC.

Duhet theksuar se ky dump trafiku në terrenin e konkursit gjatë konferencës ishte pak më i vështirë për t'u marrë. Për këtë, duhej të kuptohej skripti nga skedari i projektit për TeslaSCADA2. Nga ai mund të kuptohej se ku ndodhet dump-i i përshkruar me RC4 dhe cili çelës duhej përdorur për dekodimin e tij. Dumps e blloqeve të të dhënave mund të merreshin në terren duke përdorur klientin e protokollit S7. Unë përdora klientin demo nga paketa Snap7 për këtë.

Nxjerrja e blloqeve të përpunimit të sinjalit nga dump-i i trafikut

Duke shqyrtuar përmbajtjen e dump-it, mund të kuptohet se në të dërgoheshin blloqe të përpunimit të sinjalit OB1, FC1, FC2 dhe FC3:

Në gjurmët e Industrial Ninja: si u hackuan PLC në Positive Hack Days 9

Është e nevojshme të nxirren këto blloqe. Kjo mund të bëhet, për shembull, me këtë skript, pas konvertimit të trafikut nga formati pcapng në 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 = ''

Duke studiuar blloqet e marrë, mund të vëresh se ato gjithmonë fillojnë me byte 70 70 (pp). Tani duhet të mësojmë t'i analizojmë ato. Sugjerimi për detyrën sugjeron se për këtë duhet të përdoret PlcSim.

Marrja e instruktimeve të lexueshme nga blloqet

Fillo një përpjekje për të programuar S7-PlcSim, duke ngarkuar disa blloqe me udhëzime të përsëritura (= Q 0.0) me anë të softuerit Simatic Manager, dhe do ta ruajmë bllokun e krijuar në emulatorin PLC në skedarin example.plc. Duke parë përmbajtjen e skedarit, është e lehtë të përcaktohet fillimi i blloqeve të ngarkuara sipas nënshkrimit 70 70, të cilin e zbuluam më parë. Para blloqeve, duket se është regjistruar madhësia e bllokut në formën e një vlerë 4-bajtësh little-endian.

Në gjurmët e Industrial Ninja: si u hackuan PLC në Positive Hack Days 9

Pasi morëm informacion mbi strukturën e skedarëve plc, plani ynë i ardhshëm për leximin e programeve PLC S7 është si më poshtë:

  1. Me Simatic Manager krijojmë në S7-PlcSim një strukturë bllokesh, e ngjashme me atë që kemi marrë nga dump-i. Madhësitë e blloqeve duhet të përputhen (arrijmë këtë me anë të mbushjes së blloqeve me numrin e nevojshëm të udhëzimeve) dhe identifikuesit e tyre (OB1, FC1, FC2, FC3).
  2. E ruajmë PLC në skedar.
  3. Zëvendësojmë përmbajtjen e blloqeve në skedarin e marrë me blloqet nga dump-i i trafik. Fillimi i blloqeve përcaktohet sipas nënshkrimit.
  4. Skedarin e marrë e ngarkojmë në S7-PlcSim dhe shohim përmbajtjen e blloqeve në Simatic Manager.

Zëvendësimi i blloqeve mund të kryhet, për shembull, me kodin e mëposhtëm:

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)

Aleksej zgjodhi një rrugë, ndoshta më të komplikuar, por sërish të saktë. Ne supozuam se pjesëmarrësit do të përdornin programin NetToPlcSim për të komunikuar me PlcSim nëpërmjet rrjetit, do të ngarkonin blloqet në PlcSim përmes Snap7, dhe pastaj do të shkarkonin këto blloqe si një projekt nga PlcSim duke përdorur mjedisin e zhvillimit.

Duke hapur skedarin e marrë në S7-PlcSim, mund të lexojmë blloqet e riprogramuar me anë të Simatic Manager. Funksionet kryesore për menaxhimin e pajisjeve janë regjistruar në bllokun FC1. Vëmendje e veçantë tërheq variabli #TEMP0, me aktivizimin e të cilit, duket se menaxhimi i PLC-së kalon në modin manual në përputhje me vlerat e kujtesës bitore M2.2 dhe M2.3. Vlera #TEMP0 vendoset nga funksioni FC3.

Në gjurmët e Industrial Ninja: si u hackuan PLC në Positive Hack Days 9

Për përmbushjen e detyrës është e nevojshme të analizojmë funksionin FC3 dhe të kuptojmë se çfarë duhet të bëjmë që ai të kthejë një njësi logjike.

Blloket e përpunimit të sinjalit të PLC-së në testin Low Security në vendin e konkursit ishin ndërtuar në mënyrë të ngjashme, por për të vendosur vlerën e variablit #TEMP0 mjaftonte të shkruhej rreshti my ninja way në bllokun DB1. Kontrolli i vlerës në bllok ishte i organizuar në mënyrë të qartë dhe nuk kërkonte njohuri të thella mbi gjuhën e programimit të blloqeve. E qartë është se në nivelin e High Security, arritja e menaxhimit manual do të ishte ndjeshëm më e komplikuar dhe është e nevojshme të kuptohet në imtësi gjuhën STL (një nga metodat e programimit të PLC S7).

Rivendosja e bllokut FC3

Përmbajtja e bllokut FC3 në përfaqësimin STL:

      Kod është mjaft i gjatë dhe për një person që nuk e njeh STL-në, mund të duket i komplikuar. Nuk ka kuptim të shqyrtojmë secilën komandu në kuadër të këtij artikulli, mund të informoheni më në detaje mbi komandat dhe mundësitë e gjuhës STL në manualin përkatës:

Listë e Deklaratave (STL) për Programimin S7-300 dhe S7-400 . Këtu do të sjell të njëjtin kod pas përpunimit — riemërimin e etiketave dhe variablave dhe shtimit të komenteve që përshkruajnë algoritmin e funksionimit dhe disa struktura të gjuhës STL. Menjëherë theksoj se në blokun e shqyrtuar është realizuar një makinë virtuale, e cila ekzekuton disa kod byte, i cili ndodhet në blokun DB100, përmbajtja e të cilit na është e njohur. Komandat e makinës virtuale përbëhen nga 1 byte kodi operativ dhe byte të argumenteve, me nga një byte për çdo argument. Të gjitha komandat e shqyrtuara kanë dy argumente, vlerat e të cilëve në komente i kam shënuar si X dhe Y.Kodi pas përpunimit

Pas arritjes së një përfaqësimi të komandave të makinës virtuale, do të shkruajmë një disassembler të vogël për të analizuar kodin byte në blokun DB100:]

# Инициализация различных переменных
      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

import string alph = string.ascii_letters + string.digitswith open('DB100.bin', 'rb') as f: m = f.read()pc = 0while 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{}) '.format( m[pc + 1], m[pc + 2])) pc += 3 else: print('unk opcode {}'.format(op)) break

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

Si do të marrim kodin e mëposhtëm për makinat virtuale:

Kodi i makinës virtuale

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)

Siç duket, ky program thjesht kontrollon çdo simbol nga DB101 për barazim me një vlerë të caktuar. Shqisa përfundimtare për kalimin e të gjitha kontrollimeve: n0w u 4r3 7h3 m4573r. Nëse ky tekst vendoset në bllokun DB101, do të aktivizohet menaxhimi manual i PLC dhe do të jetë e mundur të shpërthehet ose të lihet një balon.

Këtu është! Aleksei tregoi një nivel të lartë njohurie, të merituar për një ninja industrial 🙂 Fituesve u dërguam çmime përkujtimore. Faleminderit të gjithëve pjesëmarrësve!

Burimi: habr.com

Bleni hostim të besueshëm për faqe me mbrojtje nga DDoS, serverë VPS VDS 🔥 Bleni hostim të besueshëm për faqe me mbrojtje nga DDoS, serverë VPS VDS | ProHoster