На прошедшем 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 содержит необходимые сведения и подсказки для решения задания. Вот его содержимое:
- Петрович мне вчера рассказал, что из PlcSim можно загрузить блоки в Step7.
- На стенде использовался ПЛК Siemens Simatic серии S7-300.
- 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:
- С помощью Simatic Manager создаем в S7-PlcSim структуру блоков, аналогичную той, что мы получили из дампа. Должны совпадать размеры блоков (достигается с помощью наполнения блоков нужным количеством инструкций) и их идентификаторы (OB1, FC1, FC2, FC3).
- Сохраняем PLC в файл.
- Заменяем содержимое блоков в полученном файле на блоки из дампа трафика. Начало блоков определяем по сигнатуре.
- Полученный файл загружаем в 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
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, то активируется ручное управление ПЛК и можно будет взорвать или сдуть воздушный шар.
Вот и все! Алексей продемонстрировал высокий уровень знаний, достойный индустриального ниндзя 🙂 Победителю мы отправили памятные призы. Большое спасибо всем участникам!
ссылка на оригинал статьи https://habr.com/ru/company/pt/blog/460615/
Добавить комментарий