在《工业忍者》问世之后:如何在Positive Hack Days 9中入侵PLC



在第9天的PHDay,我们举办了一场黑客攻击加油站的比赛- 工业忍者比赛。 站点上有三个具有不同安全参数(无安全,低安全,高安全)的架子,它们模仿相同的工业过程:在压力下将空气泵入气球(然后下降)。

尽管有各种安全参数,但机架的硬件是相同的:Siemens Simatic S7-300 PLC; 紧急排气按钮和压力测量装置(连接到PLC数字输入(DI)); 用于抽气和排气的阀门(连接到PLC(DO)的数字量输出)-参见下图。



PLC根据压力读数并根据其程序决定吹气或吹气球(打开和关闭相应的阀)。 但是,在所有机架上都提供了手动控制模式,这使得可以不受任何限制地控制阀门状态。

启用该模式的复杂性使它们的显着特点是:在没有保护措施的立场上,这是最容易做到的,而在“高安全性”立场上,这更加困难。

在两天内,解决了六个问题中的五个; 第一名获得233分(他花了整整一周时间为比赛做准备)。 三名优胜者:我获得-a1exdandy,二世-Rubikoid,三世-Ze。

但是,在PHDays期间,没有一个参与者能够克服这三个立场,因此我们决定进行在线竞赛,并在6月初发布了最困难的任务。 参与者必须在一个月内完成任务,找到旗帜,详细而有趣地描述解决方案。

根据削减,我们发布了针对当月发送的任务的最佳解决方案的分析,该解决方案由Digital Security公司的Alexey Kovrizhnykh(a1exdandy)发现,他在PHDays比赛中排名第一。 下面,我们提供其文字并提供我们的评论。

初步分析


因此,在任务中有一个包含文件的存档:

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

hints.txt文件包含解决任务所需的信息和提示。 其内容如下:

  1. Petrovich昨天告诉我,您可以从PlcSim中在Step7中下载块。
  2. 在展台上,使用了西门子Simatic S7-300系列PLC。
  3. PlcSim是一个PLC模拟器,可让您运行和调试Siemens S7 PLC的程序。

文件DB100.bin显然包含一个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 ....... 

从名称来看,block_upload_traffic.pcapng文件包含转储到PLC的块加载流量的转储。

值得注意的是,在会议期间,比赛站点上的这种流量转移更加困难。 为此,有必要从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。

从块中获取人类可读的指令


首先,让我们尝试使用Simatic Manager软件通过将几个带有重复指令(= Q 0.0)的块加载到S7-PlcSim中来进行编程,然后将结果保存在example.plc文件中的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) 

Aleksey采取了可能更复杂但仍然正确的方法。 我们假设参与者将使用NetToPlcSim程序,以便PlcSim可以通过网络进行通信,通过Snap7将块加载到PlcSim中,然后使用开发环境将这些块作为项目从PlcSim下载。

通过在S7-PlcSim中打开结果文件,您可以使用Simatic Manager读取覆盖的块。 主要设备管理功能记录在块FC1中。 变量#TEMP0引起了特别的注意,当它打开时,似乎PLC控制根据位存储器M2.2和M2.3的值切换到手动模式。 #TEMP0由FC3设置。



为了解决该问题,有必要分析FC3功能并了解需要做什么以使其返回逻辑单元。

比赛场地低安全性展台上的PLC信号处理模块以相同的方式排列,但是要设置#TEMP0变量的值,将我的忍者方式写到DB1模块就足够了。 检查块中的值安排得很清楚,不需要深入了解块编程语言。 显然,在“高安全性”级别,要实现手动控制将更加困难,并且您需要了解STL语言的复杂性(对S7 PLC进行编程的方式之一)。

反向块FC3


STL表示中的FC3块的内容:
  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 

该代码非常繁琐,对于不熟悉STL的人来说,它似乎很复杂。 在本文的框架内分解每条指令是没有意义的,有关详细的指令和STL语言功能,请参见相应的手册: S7-300和S7-400编程的语句列表(STL) 。 在这里,我将在处理后给出相同的代码-重命名标签和变量,并添加描述工作算法和STL语言某些构造的注释。 我马上注意到,在所考虑的块中,实现了一个虚拟机,该虚拟机执行位于DB100块中的某些字节码,我们知道其内容。 虚拟机指令是1个字节的操作代码和1个字节的参数,每个参数一个字节。 审查的所有指令都有两个参数,我在注释中将它们的值指定为X和Y。

后处理代码
]
 #    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 

了解了虚拟机的指令之后,我们将编写一个小的反汇编程序来解析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块中,则将激活PLC的手动控制,并且有可能使气球爆炸或放气。

仅此而已! 阿列克谢(Alexey)展示出了值得工业忍者使用的高水平知识:)我们向获奖者发送了值得纪念的奖品。 非常感谢所有参与者!

Source: https://habr.com/ru/post/zh-CN460615/


All Articles