En el pasado PHDays 9, organizamos una competición de hacking en una planta de bombeo de gas — concurso . En el lugar había tres stands con diferentes niveles de seguridad (Sin Seguridad, Baja Seguridad, Alta Seguridad), emulando el mismo proceso industrial: se inflaba (y luego se desinflaba) un globo con aire a presión.
A pesar de las diferentes configuraciones de seguridad, el equipo de los stands era el mismo: PLC Siemens Simatic de la serie S7-300; botón de desinflado de emergencia y dispositivo de medición de presión (conectados a las entradas digitales del PLC (DI)); válvulas que trabajan en la inflado y desinflado del aire (conectadas a las salidas digitales del PLC (DO)) — ver la imagen a continuación.

El PLC, dependiendo de las lecturas de presión y de acuerdo a su programa, tomaba decisiones sobre desinflar o inflar el globo (abría y cerraba las válvulas correspondientes). Sin embargo, en todos los stands había un modo de operación manual, que permitía controlar los estados de las válvulas sin ninguna restricción.
Los stands diferían en la dificultad de activar este modo: en el stand sin proteger era más fácil hacerlo, mientras que en el stand de Alta Seguridad, más complicado.
Durante dos días se resolvieron cinco de seis tareas; el participante que ocupó el primer lugar logró 233 puntos (se tomó una semana para prepararse para el concurso). Los tres primeros: I lugar — a1exdandy, II — Rubikoid, III — Ze.
Sin embargo, durante los PHDays, ningún participante pudo superar los tres stands, por lo que decidimos hacer un concurso en línea y a principios de junio publicamos la tarea más difícil. Los participantes tenían un mes para completar la tarea, encontrar la bandera y describir detalladamente y de manera interesante la solución.
A continuación, publicamos un análisis de la mejor solución de la tarea entre las enviadas durante el mes, que fue encontrada por Alexey Kovrizhnykh (a1exdandy) de la empresa Digital Security, quien ocupó el I lugar en el concurso durante los PHDays. A continuación, presentamos su texto con nuestros comentarios.
Análisis inicial
Así que, en la tarea había un archivo comprimido con los archivos:
- block_upload_traffic.pcapng
- DB100.bin
- hints.txt
El archivo hints.txt contiene la información y pistas necesarias para resolver la tarea. Aquí está su contenido:
- Petrovich me contó ayer que se pueden cargar bloques desde PlcSim a Step7.
- En el stand se utilizó un PLC Siemens Simatic de la serie S7-300.
- PlcSim es un simulador de PLC que permite ejecutar y depurar programas para PLC Siemens S7.
El archivo DB100.bin parece contener el bloque de datos DB100 del PLC: 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.......
Según el nombre, el archivo block_upload_traffic.pcapng contiene un volcado del tráfico de carga de bloques al PLC.
Cabe señalar que obtener este volcado de tráfico en el sitio del concurso durante la conferencia fue un poco más complicado. Para ello, era necesario entender el script del archivo del proyecto para TeslaSCADA2. De él se podía comprender dónde se encontraba el volcado cifrado con RC4 y qué clave era necesaria para su descifrado. Los volcadose de bloques de datos en el sitio se podían obtener con un cliente del protocolo S7. Para eso, utilicé el cliente de demostración del paquete Snap7.
Extracción de bloques de procesamiento de señales del volcado de tráfico
Al observar el contenido del volcado, se puede notar que se transmiten bloques de procesamiento de señales OB1, FC1, FC2 y FC3:

Es necesario extraer estos bloques. Esto se puede hacer, por ejemplo, con el siguiente script, convirtiendo previamente el tráfico de formato pcapng a 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 = ''Al estudiar los bloques obtenidos, se puede observar que siempre comienzan con los bytes 70 70 (pp). Ahora es necesario aprender a analizarlos. La pista sobre la tarea sugiere que es necesario utilizar PlcSim.
Obtención de instrucciones legibles para humanos de los bloques
Para comenzar, intentaremos programar S7-PlcSim, cargando varios bloques con instrucciones repetidas (= Q 0.0) usando el software Simatic Manager, y guardaremos el resultado obtenido en el emulador PLC en un archivo example.plc. Al observar el contenido del archivo, se puede identificar fácilmente el inicio de los bloques cargados por la firma 70 70, que descubrimos anteriormente. Antes de los bloques, aparentemente se registra el tamaño del bloque en forma de un valor little-endian de 4 bytes.

Después de obtener información sobre la estructura de los archivos plc, se presentó el siguiente plan de acción para leer programas PLC S7:
- Con Simatic Manager, creamos en S7-PlcSim una estructura de bloques similar a la que obtuvimos del volcado. Deben coincidir los tamaños de los bloques (lo que se logra rellenando los bloques con la cantidad necesaria de instrucciones) y sus identificadores (OB1, FC1, FC2, FC3).
- Guardamos el PLC en un archivo.
- Reemplazamos el contenido de los bloques en el archivo obtenido con los bloques del volcado de tráfico. Determinamos el inicio de los bloques por la firma.
- Cargamos el archivo obtenido en S7-PlcSim y observamos el contenido de los bloques en Simatic Manager.
El reemplazo de bloques se puede realizar, por ejemplo, con el siguiente código:
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)Alexey eligió un camino que, aunque posiblemente más complicado, sigue siendo correcto. Supusimos que los participantes utilizarían el programa NetToPlcSim, para que se pudiera comunicar con PlcSim a través de la red, cargarían los bloques en PlcSim mediante Snap7, y luego descargarían estos bloques como un proyecto desde PlcSim usando el entorno de desarrollo.
Al abrir el archivo obtenido en S7-PlcSim, se pueden leer los bloques sobrescritos con Simatic Manager. Las funciones principales de control de dispositivos están registradas en el bloque FC1. La variable #TEMP0 llama especialmente la atención, ya que al activarla, aparentemente, el control del PLC se cambia a modo manual basado en los valores de la memoria de bits M2.2 y M2.3. El valor de #TEMP0 se establece mediante la función FC3.

Para resolver la tarea, es necesario analizar la función FC3 y entender qué se debe hacer para que devuelva uno lógico.
Los bloques de procesamiento de señales PLC en el banco de pruebas de Seguridad Baja en el área del concurso estaban organizados de manera similar, pero para establecer el valor de la variable #TEMP0, era suficiente escribir la línea my ninja way en el bloque DB1. La verificación del valor en el bloque estaba diseñada de manera clara y no requería amplios conocimientos del lenguaje de programación de bloques. Es evidente que, a nivel de Seguridad Alta, lograr el control manual será significativamente más complicado y será necesario entender las sutilezas del lenguaje STL (uno de los métodos de programación de PLC S7).
Reverso del bloque FC3
Contenido del bloque FC3 en representación 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_VALEl código es bastante extenso y puede parecer complicado para alguien no familiarizado con el STL. No tiene sentido desglosar cada instrucción en este artículo; para conocer en detalle las instrucciones y funcionalidades del lenguaje STL, puedes consultar el manual correspondiente: . A continuación, presentaré el mismo código después de su procesamiento: renombrando etiquetas y variables y añadiendo comentarios que describen el algoritmo y algunas construcciones del lenguaje STL. Cabe destacar que en el bloque analizado se implementa una máquina virtual que ejecuta un cierto bytecode que se encuentra en el bloque DB100, cuyo contenido conocemos. Las instrucciones de la máquina virtual consisten en 1 byte de código de operación y bytes de argumentos, uno por cada argumento. Todas las instrucciones analizadas tienen dos argumentos, cuyas valores en los comentarios he denotado como X e Y.
Código después del procesamiento]
# Инициализация различных переменных
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_VALAl obtener una visión general de las instrucciones de la máquina virtual, escribiremos un pequeño desensamblador para analizar el bytecode en el bloque 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{})
'.format(
m[pc + 1], m[pc + 2]))
pc += 3
else:
print('opcode desconocido {}'.format(op))
breakComo resultado, obtendremos el siguiente código de la máquina virtual:
Código de la máquina virtual
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)Como se puede ver, este programa simplemente verifica cada carácter de DB101 para ver si es igual a un valor determinado. La cadena final para pasar todas las verificaciones es: n0w u 4r3 7h3 m4573r. Si se coloca esta cadena en el bloque DB101, se activará el control manual del PLC y se podrá explotar o desinflar el globo.
¡Y eso es todo! Alexey demostró un alto nivel de conocimiento, digno de un ninja industrial 🙂 Hemos enviado premios conmemorativos al ganador. ¡Muchas gracias a todos los participantes!
Fuente: habr.com
