По следам Industrial Ninja: как взламывали ПЛК на Positive Hack Days 9



    На прошедшем PHDays 9 мы проводили соревнование по взлому завода по перекачке газа — конкурс Industrial Ninja. На площадке было три стенда с различными параметрами безопасности (No Security, Low Security, High Security), эмулирующих одинаковый индустриальный процесс: в воздушный шар закачивался (а потом спускался) воздух под давлением.

    Несмотря на разные параметры безопасности, аппаратный состав стендов был одинаков: ПЛК Siemens Simatic серии S7-300; кнопка аварийного сдува и прибор измерения давления (подсоединены к цифровым входам ПЛК (DI)); клапаны, работающие на накачку и спуск воздуха (подсоединены к цифровым выходам ПЛК (DO)) — см. рисунок ниже.



    ПЛК, в зависимости от показаний давления и в соответствии со своей программой, принимал решение о сдуве или надуве шарика (открывал и закрывал соответствующие клапаны). Однако на всех стендах был предусмотрен режим ручного управления, который давал возможность управлять состояниями клапанов без каких-либо ограничений.

    Стенды отличались сложностью включения данного режима: на незащищенном стенде сделать это было проще всего, а на стенде High Security, соответственно, сложнее.

    За два дня были решены пять из шести задач; участник, занявший первое место, заработал 233 балла (он потратил на подготовку к конкурсу неделю). Тройка призеров: I место — a1exdandy, II — Rubikoid, III — Ze.

    Однако во время PHDays никто из участников не смог одолеть все три стенда, поэтому мы решили сделать онлайн-конкурс и в начале июня опубликовали самое сложное задание. Участники должны были за месяц выполнить задание, найти флаг, подробно и интересно описать решение.

    Под катом мы публикуем разбор лучшего решения задания из присланных за месяц, его нашел Алексей Коврижных (a1exdandy) из компании Digital Security, который занял I место в конкурсе во время PHDays. Ниже мы приводим его текст с нашими комментариями.

    Первоначальный анализ


    Итак, в задании был архив с файлами:

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

    Файл hints.txt содержит необходимые сведения и подсказки для решения задания. Вот его содержимое:

    1. Петрович мне вчера рассказал, что из PlcSim можно загрузить блоки в Step7.
    2. На стенде использовался ПЛК Siemens Simatic серии S7-300.
    3. PlcSim — это эмулятор ПЛК, позволяющий выполнять и отлаживать программы для ПЛК Siemens S7.

    Файл DB100.bin, судя по всему, содержит блок данных DB100 ПЛК:
    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...........&.
    00000120: 0501 0202 4c02 0501 0206 0100            ....L.......

    Судя по названию, файл block_upload_traffic.pcapng содержит дамп трафика загрузки блоков на ПЛК.

    Стоит отметить, что этот дамп трафика на площадке конкурса во время конференции получить было немного сложнее. Для этого необходимо было разобраться в скрипте из файла проекта для TeslaSCADA2. Из него можно было понять, где находится зашифрованный с помощью RC4 дамп и какой ключ необходимо использовать для его расшифровки. Дампы блоков данных на площадке можно было получить с помощью клиента протокола S7. Я для этого использовал демоклиент из пакета Snap7.

    Извлечение блоков обработки сигнала из дампа трафика


    Взглянув на содержимое дампа, можно понять, что в нем передаются блоки обработки сигнала OB1, FC1, FC2 и FC3:



    Необходимо извлечь эти блоки. Это можно сделать, например, следующим скриптом, предварительно сконвертировав трафик из формата pcapng в 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 ('\x1e\x00', '\x1e\x01'):  # upload
                buf += s7_data[4:]
            elif s7_param == '\x1f':
                with open(next(names), 'wb') as f:
                    f.write(buf)
                buf = ''

    Изучив полученные блоки, можно заметить, что они всегда начинаются с байтов 70 70 (pp). Теперь нужно научиться их анализировать. Подсказка к заданию наводит на мысль, что для этого необходимо использовать PlcSim.

    Получение человекочитаемых инструкций из блоков


    Для начала попробуем запрограммировать S7-PlcSim, загрузив в него несколько блоков с повторяющимися инструкциями (= Q 0.0) с помощью ПО Simatic Manager, и сохраним полученный в эмуляторе PLC в файл example.plc. Посмотрев на содержимое файла, можно легко определить начало загруженных блоков по сигнатуре 70 70, которую мы обнаружили ранее. Перед блоками, судя по всему, записан размер блока в виде 4-байтового little-endian значения.



    После того как мы получили сведения о структуре plc-файлов, появился следующий план действий для чтения программ PLC S7:

    1. С помощью Simatic Manager создаем в S7-PlcSim структуру блоков, аналогичную той, что мы получили из дампа. Должны совпадать размеры блоков (достигается с помощью наполнения блоков нужным количеством инструкций) и их идентификаторы (OB1, FC1, FC2, FC3).
    2. Сохраняем PLC в файл.
    3. Заменяем содержимое блоков в полученном файле на блоки из дампа трафика. Начало блоков определяем по сигнатуре.
    4. Полученный файл загружаем в S7-PlcSim и смотрим содержимое блоков в Simatic Manager.

    Замену блоков можно произвести, например, следующим кодом:

    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)

    Алексей пошел по, возможно, более сложному, но все равно правильному пути. Мы предполагали, что участники воспользуются программой NetToPlcSim, чтобы c PlcSim можно было общаться по сети, загрузят блоки в PlcSim через Snap7, а потом скачают эти блоки в виде проекта из PlcSim с помощью среды разработки.

    Открыв полученный файл в S7-PlcSim, можно прочитать перезаписанные блоки с помощью Simatic Manager. Основные функции управления устройствами записаны в блоке FC1. Особое внимание привлекает переменная #TEMP0, при включении которой, судя по всему, управление ПЛК переводится в ручной режим на основе значений битовой памяти M2.2 и M2.3. Значение #TEMP0 устанавливается функцией FC3.



    Для решения задания необходимо проанализировать функцию FC3 и понять, что нужно сделать, чтобы она вернула логическую единицу.

    Блоки обработки сигналов ПЛК на стенде Low Security на площадке конкурса были устроены аналогичным образом, но для установки значения переменной #TEMP0 достаточно было написать строку my ninja way в блок DB1. Проверка значения в блоке была устроена понятно и не требовала глубоких знаний языка программирования блоков. Очевидно, что на уровне High Security добиться ручного управления будет значительно сложнее и необходимо разбираться в тонкостях языка STL (один из способов программирования ПЛК S7).

    Реверс блока FC3


    Содержимое блока FC3 в 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

    Код довольно объемный и человеку, незнакомому с STL, может показаться сложным. Разбирать каждую инструкцию в рамках данной статьи нет смысла, подробно с инструкциями и возможностями языка STL можно ознакомиться в соответствующем мануале: Statement List (STL) for S7-300 and S7-400 Programming. Здесь я приведу тот же самый код после обработки — переименования меток и переменных и добавления комментариев, описывающих алгоритм работы и некоторые конструкции языка STL. Сразу отмечу, что в рассматриваемом блоке реализована виртуальная машина, исполняющая некоторый байт-код, находящийся в блоке DB100, содержимое которого нам известно. Инструкции виртуальной машины представляют собой 1 байт операционного кода и байты аргументов, по одному байту на каждый аргумент. Все рассмотренные инструкции имеют по два аргумента, их значения в комментариях я обозначил как X и Y.

    Код после обработки
    ]
    # Инициализация различных переменных
          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

    Получив представление об инструкциях виртуальной машины, напишем небольшой дизассемблер для разбора байт-кода в блоке 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

    В результате получим следующий код виртуальной машины:

    Код виртуальной машины
    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)

    Как видно, данная программа просто проверяет каждый символ из DB101 на равенство определенному значению. Итоговая строка для прохождения всех проверок: n0w u 4r3 7h3 m4573r. Если данную строку поместить в блок DB101, то активируется ручное управление ПЛК и можно будет взорвать или сдуть воздушный шар.


    Вот и все! Алексей продемонстрировал высокий уровень знаний, достойный индустриального ниндзя :) Победителю мы отправили памятные призы. Большое спасибо всем участникам!
    Positive Technologies
    135,64
    Компания
    Поделиться публикацией

    Комментарии 0

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое