S7 PDU 的结构和通用协议标头在上一部分进行了说明。但是,参数标头特定于消息类型,对于作业和 Ack 数据消息,它以函数代码开头。其余字段的结构取决于此值。此函数代码确定消息的用途,并作为进一步讨论的基础。
pcap:S300 设置通信
此消息对(作业和确认数据响应)在每个会话开始时发送,然后才能交换任何其他消息。它用于协商 Ack 队列的大小和最大 PDU 长度,双方声明其支持的值。Ack 队列的长度决定了无需确认即可同时启动的并行作业数。PDU 和队列长度字段都是大端序。
下图显示了参数标头:
pcap:s300 身份验证
这可能是讨论 S7 身份验证和保护机制的好地方(即使它们与实际的通信设置无关)。在配置 CPU 期间,可以设置三种保护模式。
必须注意的是,即使启用了读/写保护,也允许某些操作,例如读取 SZL 列表或读取和写入标记区域。其他操作(如读取或写入对象/功能/数据块)应返回权限错误。
有两个与 CPU 关联的保护级别集,即分配的保护级别和实际保护级别。分配的保护级别是配置期间设置的保护级别,而实际保护级别是适用于通信会话的当前保护级别。
在正常操作期间,需要读/写权限的客户端在通信设置后,通过 SZL 读取(SZL ID:0x0132 SZL 索引:0x0004)查询实际和分配的保护级别。如果需要身份验证,则密码将以用户数据消息的形式发送到设备,这会降低有效保护级别。
在任何人认为这至少提供了一点点安全性之前,让我澄清一下事实并非如此。密码是六个字节,几乎发送 在明文中(用常量进行异或并移位)。它是可重玩的,可以暴力破解。该协议还提供完整性或机密性保护,消息注入和修改是可能的。在S7安全性方面,一般的经验法则是,如果您可以ping设备,则可以拥有它。
这里必须注意的是,S7-1200/1500 系列设备使用的方法略有不同,保护级别的处理方式略有不同,发送的密码明显更长(实际上是密码的哈希值),但它仍然是恒定且可重放的。
帽子:
当事情开始变得有点复杂时,我强烈建议在阅读本节时查看提供的 pcaps(wireshark2 默认启用 S7 剖析器)。数据读取和写入操作是通过指定变量的内存区域、其地址(偏移量)及其大小或类型来执行的。在详细介绍协议之前,我想简要介绍一下 S7 寻址模型。
如前所述,变量通过指定其地址来访问,此地址由三个主要属性组成。内存区域:
还有其他不太常见的内存区域(例如本地数据[L]和外设访问[P]等)。
变量的类型决定了它的长度以及如何解释它。一些例子是:
变量的一个示例地址是 DB123X 2.1,它访问数据块 #123 的第三个字节的第二个位。
在这个简短的绕道之后,让我们回到协议的变量读/写实现。S7 协议支持在具有不同寻址模式的单个消息中查询多个变量读/写。主要有三种模式:
对于每种寻址模式,参数标头的结构方式相同:
S7 PDU 的数据部分因消息的类型(读/写)和方向(作业/确认数据)而异:
总而言之,请求项始终包含变量的描述,并且可以在作业请求中发送其中的多个变量,而数据项包含所描述变量的实际值。数据项结构必须从偶数字节开始,因此如果它们的长度是奇数并且有后面的数据项,则用零字节填充它们。
剩下的讨论是请求/数据项结构的格式。如前所述,它们依赖于所使用的寻址模式,因此将基于此引入它们。
下图显示了请求和数据项结构:
请求项的字段:
40 * 8 + 3
类似地,关联数据项的字段:
len(variable) * count
我只见过这种类型的寻址用于 S400 系列设备,但某些 S300 系列 PLC 也可能支持它。它仅用于访问数据库变量,并提供一种替代方法,以更紧凑的格式在单个项目中处理多个不同的变量。下图显示了请求和数据项结构:
请求项的字段:
数据项的字段:
Pcaps:
这就是事情开始变得混乱的地方。首先,在西门子术语中,下载是指主站向从站发送块数据,上传是另一个方向。在西门子设备上,程序代码和(大部分)程序数据存储在块中,这些块有自己的标头和编码格式,这里不再详细讨论。从协议的角度来看,它们是需要传输的二进制 blob(对于感兴趣的读者,snap7 源提供了 有关块头及其编码的信息)。
西门子设备可识别七种不同类型的模块:
西门子文档中详细描述了这些块的用途。
这些块在上传/下载请求中使用特殊的 ASCII 文件名进行寻址。此文件名的结构如下:
一个示例文件名是 _0800001P,用于将 OB 1 复制到被动文件系统或从被动文件系统复制 OB <>。
** 让我快速说明一下块编码和内容保护。有两种措施来保护设备上的程序内容和数据,并允许分发程序库。第一个称为专有技术保护,如果设置,则会阻止STEP7或TIA显示块的实际内容。不幸的是,这很容易绕过,因为它只是在块的标头中设置了两个位,可以很容易地清除。另一种保护措施是块“加密”,实际上只是线性混淆 变换(按字节 xoring 和随常量旋转)再次应该很容易绕过。因此,不要依赖这些“安全”机制来保护您的专有技术。否则,数据块包含内存的原始初始化映像。程序块包含 MC7(机器代码 7)二进制指令。 **
上传和下载块涉及 3-3 种不同类型的消息对。下面列出了这些以及相关的功能代码:
这些消息的结构非常简单,但是消息序列(尤其是下载)需要一些解释。
上传块序列相当直观,如下所示:
在确认数据 - 开始上传消息中,从站告诉块的长度,然后主站继续发送作业 - 上传块消息,直到收到所有字节。最后,它使用作业 - 结束上传消息关闭上传序列。块的实际数据由从站发送在确认数据 - 上传块消息中。
作业 - 开始上传参数标头:
确认数据 - 开始上传参数标头:
作业 - 上传参数标头:
确认数据 - 上传参数和数据部分:
作业 - 结束上传参数标头:
确认数据 - 结束上传参数标头:
上传和下载之间的主要区别在于,在下载过程中,通信的方向会发生变化,从站成为主站(嗯)。在初始请求下载交换后,从站发送作业消息,主站使用Ack Data回复,这是“仅从站应答”规则的唯一例外。发送所有字节后,主节点(原始字节)将下载结束作业发送到 关闭下载会话。请参阅下面的序列图。
实际消息的结构与上传消息非常相似,因此我仅介绍差异。为了获得准确的语法描述,请在 wireshark 中打开示例 pcap。
作业 - 请求下载消息包含两个额外的字段,即下载块的块长度和块的有效负载长度(没有块标头的长度)。这两个字段都是编码为 ASCII 字符串的十进制数字。响应 Ack 数据 - 请求下载仅包含函数代码。
另一个显着的区别是,尽管存在会话 ID 字段,但它不会被使用(保留0x00000000),而是在每个作业 - 下载块中传输文件名。其余消息的结构与前面讨论的相同。
Pcaps:
(尝试使用 wireshark 过滤器查找 PLC 控制消息)s7comm.param.func == 0x28
PLC控制消息用于在从设备上执行修改其执行/内存状态的不同例程。此类命令用于启动或停止PLC控制程序的执行,激活或删除设备上的程序块或将其配置保存到持久内存中。这些消息的结构相当简单,将在不讨论确切细节的情况下对其进行解释(有关此,请参阅随附的捕获)。
作业 - PLC 控制消息由两个主要部分组成,即被调用方法的 ASCII 名称及其参数(也编码为 ASCII 字符串)。方法名称的结构方式与块传输部分中引入的文件名类似。参数取决于方法类型,可以将它们视为方法的参数。确认数据消息仅包含 PLC 控制功能代码。
一些示例函数名称及其关联参数:
_INSE
:激活设备上下载的块,参数是块的名称(例如OB1)。_DELE
:从设备的文件系统中删除一个块,参数又是块的名称。P_PROGRAM
:设置设备的运行状态(启动、停止、内存重置)。它不带参数来启动设备,但是停止 plc 程序使用不同的功能代码(请参阅下一节)。_GARB
:压缩 PLC 内存。_MODU
:将RAM复制到ROM,该参数包含文件系统标识符(A/E/P)。Pcap s300停止程序
PLC 停止消息与 PLC 控制消息基本相同。唯一的区别是消息中没有参数,例程部分始终设置为 .我不知道为什么它有单独的类型,而不是使用参数来确定它是开始消息还是停止消息。P_PROGRAM
希望它对某些人有用。这在现在可能很明显,但 S7 协议不是一个设计良好的协议。它最初是为了简单地查询寄存器值而创建的,它做得很好,但后来不断添加功能,直到它成为这个怪物。它充满了不一致和不必要的冗余,并且只会因用户数据消息而变得更糟。这些 在尝试为协议编写解析器时,不规则和设计缺陷变得更加明显(和烦人)。
如果 S7 是一辆汽车,它可能看起来像这样: