
在第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文件包含解决任务所需的信息和提示。 其内容如下:
- Petrovich昨天告诉我,您可以从PlcSim中在Step7中下载块。
- 在展台上,使用了西门子Simatic S7-300系列PLC。
- 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之后,例如,可以使用以下脚本来完成此操作:
研究了收到的块后,您会发现它们始终以字节70 70(pp)开头。 现在,您需要学习如何分析它们。 该任务的提示表明,您需要为此使用PlcSim。
从块中获取人类可读的指令
首先,让我们尝试使用Simatic Manager软件通过将几个带有重复指令(= Q 0.0)的块加载到S7-PlcSim中来进行编程,然后将结果保存在example.plc文件中的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)
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的人来说,它似乎很复杂。 在本文的框架内分解每条指令是没有意义的,有关详细的指令和STL语言功能,请参见相应的手册:
S7-300和S7-400编程的语句列表(STL) 。 在这里,我将在处理后给出相同的代码-重命名标签和变量,并添加描述工作算法和STL语言某些构造的注释。 我马上注意到,在所考虑的块中,实现了一个虚拟机,该虚拟机执行位于DB100块中的某些字节码,我们知道其内容。 虚拟机指令是1个字节的操作代码和1个字节的参数,每个参数一个字节。 审查的所有指令都有两个参数,我在注释中将它们的值指定为X和Y。
了解了虚拟机的指令之后,我们将编写一个小的反汇编程序来解析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)展示出了值得工业忍者使用的高水平知识:)我们向获奖者发送了值得纪念的奖品。 非常感谢所有参与者!