工控安全 | 西门子通信协议S7COMM(Part 2) – 作者:LiukerTeam

*本文原创作者:LiukerTeam,本文属FreeBuf原创奖励计划文章,未经许可禁止转载

前言

随着网络强国、工业4.0,工控安全市场今年明显有相当大的改善,无论从政策还是客户需求,都在逐步扩大中。但是,搞工控安全研究的人员却寥寥无几,一方面,没有可以研究和学习的便利的环境;另一方面工控安全是个跨学课的技术,需要了解多方面的知识,有比较高的技术上的门槛。特别是工控系统中通信协议,在工控系统中通信协议存在众多标准,也存在众多私有协议,如果你有过使用组态软件的经历,你便会发现,在第一步连接设备时除连接设备的方式有以太网/串行等方式外,各家基本上都存在自己的私有通信协议。比如:西门子的是S7Comm协议。

上一篇文章《工控安全 | 西门子通信协议S7COMM(Part 1)》带来了西门子PLC系统构成S7协议结构TPKT协议COTP协议S7Comm协议五大块内容,本文紧接着上文中的S7Comm协议章节继续开展,没看过上一篇的小伙伴需要补补课哦,不然会不知所云~

5.2.4下载

下载是Step7发送块数据给PLC(图25)。在西门子设备上,程序代码和(大部分)程序数据存储在块中,这些块有自己的头和编码格式。

在西门子设备中有8种不同类型的功能块,具体的请参考6.7

这些块在上/下载请求中用特殊的ASCII文件名寻址。这个文件名的结构如下:

1 (1 byte): File identifier(ASCII),文件标识符。其有_ (Complete Module)、$ (Module header for up-loading)两种文件标识符;

2 (2 bytes): Block type,块类型。具体类型,请参考6.7

3 (5 bytes): Block number,块编号;

4 (1 byte): Destination filesystem(ASCII),目标的文件系统。其有三种文件系统:

P(Passive (copied, but not chained) module):被动文件系统

A (Active embedded module):主动文件系统

B (Active as well as passive module):既主既被文件系统

例如:文件名为_0A00001P(文件标识是_,块类型为DB,块的编号为00001,目标块的文件系统是P。),用于将DB 1复制到被动文件系统或从被动文件系统复制。

下载有3中不同的功能类型:

请求下载(Request download [0x1A])

下载块(Download block [0x1B])

下载结束(Download ended [0x1C])

在下载过程中,先是Step7向PLC发送一个请求下载的Job,PLC收到后则回复一个Ack_Data。在发送完所有字节后,Step7向PLC发送一个下载结束的Job来关闭下载会话。 时序图如下:

图25 下载时序图(图片来源:互联网)

图25 下载时序图(图片来源:互联网)

好了,开始介绍下载的结构啦!

如图26所示,即为一个完整的下载过程:

完整的下载过程

图26 一个完整的下载过程例子

5.2.4.1请求下载(Request download [0x1A])

先来介绍,当PDU类型为Job时,Request download [0x1A]没有Data,其Parameter的结构,如下:

1 (1 byte): Function Status,功能码状态;

2 (2 bytes): for all unknown bytes in blockcontrol;

3 (4 bytes): 无意义,一般为0x00000000;

4 (1 byte): filename length,文件名长度;

5 (? bytes): filename, default is 9 byte,文件名,长度一般为9个字节;

1 (1 byte): File identifier(ASCII),文件标识符。其有_ (Complete Module)、$ (Module header for up-loading)两种文件标识符;

2 (2 bytes): Block type,块类型。具体类型,请参考6.7

3 (5 bytes): Block number,块编号;

4 (1 byte): Destination filesystem(ASCII),目标的文件系统。其有P(Passive (copied, but not chained) module)、A (Active embedded module)、B (Active as well as passive module)三种文件系统;

6 (1 byte): Length part 2 in bytes,参数的第二部分长度,也就是接下来的字段长度;

7 (1 byte): Unknown char(ASCII);

8 (6 bytes): Length load memory in bytes(ASCII);

9 (6 bytes): Length of MC7 code in bytes(ASCII)。

其实就是告诉PLC要下载块。举个例子:

图27 请求下载_0800001P的作业请求

图27 请求下载_0800001P的作业请求

如图27所示,文件标识是_ (Complete Module),块类型为OB,块的编号为00001,目标块的文件系统是P (Passive (copied, but not chained) module),所以文件名为_0800001P。

那PDU类型为Ack_Data时,Request download [0x1A]的Parameter中只有一个function。下图即为图27的响应:

图28 请求下载_0800001P的确认数据响应

图28 请求下载_0800001P的确认数据响应

OK,请求下载完成后,接下来就可以Download block了!

5.2.4.2下载块(Download block [0x1B])

上面说了,下载是Step7发送块数据给PLC。

当PDU类型为Job时,Download block [0x1B]也没有Data,其Parameter的结构,如下:

1 (1 byte): Function Status,功能码状态;

2 (2 bytes): for all unknown bytes in blockcontrol;

3 (4 bytes): 无意义,一般为0x00000000;

4 (1 byte): filename length,文件名长度;

5 (? bytes): filename, default is 9 byte,文件名,长度一般为9个字节;

1 (1 byte): File identifier(ASCII),文件标识符。其有_ (Complete Module)、$ (Module header for up-loading)两种文件标识符;

2 (2 bytes): Block type,块类型。具体类型,请参考6.7

3 (5 bytes): Block number,块编号;

4 (1 byte): Destination filesystem(ASCII),目标的文件系统。其有P(Passive (copied, but not chained) module)、A (Active embedded module)、B (Active as well as passive module)三种文件系统;

是的,Download block [0x1B]的Parameter比Request download [0x1A]的Parameter的第一部分相同!

为了更好比较,举个例子:

图29 下载块_0800001P的作业请求

图29 下载块_0800001P的作业请求

上图是下载_0800001P的作业请求。

那PDU类型为Ack_Data时,Download block [0x1B]有Parameter和Data,其Parameter的结构,如下:

1 (1 byte): Function Status,功能码状态;

而其Data的结构,如下:

1 (Unsigned integer, 2 bytes): Length,数据长度;

2 (Unsigned integer, 2 bytes): Unknown byte(s) in blockcontrol,未知字节;

3 (Label,data_length-4 bytes): Data,数据;

下图即为图29的响应:

图30 下载块_0800001P的响应

图30 下载块_0800001P的响应

5.2.4.3下载结束(Download ended [0x1C])

当PDU类型为Job时,Download ended [0x1C]也没有Data,其Parameter的结构,如下:

1 (1 byte): Function Status,功能码状态;

2 (2 bytes): for all unknown bytes in blockcontrol;

3 (4 bytes): 无意义,一般为0x00000000;

4 (1 byte): filename length,文件名长度;

5 (? bytes): filename, default is 9 byte,文件名,长度一般为9个字节;

1 (1 byte): File identifier(ASCII),文件标识符。其有_ (Complete Module)、$ (Module header for up-loading)两种文件标识符;

2 (2 bytes): Block type,块类型。具体类型,请参考6.7

3 (5 bytes): Block number,块编号;

4 (1 byte): Destination filesystem(ASCII),目标的文件系统。其有P(Passive (copied, but not chained) module)、A (Active embedded module)、B (Active as well as passive module)三种文件系统;

是的,Download ended [0x1C]跟Download block [0x1B]的Parameter和Request download [0x1A]的Parameter的第一部分相同!

举个例子:

图31 结束下载_0800001P的作业请求

图31 结束下载_0800001P的作业请求

那PDU类型为Ack_Data时,Download ended [0x1C]的Parameter中只有一个function。下图即为图31的响应:

图32 结束下载_0800001P的响应

图32 结束下载_0800001P的响应

这样,整个下载过程就完成了!

下载到这就介绍完了,接着就介绍上传啦!

5.2.5上传

上传是PLC发送块数据给Step7(如图33)。

上传有3中不同的功能类型:

开始上传(Start upload [0x1D])

上传(Upload [0x1E])

上传结束(End upload [0x1F])

在上传过程中,先是Step7向PLC发送一个开始上传的Job,PLC收到后则回复一个Ack_Data,并告诉Step7块的长度、上传会话ID。然后PLC继续上传块数据到Step7,直到Step7收到所有字节。最后,Step7发送结束上传的作业请求来关闭上传会话。时序图如下:

图33 上传的时序图(图片来源:互联网)

图33 上传的时序图(图片来源:互联网)

好了,开始介绍上传的结构啦!

如图34所示,即为一个完整的下载过程:

图34 一个完整的上传过程例子

图34 一个完整的上传过程例子

5.2.5.1 开始上传(Start upload [0x1D])

先来介绍,当PDU类型为Job时,Start upload [0x1D]没有Data,其Parameter的结构,如下:

1 (1 byte): Function Status,功能码状态;

2 (2 bytes): for all unknown bytes in blockcontrol;

3 (4 bytes): 上传的会话ID,此时为0x00000000;

4 (1 byte): filename length,文件名长度;

5 (? bytes): filename, default is 9 byte,文件名,长度一般为9个字节;

1 (1 byte): File identifier(ASCII),文件标识符。其有_ (Complete Module)、$ (Module header for up-loading)两种文件标识符;

2 (2 bytes): Block type,块类型。具体类型,请参考6.7 功能块

3 (5 bytes): Block number,块编号;

4 (1 byte): Destination filesystem(ASCII),目标的文件系统。其有P(Passive (copied, but not chained) module)、A (Active embedded module)、B (Active as well as passive module)三种文件系统;

其实就是告诉PLC你上传的位置。举个例子:

图35 开始上传的作业请求

图35 开始上传的作业请求

如图35所示,文件标识是_ (Complete Module),块类型为0B(SDB),块的编号为00000,目标块的文件系统是A (Active embedded module),所以文件名为_0B00000A。

那PDU类型为Ack_Data时,Start upload [0x1D]的Parameter的结构,如下:

1 (1 byte): Function Status,功能码状态;

2 (2 bytes): for all unknown bytes in blockcontrol;

3 (4 bytes): 上传的会话ID,告诉Step7上传会话ID;

4 (Unsigned integer, 1 byte): Blocklengthstring Length;

5 (Character string): Blocklength,块的长度;

下图即为图35的响应:

图36开始上传的响应

图36开始上传的响应

图36中,其上传会话ID为0x00000007。

5.2.5.2 上传(Upload [0x1E])

上面说了,上传是PLC发送块数据给Step7。

当PDU类型为Job时,Upload [0x1E]也没有Data,其Parameter的结构,如下:

1 (1 byte): Function Status,功能码状态;

2 (2 bytes): for all unknown bytes in blockcontrol;

3 (4 bytes): 上传的会话ID,告诉Step7上传会话ID;

为了更好比较,举个例子:

图37 上传的作业请求

图37 上传的作业请求

那PDU类型为Ack_Data时,Upload [0x1E]有Parameter和Data,其Parameter的结构,如下:

1 (1 byte): Function Status,功能码状态;

而其Data的结构,如下:

1 (Unsigned integer, 2 bytes): Length,数据长度;

2 (Unsigned integer, 2 bytes): Unknown byte(s) in blockcontrol,未知字节;

3 (Label,data_length-4 bytes): Data,数据;

下图即为图37的响应:

图38 上传的确认数据响应

图38 上传的确认数据响应

5.2.5.3 上传结束(End upload [0x1F])

上传结束的过程,即为所有数据上传完成后,Step7发送结束上传的作业请求,PLC收到后就关闭会话,然后返回一个响应。

当PDU类型为Job时,End upload [0x1F]也没有Data,其Parameter的结构,如下:

1 (1 byte): Function Status,功能码状态;

2 (2 bytes): Error code,错误代码:

详细的Error code,参考6.1.2 Error code in parameter part

3 (4 bytes): 上传的会话ID,告诉Step7上传会话ID;

举个例子:

图39 上传结束的作业请求

图39 上传结束的作业请求

那PDU类型为Ack_Data时,End upload [0x1F]的Parameter中只有一个function。

图40所示,即为图39的响应:

图40 上传结束的响应

图40 上传结束的响应

这样,整个上传过程就完成了!

5.2.6 程序调用服务(PI service [0x28])

程序调用是用于在PLC执行修改执行/内存状态的日常工作。这些命令可以用于启动或停止PLC控制程序、激活或删除程序块。

当PDU类型为Job时,PI service [0x28]没有Data,只有Parameter,那Parameter的结构,如下:

1 (7 bytes): Unknown;

2 (Unsigned integer, 2 bytes): Parameter block length;

3 (?bytes): Parameter block,参数;

4 (Unsigned integer, 1 byte):String length,PI service的字符串长度;

5 (Character string, ASCII):PI (program invocation) Service name,程序调用服务名,参考6.8 程序调用服务名(PI service names)

Parameter包含两个主要部分:

服务名称

参数:取决于方法类型,可以将它们看作是它的参数

服务名称及其相关参数的示例:

_INSE:激活设备上下载的块,参数是块的名称(比如:OB 1)。

_DELE:从设备的文件系统中删除一个块,该参数也是该块的名称。

P_PROGRAM:设置设备的运行状态(启动、停止、复位)。

_GARB:压缩PLC内存。

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

如果服务调用的参数是块的话,那么Parameter block的结构如下:

1 (1 byte): Number of block;

2 (1 byte): Unknown,默认为0x00;

3 (? bytes): filename,文件名:

1 (2 bytes, ASCII): Block type,块类型。具体类型,请参考6.7 功能块

2 (5 bytes, ASCII): Block number,块编号;

3 (1 byte, ASCII): Destination filesystem(ASCII),目标的文件系统。其有P(Passive (copied, but not chained) module)、A (Active embedded module)、B (Active as well as passive module)三种文件系统;

举个例子,如图41所示:

图41 _INSE(激活PLC模块)的作业请求

图41 _INSE(激活PLC模块)的作业请求

上图可知服务名称是_INSE,参数是0B0004P(SDB4),那么它的作业请求是激活PLC中SDB 4,那么它的请求响应又是如何呢?如图42所示:

图42 _INSE(激活PLC模块)的响应

图42 _INSE(激活PLC模块)的响应

而另一种情况,如图43所示:

图43_MODU(复制RAM到ROM)的作业请求

图43_MODU(复制RAM到ROM)的作业请求

上图中,其Parameter block中只有Argument。

5.2.7 PLC STOP [0x29]

PLC STOP 基本上跟5.2.6 程序调用服务(PI service [0x28])一致,唯一的区别就是它没有Parameter block,而它的PI service为P_PROGRAM。搞不明白为啥单独占用一个功能码~~~

看个例子吧,如图44所示:

图44 PLC STOP的作业请求

图44 PLC STOP的作业请求

到此为此JOB和ACK_DATA类型下的功能码都介绍完了,接下来介绍S7commm协议的扩展。

5.3 协议拓展(Userdata)

上面介绍了S7Comm的JOB和ACK_DATA两个PDU类型,那接着将介绍PDU类型是UserData的内容,它用于编程/调试、读取SZL、安全功能、时间设置,循环读取等,可以说是S7Comm中最复杂的一部分。

大家不要慌哈,Are u ready?

Okay,当PDU类型为UserData时,其S7Comm结构,如图45所示:

图45 S7Comm的结构(UserData)

图45 S7Comm的结构(UserData)

图45中蓝色部分为S7Comm头部,橘色为Parameter部分,具体的Parameter结构如下:

1 (3 bytes):参数头(Parameter head);

2 (1 byte):参数长度(Parameter length),它的可能是8字节或12字节;

3 (1 byte):未知定义;

4 (1/2 byte,高位):参数类型(Type),常见的类型可参考《6.9 拓展协议的参数类型》

5 (1/2 byte,Low nibble):功能组(Function group),常见的功能组可参考《6.10 拓展协议的功能组》

6 (1 byte):子功能码(SubFunction);

7 (1 byte):序号。

接着就是一一介绍各个功能组。

5.3.1 转换工作模式(Mode-transition [0x0])

当功能组为转换工作模式(Mode-transition)时,请求报文中是没有Data部分的,而主要起作用的是子功能码(Subfunction),常见的子功能码有:

STOP(0x00):STOP模式;

Warm Restart(0x01):暖启动;

RUN(0x02):RUN模式;

Hot Restart(0x03):热启动;

HOLD(0x04):HOLD模式;

Cold Restart(0x06):冷启动;

RUN_R (H-System redundant)(0x09):H-System冗余运行;

LINK-UP(0x0B):LINK-UP模式;

UPDATE(0x0C):UPDATE模式。

关于暖启动、冷启动、热启动的区别可参考:S7-400 CPU 启动(暖启动),冷启动和热启动的区别是什么? – ID: 34053758 – Industry Support Siemens,至于冗余可参考: 何为冗余-找答案-工业支持中心-西门子(中国)有限公司(SLC)

来看个栗子消化一下吧,如图46所示:

图46 工作模式转换为暖启动

图46 工作模式转换为暖启动

如图46中绿色部分为参数类型(Type)和功能组(Function group),蓝色框内容是子功能码(SubFunction),值是0x01,即为暖启动。

5.3.2 程序员命令(Programmer commands [0x1])

程序员命令(Programmer commands)主要是工程师用于编程或调试,比如:监视/修改变量、读取修改诊断数据。所有的子功能码有:

请求诊断数据(Request diag data (Type 1)):0x01;

变量表(VarTab):0x02;

读取诊断数据(Read diag data):0x0c;

移除诊断数据(Remove diag data):0x0e;

清除(Erase):0x0f;

强制(Forces):0x10;

请求诊断数据(Request diag data (Type 2)):0x13;

这里的请求报文和响应报文都和图45有点不一样,具体如图47所示:

图47 功能码组为Programmer commands的报文结构

图47 功能码组为Programmer commands的报文结构

下面以变量表为例,变量表如图48所示:

图48 变量表

图48 变量表

如果对 DB100.DBW 2进行监视,那么他的请求报文如图49所示:

图49 监视变量表的请求报文

图49 监视变量表的请求报文

图49中的Header、Parameter在前面已经介绍了,重点介绍Data部分的结构,请求报文的结构如下:

1 (1 byte) : 返回码,具体的可参考6.6.1

2 (1 byte) :Transport sizes,指的数据类型,通常有bit、byte等,具体可参考6.4.2

3 (2 bytes) : 往后的数据长度,如图49为32个字节;

4 (1 byte) : Unknown;

5 (1 byte) : 报文类型(type of data),分为请求(0x14)、响应(0x04);

6 (2 bytes) : Item count和Item data的长度(Byte count);

7 (20bytes) : Unknown;

8 (2bytes) : Item个数;

9 (varibalebytes) : Item 1;

1 (1 byte) : 区域(Area);

2 (1 byte) : 长度(Length (repetition factor));

3 (2 bytes) : 模块号(DB number);

4 (2 bytes) : 偏移地址(Startaddress)。

...

n (varibalebytes) : Item n;

响应报文跟请求非常的像,但是还是有所不一样,响应报文结构如下:

1 (1 byte) : 返回码,具体的可参考6.6.1

2 (1 byte) :数据类型(Transport sizes),通常有bit、byte等,具体可参考6.4.2

3 (2 bytes) : 往后的数据长度,如图49为32个字节;

4 (1 byte) : Unknown;

5 (1 byte) : 报文类型(type of data),分为请求(0x14)、响应(0x04);

6 (2 bytes) : Item count和Item data的长度(Byte count);

7 (4bytes) : Unknown;

8 (2bytes) : Item个数;

9 (varibalebytes) : Item 1;

1 (1 byte) : 返回码,具体的可参考6.6.1

2 (1 byte) :数据类型(Transport sizes),通常有bit、byte等,具体可参考6.4.2

3 (2 bytes) : 往后的数据长度;

4 (varibale bytes) : Data。

...

n (varibalebytes) : Item n;

图50 监视变量表的响应报文

从图50中,得知DB100.DBW 2的值是61a8。

其它的子功能都比监视/修改变量表(VarTab)简单,在这就不一一介绍了,感兴趣的可以去研究研究。

未完待续~

*本文原创作者:LiukerTeam,本文属FreeBuf原创奖励计划文章,未经许可禁止转载

来源:freebuf.com 2018-11-12 10:00:02 by: LiukerTeam

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论