You are on page 1of 18

西门子 S7 通信.

第 1 部分:一般结构

我与西门子 PLC 合作已经有一段时间了,主要是开发与其通信或观察/模拟其通


信的应用程序。我认为是时候分享我收集的 S7 协议的知识了,因为有些人认为
它很有用,很有趣。本文旨在帮助那些希望深入了解西门子 S7 通信协议的人,
并帮助开发干扰这些设备的软件。这个协议的文档不全面,还有很多部分有待
发掘。在撰写本文时,我只能访问 S-300 和 S-400 系列设备(具体来说,S315-
2A 和 S417),并且我以前从未使用过 S-200/S-1200/S-1500 系列 PLC,因此这里
不涉及这些特定功能。

据我所知,S7 协议没有公开可用的文档,但是有几个值得注意的项目可以帮助
处理它。Davide Nardella 创建了一个很棒的开源通信库 Snap7,它实现了基本的
通信场景。该库附带了 S7 协议的基本结构的广泛文档。另一个伟大的项目是
Thomas W.的 S7 Wireshark 剖析器,它覆盖了协议的大部分,其源代码包含一个
冗长的协议常数列表。在我和西门子设备一起工作的那些年里,这些对我来说
是无价之宝。由于没有官方文件,在 S7 议定书中不存在官方术语。在本文的其
余部分中,我试图遵守上述项目中使用的术语。

1.西门子通讯方案

在进入更多的技术细节之前,我想简单介绍一下西门子通讯的基本原理。当我
谈到“S7 协议”时,我指的是以太网 S7 通信,它主要用于将 PLC 连接到(I)PC 站
(PG/PC-PLC 通信)。这不会与西门子设备使用的不同现场总线协议混淆,例如
MPI、Profibus、IE 和 Proinet(这是用于将 PLC 连接到 IO 模块的基于以太网的协
议,而不是设备的管理协议)。

大多数时候,西门子通信遵循传统的主-从或客户端-服务器模型,其中 PC(主/
客户端)向现场设备(从/服务器)发送 S7 请求。这些请求用于向设备查询或
发送数据或发出某些命令。当 PLC 可以是通信主机时,有一些例外,使用
FB14/FB15,设备可以向其他设备发起 GET 和 PUT 请求。

在 S400 系列中,实现了所谓的循环数据 I/O 功能,这与传统发布者-订阅者模型


类似。PC 可以订阅某些事件,而不是 PLC 周期性地将请求的数据推送到网络。
当活动伙伴请求连接并调用 Block Send 同时被动伙伴调用 Block Receive 方法时,
还存在 Partner 或对等模型。

有关 S7 通信的一般概述的更多信息,请参阅西门子 SIMATIC NET 和 SNAP7 文档。


2. the S7 PDU

S7 协议 TCP/IP 的实现依赖于面向块的 ISO 传输服务。S7 协议被包装在 TPKT 和


ISO-COTP 协议中,这使得 PDU(协议数据单元)可以通过 TCP 进行传输。ISO over
TCP 通信在 RFC 1006 中定义,ISO-COTP 在基于 ISO 8073 协议(RFC 905)的 RFC
2126 中定义。

结构图如下。

S7 协议是面向功能/命令的,这意味着传输包括 S7 请求和适当的应答(很少有
例外)。在连接建立期间协商并行传输的数量和 PDU 的最大长度。
S7 PDU 由三个主要部分组成:
标题:包含长度信息、PDU 引用和消息类型常量
参数:基于 PDU 的消息和函数类型,内容和结构有很大的变化。
数据:如果有数据,例如内存值、块代码、固件数据等,它是一个可选的
字段。

2.1 标头

该报头为 10-12 字节长,确认消息包含两个额外的错误代码字节。除此之外,


报头格式在所有 PDU 上都是一致的。
领域:

协议 ID:[1b]协议常数总是设置为 0x32

消息类型:[1b]消息的一般类型(有时称为 ROSCTR 类型)

0x01 作业请求:主机发送的请求(例如读/写存储器、读/写块、启动/停止
设备、设置通信)

0x02 ACK:由没有数据字段的从属设备发送的简单确认(我从未见过它由
S300 /S400 设备发送)

0x03 ACK 数据:带有可选数据字段的确认,包含对作业请求的答复

0x07-Userdata:原始协议的扩展,参数字段包含请求/响应 id(用于编程/
调试、SZL 读取、安全函数、时间设置、循环读取)。

保留:[2b]总是设置为 0x000(但可能忽略)

PDU 引用:[2b]由主机生成,随着每次新的传输递增,用于链接响应他们的请
求,.-Endian(注意:这是 WinCC、Step7 和其他西门子程序的行为,它可能是
随机生成的,PLC 只是将它复制到应答)

参数长度:[2b]参数字段的长度,Big Endian

数据长度:[2b]数据字段的长度,Big Endian

(错误类):[1b]只有在 Ack-数据消息中,Constants.txt 中列出了可能的错误常


量。
(错误代码):[1b]只有在 Ack-数据消息中,Constants.txt 中列出了可能的错误
常量。
西门子 S7 通信第 2 部分:作业请求和 ACK 数据

第一部分详细介绍了一般通信场景和分组结构。本部分将进一步研究“职务请
求”和“Ack 数据消息”的用途和内部结构。这些消息类型是一起讨论的,因
为它们非常相似,而且通常每个 Job Request(作业请求)都会产生一个 Ack 数据
应答

S7 PDU 的结构和通用协议报头在前一部分进行了说明。但是,参数头是特定于
消息类型的,对于 Job 和 Ack Data 消息,它以一个函数代码开始。其余字段的
结构取决于此值。该功能代码决定了消息的目的,并作为进一步讨论的基础。

1.设置通信[0xF0]

Pcap:S300 安装通信

此消息对(Job 和 Ack Data 响应)在每个会话开始时发送,然后才能交换任何


其他消息。它用于协商 ACK 队列的大小和 MAX PDU 长度,双方声明其支持的值。
ACK 队列的长度决定了并行作业的数量,这些并行作业可以在没有确认的情况
下同时启动。PDU 和队列长度字段都是大的 Endiad。

参数标题显示在以下图表中:

1.1 S7 认证与保护

Pcap:S300 认证
这可能是讨论 S7 身份验证和保护机制的好地方(即使它们与实际通信设置无
关)。在 CPU 的配置过程中,可以设置三种保护模式。

没有保护:正如预期的那样,不需要身份验证。

写入保护:对于某些数据写入和配置更改操作,需要进行身份验证。

读/写保护:就像前一个一样,但是某些读操作也需要认证。

必须注意,即使启用了读/写保护,仍然允许某些操作,例如读取 SZL 列表或读


取和写入标记区域。其他操作,如读取或写入对象/函数/数据块应返回权限错
误。

有两个与 CPU 相关联的保护级别集、指定的保护级别和真正的保护级别。分配


的保护级别是在配置期间设置的保护级别,而真正的保护级别是适用于通信会
话的当前保护级别。

在正常操作期间,需要读/写权限的客户端在通信设置之后,通过 SZL 读取
(SZL ID:0x0132SZL 索引:0x0004)查询实际和指定的保护级别。如果需要
身份验证,则在 userdata 消息中将密码发送到设备,这将降低有效保护级别。

在任何人都认为这至少提供了一点点安全性之前,让我澄清它不是。密码为六
字节,几乎在清除中发送(使用常数和移位的 XORD)。它是可重放的,可以被
强迫。该协议还不提供完整性或机密性保护,消息注入和修改是可能的。当涉
及到 S7 安全性时,一般的经验法则是,如果你能 ping 这个设备,你就可以拥
有它。

这里必须注意,S7-1200/1500 系列设备使用稍微不同的方法,保护级别的处理
稍有不同,并且发送的密码明显更长(实际上是密码的哈希),但是它仍然是
恒定的和可重放的。

2.读/写变量[0x04/0x05]

Pcaps:

S300 读数变量单

S300 读写变量(多变量读写简单寻址)

S400 读写变量 DB(多变量读写与数据库寻址)

这里,当事情开始变得有点复杂时,我强烈建议在阅读本节时查看所提供的
pcap(wireshark2 带有默认启用的 S7disctor)。数据读写操作是通过指定变
量的存储器区域、它的地址(偏移)及其大小或类型来执行的。在进入协议细
节之前,我想简单介绍 S7 寻址模型。

与前面提到的变量一样,通过指定它们的地址来访问这些变量,这个地址由三
个主要属性组成。内存区域:
Merker:[M]任意标记变量或标志寄存器驻留在这里。
数据块:[DB]DB 区域是存储设备不同功能所需的数据的最常见位置,这些数
据块编号,这是地址的一部分。
输入:[i]数字和模拟输入模块的值,映射到内存中。
输出:[Q]同样的内存映射输出。
计数器:[C]由 PLC 程序使用的不同计数器的值。
定时器:[T]由 PLC 程序使用的不同定时器的值。

还有其他不太常见的内存区域(如本地数据[L]和外围访问[P]等)。
变量的类型决定了它的长度以及它应该如何解释。举几个例子:
位:[X]一位。
字:两个字节宽无符号整数。
32 位整数(DINT,Double Integer)四字节宽符号整数。
实数:四字节宽 IEEE 浮点数。
计数器:计数器类型由 PLC 程序计数器使用。

变量的示例地址是 DB123X2.1,它访问数据块#123 的第三个字节的第二个位。

在这个简短的绕行之后,让我们回到协议的变量读/写的实现。S7 协议支持用
不同的寻址模式查询单个消息中的多变量读/写。主要有三种模式:

任何类型:这是默认的寻址模式,它用于查询任意变量。所有三个参数
(区域、地址、类型)都是针对每个被寻址的变量指定的。
DB 类型:这是专门设计用来解决 DB 区域变量的特殊模式,它比任何类型的
寻址都更紧凑。
符号寻址:S7-1200/1500 系列设备使用此模式,并且允许使用预先定义的
符号名称来寻址某些变量。此模式将不在这里详细介绍。

对于每个寻址模式,参数报头以相同的方式构造:
函数代码:[1b]常数 0x04,用于读取或 0x05 的写作业和回复。
项目计数:[1B]以下请求项结构的数目。
请求项:此结构用于处理实际变量,其长度和字段取决于所使用的寻址类
型。这些项仅存在于 Job 请求中,并且从相应的 Ack Data 发出,而不管寻
址模式是什么,也不管它是读还是写请求。

S7 PDU 的数据部分根据消息的类型(读/写)和方向(作业/ ACK 数据)而变化:


读取请求:数据部分是空的。
读取响应:Ack Data 消息的 Data 部分由 Data Item 结构组成,每个结构用
于原始请求中出现的请求项。这些项包含读取变量的实际值,并且格式取
决于寻址模式。

写入请求:包含与读取响应相似的数据项,参数头中的每个请求项都包含
一个数据项。类似地,这些包含要在从属设备上写入的变量值。

写响应:Ack Data 消息的 Data 部分只包含原始写请求中的每个请求项的一


个字节错误代码。请参阅 CONTANTS.TXT 的错误代码值。

总之,请求项总是包含变量的描述,其中多个变量可以在作业请求中发送,而
数据项包含所描述变量的实际值。数据项结构必须以偶数字节开始,因此,如
果它们的长度是奇数,并且有下面的数据项,则它们被填充为零字节。

剩下要讨论的是请求/数据项结构的格式。如前所述,它们依赖于正在使用的寻
址模式,因此将基于此来引入寻址模式。

2.1 具有任意类型寻址的项结构

下图显示了请求和数据项结构:

请求项的字段:

规范类型:[1b]此字段确定项结构的主要类型,对于读/写消息,它总是具
有代表可变规范的值 0x12。
长度:[1B]这个项目其余部分的长度。

语法 ID:[1B]此字段决定了寻址模式和其余项结构的格式。对于任何类型的
寻址,它具有恒定值 0x10。

变量 Type:[1b]用于确定变量的类型和长度(通常使用 S7 类型,如
REAL、BIT、BYTE、WORD、DWORD、COUNTER、…)。

计数:[2B]可以选择一个具有单个项结构的类似变量的整数组。这些变量
必须具有相同的类型,并且必须在内存中是连续的,并且 count 字段决定
这个数组的大小。它被设置为一个用于单变量读或写。

DB 编号:[2B]数据库的地址,如果该区域未设置为 DB(见下一字段),则
忽略该地址。

区域:[1B]选择寻址变量的内存区域。请参阅 CONTANTS.TXT 内存区域常数。

地址:[3b]包含所选存储器区域中的寻址变量的偏移量。基本上,地址被
转换为位偏移,并在网络(大端字节)字节顺序上编码 3 字节。在实践中,
最重要的 5 位永远不会被使用,因为地址空间比那个小。作为例子,
dxx40.3 将是 0x000 0143,它是 40×8+3。

