📜 ⬆️ ⬇️

In the wake of Industrial Ninja: how PLCs were cracked at Positive Hack Days 9



At the last PHDays 9, we held a hacking contest for a gas pumping plant - the Industrial Ninja competition. There were three stands on the site with different security parameters (No Security, Low Security, High Security), emulating the same industrial process: air was pumped into the balloon (and then descended) under pressure.

Despite the different security parameters, the hardware of the stands was the same: Siemens Simatic S7-300 PLC; emergency blower button and pressure measurement device (connected to digital PLC inputs (DI)); pumping and deaerating valves (connected to digital PLC outputs (DO)) - see the figure below.
')


The PLC, depending on the pressure readings and in accordance with its program, made the decision on blowing the ball or inflating the ball (opened and closed the corresponding valves). However, all the stands were provided with a manual control mode, which made it possible to control the states of the valves without any restrictions.

The stands were distinguished by the complexity of enabling this mode: on an unprotected stand, this was easiest to do, and at the High Security stand, respectively, more difficult.

In two days, five of the six tasks were solved; The first place winner earned 233 points (he spent a week preparing for the competition). Three winners: I place - a1exdandy, II - Rubikoid, III - Ze.

However, during PHDays, none of the participants could not overcome all three stands, so we decided to make an online competition and in early June we published the most difficult task. Participants had to complete the task in a month, find the flag, describe the solution in detail and interestingly.

Under the cut, we publish the analysis of the best solution of the task sent in a month, Alexey Kovrizhnykh (a1exdandy) from the Digital Security company, who took the first place in the competition during PHDays, found it. Below we provide its text with our comments.

Initial analysis


So, there was an archive with files in the task:


The hints.txt file contains the necessary information and tips for solving the task. Here are its contents:

  1. Petrovich told me yesterday that from PlcSim you can load blocks in Step7.
  2. The stand used Siemens Simatic PLC S7-300 series.
  3. PlcSim is a PLC emulator that allows you to execute and debug programs for a Siemens S7 PLC.

  The file DB100.bin, apparently, contains the data block DB100 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 ....... 

Judging by the name, the block_upload_traffic.pcapng file contains a traffic dump for loading blocks on the PLC.

It is worth noting that this traffic dump at the competition site during the conference was a bit more difficult to get. To do this, it was necessary to understand the script from the project file for TeslaSCADA2. From it it was possible to understand where the encrypted using RC4 dump is and what key should be used to decrypt it. Dumps of data blocks on the site could be obtained using an S7 protocol client. I used a Snap7 demo client for this.

Extraction of signal processing units from traffic dump


Looking at the contents of the dump, you can see that OB1, FC1, FC2 and FC3 signal processing blocks are transmitted in it:



It is necessary to extract these blocks. This can be done, for example, with the following script, by first converting traffic from pcapng format to 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 = '' 

After examining the blocks, you can see that they always begin with bytes 70 70 (pp). Now you need to learn how to analyze them. The task hint suggests that you need to use PlcSim for this.

Getting human readable instructions from blocks


To begin with, we will try to program S7-PlcSim by loading several blocks with duplicate instructions (= Q 0.0) into it using Simatic Manager software, and save the data obtained in the PLC emulator into the file example.plc. Looking at the contents of the file, you can easily determine the beginning of the loaded blocks by the signature 70 70, which we found earlier. Before the blocks, apparently, the block size is written in the form of 4-byte little-endian values.



After we received information about the structure of the plc-files, the following action plan appeared for reading the S7 PLC programs:

  1. Using Simatic Manager, we create in S7-PlcSim a block structure similar to the one we got from the dump. The block sizes must be the same (achieved by filling the blocks with the required number of instructions) and their identifiers (OB1, FC1, FC2, FC3).
  2. Save the PLC to a file.
  3. Replace the contents of the blocks in the resulting file with blocks from the traffic dump. The beginning of the blocks is determined by the signature.
  4. The resulting file is loaded into S7-PlcSim and we look at the contents of the blocks in the Simatic Manager.

Replacing blocks can be done, for example, with the following 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) 

Alex went on, perhaps more difficult, but still the right way. We assumed that the participants would use the NetToPlcSim program so that they could communicate over the network with PlcSim, load the blocks into PlcSim via Snap7, and then download these blocks as a project from PlcSim using the development environment.

By opening the file in S7-PlcSim, you can read the rewritten blocks using Simatic Manager. The main functions of device management are recorded in the FC1 block. Particular attention is attracted by the variable # TEMP0, when turned on, it appears that the PLC control is transferred to manual mode based on the values ​​of bit memory M2.2 and M2.3. The value # TEMP0 is set by the function FC3.



To solve a task, it is necessary to analyze the function FC3 and understand what needs to be done so that it returns a logical unit.

The PLC signal processing units at the Low Security booth at the competition site were arranged in the same way, but to set the value of the # TEMP0 variable, it was enough to write the line my ninja way in the DB1 block. Validation of the value in the block was arranged clearly and did not require deep knowledge of the programming language of the blocks. Obviously, at the High Security level, it will be much more difficult to achieve manual control and it is necessary to understand the intricacies of the STL language (one of the ways of programming an S7 PLC).

FC3 block reverse


The contents of the FC3 block in the STL view:
  LB#16#0 T #TEMP13 T #TEMP15 LP#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 LW#16#0 <>I JC M00d LP#DBX 0.0 LAR1 M00d: LB [AR1,P#0.0] T #TEMP5 LW#16#1 ==I JC M007 L #TEMP5 LW#16#2 ==I JC M008 L #TEMP5 LW#16#3 ==I JC M00f L #TEMP5 LW#16#4 ==I JC M00e L #TEMP5 LW#16#5 ==I JC M011 L #TEMP5 LW#16#6 ==I JC M012 JU M010 M007: +AR1 P#1.0 LP#DBX 0.0 LAR2 LB [AR1,P#0.0] LC#8 *I +AR2 +AR1 P#1.0 LB [AR1,P#0.0] JL M003 JU M001 JU M002 JU M004 M003: JU M005 M001: OPN DB 101 LB [AR2,P#0.0] T #TEMP0 JU M006 M002: OPN DB 101 LB [AR2,P#0.0] T #TEMP1 JU M006 M004: OPN DB 101 LB [AR2,P#0.0] T #TEMP2 JU M006 M00f: +AR1 P#1.0 LB [AR1,P#0.0] LC#8 *IT #TEMP11 +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP7 LP#M 100.0 LAR2 L #TEMP7 LC#8 *I +AR2 TAR2 #TEMP9 TAR1 #TEMP4 OPN DB 101 LP#DBX 0.0 LAR1 L #TEMP11 +AR1 LAR2 #TEMP9 LB [AR2,P#0.0] TB [AR1,P#0.0] L #TEMP4 LAR1 JU M006 M008: +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP3 +AR1 P#1.0 LB [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 LB [AR1,P#0.0] T #TEMP7 LP#M 100.0 LAR2 L #TEMP7 LC#8 *I +AR2 TAR2 #TEMP9 +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP8 LP#M 100.0 LAR2 L #TEMP8 LC#8 *I +AR2 TAR2 #TEMP10 TAR1 #TEMP4 LAR1 #TEMP9 LAR2 #TEMP10 LB [AR1,P#0.0] LB [AR2,P#0.0] AW INVI T #TEMP12 LB [AR1,P#0.0] LB [AR2,P#0.0] OW L #TEMP12 AW TB [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 LB [AR1,P#0.0] T #TEMP7 LP#M 100.0 LAR2 L #TEMP7 LC#8 *I +AR2 TAR2 #TEMP9 +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP8 LP#M 100.0 LAR2 L #TEMP8 LC#8 *I +AR2 TAR2 #TEMP10 TAR1 #TEMP4 LAR1 #TEMP9 LAR2 #TEMP10 LB [AR1,P#0.0] LB [AR2,P#0.0] -ITB [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 LB [AR1,P#0.0] T #TEMP7 LP#M 100.0 LAR2 L #TEMP7 LC#8 *I +AR2 TAR2 #TEMP9 +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP8 LP#M 100.0 LAR2 L #TEMP8 LC#8 *I +AR2 TAR2 #TEMP10 TAR1 #TEMP4 LAR1 #TEMP9 LAR2 #TEMP10 LB [AR1,P#0.0] LB [AR2,P#0.0] ==I JCN M013 JU M014 M013: LP#DBX 0.0 LAR1 T #TEMP4 LB#16#0 T #TEMP6 JU M006 M014: L #TEMP4 LAR1 L #TEMP13 LL#1 +IT #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: LP#DBX 0.0 LAR1 L 0 T #TEMP6 TAR1 #TEMP4 M005: TAR1 #TEMP4 CLR = #TEMP16 L #TEMP13 LL#20 ==IS #TEMP16 L #TEMP15 ==IA #TEMP16 JC M017 L #TEMP13 LL#20 <IS #TEMP16 L #TEMP15 ==IA #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 

The code is quite voluminous and a person unfamiliar with STL may seem complicated. There is no sense in analyzing each instruction in this article; details of the instructions and features of the STL language can be found in the appropriate manual: Statement List (STL) for S7-300 and S7-400 Programming . Here I will give the same code after processing - renaming labels and variables and adding comments describing the operation algorithm and some constructions of the STL language. Immediately, I note that in this block a virtual machine is implemented that executes some bytecode located in the DB100 block, the contents of which we know. Virtual machine instructions are 1 byte of operation code and bytes of arguments, one byte for each argument. All the instructions considered have two arguments each; I denoted their values ​​in the comments as X and Y.

Code after processing
]
 #    LB#16#0 T #CHECK_N #     T #COUNTER_N #     LP#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 LW#16#0 <>I JC M00d LP#DBX 0.0 LAR1 #  switch - case     M00d: LB [AR1,P#0.0] T #OPCODE LW#16#1 ==I JC OPCODE_1 L #OPCODE LW#16#2 ==I JC OPCODE_2 L #OPCODE LW#16#3 ==I JC OPCODE_3 L #OPCODE LW#16#4 ==I JC OPCODE_4 L #OPCODE LW#16#5 ==I JC OPCODE_5 L #OPCODE LW#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 LP#DBX 0.0 LAR2 LB [AR1,P#0.0] #   X (  DB101) LC#8 *I +AR2 +AR1 P#1.0 LB [AR1,P#0.0] #   Y ( ) JL M003 #  switch - case    Y JU M001 #      . JU M002 #       JU M004 #      M003: JU LOOPEND M001: OPN DB 101 LB [AR2,P#0.0] T #REG0 #   DB101[X]  REG[0] JU PRE_LOOPEND M002: OPN DB 101 LB [AR2,P#0.0] T #REG1 #   DB101[X]  REG[1] JU PRE_LOOPEND M004: OPN DB 101 LB [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 LB [AR1,P#0.0] T #TEMP3 +AR1 P#1.0 LB [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 LB [AR1,P#0.0] T #TEMP7 #   - X LP#M 100.0 LAR2 L #TEMP7 LC#8 *I +AR2 TAR2 #TEMP9 # REG[X] +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP8 LP#M 100.0 LAR2 L #TEMP8 LC#8 *I +AR2 TAR2 #TEMP10 # REG[Y] TAR1 #POINTER LAR1 #TEMP9 # REG[X] LAR2 #TEMP10 # REG[Y] LB [AR1,P#0.0] LB [AR2,P#0.0] AW INVI T #TEMP12 # ~(REG[Y] & REG[X]) LB [AR1,P#0.0] LB [AR2,P#0.0] OW L #TEMP12 AW # (~(REG[Y] & REG[X])) & (REG[Y] | REG[X]) -     TB [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 LB [AR1,P#0.0] T #TEMP7 LP#M 100.0 LAR2 L #TEMP7 LC#8 *I +AR2 TAR2 #TEMP9 # REG[X] +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP8 LP#M 100.0 LAR2 L #TEMP8 LC#8 *I +AR2 TAR2 #TEMP10 # REG[Y] TAR1 #POINTER LAR1 #TEMP9 LAR2 #TEMP10 LB [AR1,P#0.0] LB [AR2,P#0.0] -I # ACCU1 = ACCU2 - ACCU1, REG[X] - REG[Y] TB [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 LB [AR1,P#0.0] T #TEMP7 # REG[X] LP#M 100.0 LAR2 L #TEMP7 LC#8 *I +AR2 TAR2 #TEMP9 # REG[X] +AR1 P#1.0 LB [AR1,P#0.0] T #TEMP8 LP#M 100.0 LAR2 L #TEMP8 LC#8 *I +AR2 TAR2 #TEMP10 # REG[Y] TAR1 #POINTER LAR1 #TEMP9 # REG[Y] LAR2 #TEMP10 # REG[X] LB [AR1,P#0.0] LB [AR2,P#0.0] ==I JCN M013 JU M014 M013: LP#DBX 0.0 LAR1 T #POINTER LB#16#0 T #TEMP6 JU PRE_LOOPEND M014: L #POINTER LAR1 #   #CHECK_N L #CHECK_N LL#1 +IT #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: LP#DBX 0.0 LAR1 L 0 T #TEMP6 TAR1 #POINTER LOOPEND: TAR1 #POINTER CLR = #TEMP16 L #CHECK_N LL#20 ==IS #TEMP16 L #COUNTER_N ==IA #TEMP16 #   ,  #CHECK_N == #COUNTER_N == 20 JC GOOD L #CHECK_N LL#20 <IS #TEMP16 L #COUNTER_N ==IA #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 

After getting an idea of ​​the virtual machine instructions, let's write a small disassembler to parse the bytecode in the 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 

As a result, we get the following 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) 

As you can see, this program simply checks each character from DB101 for equality to a specific value. The final line for passing all checks is: n0w u 4r3 7h3 m4573r. If this line is placed in block DB101, then the PLC manual control is activated and the balloon can be blown up or blown off.

That's all! Alexey demonstrated a high level of knowledge, worthy of an industrial ninja :) The winner, we sent memorable prizes. Many thanks to all participants!

Source: https://habr.com/ru/post/460615/


All Articles