类似地,关联数据项的字段:

错误代码:[1b]操作的返回值,0xFF 信号成功。在写请求消息中,这个字
段总是设置为零。

变量类型和计数:[1B2B]与请求项相同。

数据:该字段包含已寻址变量的实际值,其大小为 LeN(变量)*计数。
2.2 具有 DB 类型寻址的项结构

S400 系列设备中使用这种类型的寻址,不过它也可能由一些 S300 系列 PLC 支持。


它仅用于访问 DB 变量,并提供了以更紧凑的格式处理单个项中的多个不同变量
的备选方案。下图显示了请求和数据项结构:

请求项的字段:
规格类型:[1B]与任何类型的寻址相同。
长度:[1B]这个项目其余部分的长度。
语法 ID:[1B]确定寻址模式,对于 DB 类型具有恒定值 0xB0。
子项的数目:[1b]以下子项的数目。
Subitem:
大小:[1B]指定从所选地址读取或写入的字节数。
DB 编号:[2B]地址变量所在的 DB。
地址:[2b]将变量的字节偏移量转换为给定的 dB。
数据项的字段:
错误代码:[1b]操作的返回值,0xFF 信号成功。
变量类型:[1b]总是设置为 0x09(八位字节字符串)。
长度:[2b]剩余的子响应数据的长度。

Subresponse:
错误代码:[1b]与子项请求相关联的返回值。
数据:要读取或写入的实际数据,解释这需要相应的子项。

3.块下载/下载[0x1a1f]

Pcaps:
S300 下载 OB1
S300—SNAP7 上传

首先,在西门子术语中,下载是指主机向从机发送块数据,而上传是另一个方
向。在西门子设备上,程序代码和(大部分)程序数据存储在块中,这些块具
有它们自己的头部和编码格式,这里不再详细讨论。从协议的角度来看,它们
是需要传输的二进制块(对于感兴趣的读者,snap7 源提供有关块标头及其编
码的信息)。

有七种不同类型的块由西门子设备识别:

OB:组织块,存储主要程序。

DB:(系统)数据块,存储 PLC 程序所需的数据。

(S) fc:(System)函数,无状态的函数(没有自己的内存),可以从其他程
序调用他们

(S)FB:(System)函数块,即有状态函数,它们通常具有关联的(S)DB。

这些块的目的在西门子文档中得到了很好的描述。
这些块是用一个特殊的 ASCII 文件名在 Up/Dead 请求中处理的。这个文件名
的结构如下:

文件标识符:(1 字符)据我所知,这总是有“值”的值。
块类型:(2 字符)确定块类型,参见具体值的康斯坦茨.txt。
块编号:[ 5 字符]以十进制格式给定的块的数目。
目标文件系统:(1 字符)这个字段既可以有被动文件系统的“a”值,
也可以有“p”值。复制到活动文件系统的块立即被链接起来,这意味着一旦 PLC
执行恢复,它们就生效。另一方面,需要首先激活复制到被动文件系统的块。

一个示例文件名是_0800001P,它用于将 OB 1 复制到或从被动文件系统复制。

关于块编码和内容保护的快速说明。有两项措施来保护节目的内容和数据,并
允许节目库的分布。第一个被称为诀窍保护,如果设置防止 STEP7 或 TIA 显示块
的实际内容。不幸的是,绕过这一点是微不足道的,因为它只是在块的头中设
置的两个位,并且可以很容易地被清除。另一个保护措施是块“加密”,实际
上它只是用线性变换(字节的 xoring 和常数的旋转)进行混淆,再绕过它应该
是很简单的。所以不要依赖这些“安全”机制来保护你的技术诀窍。否则,数
据块包含原始的、初始化的内存映像。程序块包含 MC7(机器代码 7)二进制
指令。

上传和下载块涉及 3-3 种不同类型的消息对。下面列出了相关的函数代码:

请求下载-0x1a
下载块-0x1b
下载结束-0x1c
开始上传-0x1D
上传块-0x1e
结束上传-0x1f

这些消息的结构非常简单,但是消息序列(特别是用于下载)需要稍加解释。
3.1 上传块

上传块顺序相当直观,如下所示:
在 Ack 数据启动上传消息中,从节点告诉块的长度,然后主程序继续发送 Job-
Upload 块消息,直到接收到所有字节为止。最后,它用作业结束上传消息关闭
上传序列。块的实际数据由 Ack 数据上传块消息中的从服务器发送。

Job-开始上传参数头:

函数代码:[1B]0x1d 用于开始上载。

功能状态:[1B]仅用于上传消息,如果要发送更多数据,则设置为
0x01。未知:[2B]总是 0x0000。

会话 ID:[4B]是与每个上传序列相关联的唯一 id,它在 Ack Data-Start


Upload 中设置-开始上传消息。

文件名长度:[1B]以下文件名的长度。文件名:标识上述块的文件名

Ack Data -开始上载参数标题:


函数代码:[1B]0x1d 用于开始上载。
功能状态:[1B]与上面相同。
未知:[2B]总是 0x0100。
会话 ID:[4B]会话 ID:[4B]会话 ID 在这里设置为,连续消息使用相同
的值。
长度字符串长度:[1B]以下块长度字符串的长度。
长度字符串:以 ASCIC 字符串编码的块的十进制长度。

Job-上载参数标题:

包含函数代码(0x1e)、函数状态、未知(0x0000)和会话 ID 字段。

Ack Data-上载参数和数据部分:

函数代码:[1B]0x1e 用于上载。

功能状态:[1B]如果要发送更多数据,设置为 0x01。

数据部分:

长度:[2B]块数据的长度。

未知:[2B]总是 0x00fb。

块数据:上传数据块的一部分。

Job 端上传参数头:
包含函数代码(0x1f)、函数状态、未知(0x0000)和会话 ID 字段。

Ack Data-末端上传参数头:

只包含函数代码(0x1f)

3.2 下载块

上传和下载之间的关键区别在于,在下载过程中,通信的方向发生了变化,从
服务器变成了主(不错)。在初始请求下载交换之后,从服务器发送作业消息,主
应答以 Ack 数据答复,这是“仅从答复”规则的唯一例外。在发送所有字节后,
主(原始)发送下载结束作业以关闭下载会话。请参阅下面的顺序图。
实际消息的结构与上传消息的结构非常相似,所以我只想介绍它们的不同之处。
要获得准确的语法描述,请在 Wireshark 中打开示例 pcapk。作业请求下载消息
包含两个额外的字段,下载块的块长度和块的有效载荷长度(没有块头的长度)。
这两个字段都是以 ASCII 字符串编码的十进制数字。响应 Ack 数据请求下载只包
含函数代码,另一个重要的区别是,虽然存在会话 ID 字段,但它没有被使用(仍
然是 0x00000000),而是在每个作业下载块中传输文件名。其余消息的结构与前
面讨论的相同。

4. PLC Control [0x28]

PCAPS:
S 300-控制命令(将 RAM 复制到 ROM,压缩内存,启动 PLC)

S 300-复制-ram 到-rom

S 300-激活-

s 300-删除块(激活/删除块,启动 PLC)

(尝试使用 s7comm.par.func=0x28Wireshark 筛选器查找 PLC 控制消息)

PLC 控制消息用于在从设备上执行修改其执行/内存状态的不同例程。这些命令
用于启动或停止 PLC 控制程序的执行,激活或删除设备上的程序块,或将其配
置保存到持久内存中。这些消息的结构相当简单,不需要讨论确切的细节(请参
阅附件中的捕获)。作业-PLC 控制消息由两个主要部分组成:被调用方法的 ASCII
名称及其参数(也被编码为 ASCII 字符串)。方法名称的结构与块传输部分中引入
的文件名类似。参数取决于方法类型,它们可以被看作是它的一个参数。Ack
数据消息只包含 PLC 控制功能代码。

一些示例函数名及其相关参数:

_INSE:激活设备上下载的块,参数是块的名称(例如 OB1)。_DELE:从
设备的文件系统中移除一个块,该参数又是块的名称。

P_PROGRAM:设置设备的运行状态(启动,停止,MEM 重置)。它没有参
数发送来启动设备,但是停止 PLC 程序使用不同的函数代码(见下一节)。

_GARB 压缩内存。

_MODU:复制 RAM 到 ROM,参数包含文件系统标识符(A/E/P)

5.PLC 停止[0x29]

PCAP

S300-停止程序

PLC 停止消息本质上与 PLC 控制报文相同。唯一的区别是消息中没有参数,例程


部分总是被设置为 P_Program。我不知道为什么它有它的独立类型,而不是使
用一个参数来确定它是开始消息还是停止消息。

You might also like