[ffffffff0x] 工控协议:S7COMM协议分析(上)

2021-01-19 10:30:23 浏览数 (1)

前言

在上一篇文章中,我们通过模拟器环境实现了S7-300的启停实验。本次文章,我们将详细介绍S7comm协议的S7Comm Header和Job 和 Ack_Data机制并抓包分析其请求和相应报文。


S7Comm

S7Comm(S7 Communication)是西门子专有的协议,是西门子 S7 通讯协议簇里的一种。

S7 协议的 TCP/IP 实现依赖于面向块的 ISO 传输服务。S7 协议被封装在 TPKT 和 ISO-COTP 协议中,这使得 PDU(协议数据单元)能够通过 TCP 传送。

它用于 PLC 编程,在 PLC 之间交换数据,从 SCADA(监控和数据采集)系统访问 PLC 数据以及诊断目的。

S7Comm 以太网协议基于 OSI 模型,从 wireshark 协议分级可以看出排列。

S7Comm 数据作为 COTP 数据包的有效载荷,第一个字节总是 0×32 作为协议标识符。

S7Comm 协议包含三部分:

  • Header
  • Parameter
  • Data

根据实现的功能不同,S7 comm 协议的结构会有所不同。


S7Comm Header

S7Comm 的头,定义了该包的类型、参数长度、数据长度等。

S7Comm Header的格式为:

  • 0 (unsigned integer, 1 byte): Protocol Id,协议 ID,通常为 0×32;
  • 1 (unsigned integer, 1 byte): ROSCTR,PDU type,PDU 的类型,一般有以下值:
    • 0×01 - JOB(Request: job with acknowledgement):作业请求。由主设备发送的请求(例如,读/写存储器,读/写块,启动/停止设备,设置通信);
    • 0×02 - ACK(acknowledgement without additional field):确认响应,没有数据的简单确认(未遇到过由 S7 300/400 设备发送得);
    • 0×03 - ACK_DATA(Response: acknowledgement with additional field):确认数据响应,这个一般都是响应JOB的请求;
    • 0×07 - USERDATA:原始协议的扩展,参数字段包含请求/响应 ID(用于编程/调试,读取 SZL,安全功能,时间设置,循环读取…)。
  • 2~3 (unsigned integer, 2 bytes): Redundancy Identification (Reserved),冗余数据,通常为 0×0000;
  • 4~5 (unsigned integer, 2 bytes): Protocol Data Unit Reference,it’s increased by request event。协议数据单元参考,通过请求事件增加;
  • 6~7 (unsigned integer, 2 bytes): Parameter length,the total length (bytes) of parameter part。参数的总长度;
  • 8~9 (unsigned integer, 2 bytes): Data length,数据长度。如果读取 PLC 内部数据,此处为 0×0000;对于其他功能,则为 Data 部分的数据长度;

其中最重要的字段就是 ROSCTR,它决定了后续参数的结构

在响应数据包中,还有可能存在错误信息,其错误信息结构为:

  • 10 (unsigned integer, 1 bytes): Error class,错误类型:
  • 11 (unsigned integer, 1 bytes): Error code,错误代码;流量包分析

下载 wireshark 官网公开的pcap包 https://wiki.wireshark.org/SampleCaptures?action=AttachFile&do=get&target=s7comm_downloading_block_db1.pcap

COTP Connection Packet

  • COTP 连接请求包
  • COTP 请求确认包

COTP Fuction Packet

  • 数据传输包

S7Comm Header

其中最重要的字段就是 ROSCTR,它决定了后续参数的结构

在响应数据包中,还有可能存在错误信息

可见图中的错误类型就是 No error


Job 和 Ack_Data

S7Comm 中 Job(作业请求) 和 Ack_Data(确认数据响应) 中的 Parameter 项的第一个字段是 function(功能码),其类型为 Unsigned integer,大小为 1 byte,决定了其余字段的结构、消息的目的。

建立通信(Setup communication 0xF0)

建立通信在每个会话开始时被发送,然后可以交换任何其他消息。它用于协商 ACK 队列的大小和最大 PDU 长度,双方声明它们的支持值。ACK 队列的长度决定了可以同时启动而不需要确认的并行作业的数量。PDU 和队列长度字段都是大端。

当 PDU 类型为 Job 时,具体的 Parameter 结构,如下:

  • 1 (Unsigned integer, 1 byte): Parameter part: Reserved byte in communication setup pdu,保留字节;
  • 2 (Unsigned integer, 2 bytes): Max AmQ (parallel jobs with ack) calling;
  • 3 (Unsigned integer, 2 bytes): Max AmQ (parallel jobs with ack) called;
  • 4 (Unsigned integer, 2 bytes): Parameter part: Negotiate PDU length。协商 PDU 长度。

读取值(Read Var 0x04)

数据读写操作通过指定变量的存储区域,地址(偏移量)及其大小或类型来执行。

当 PDU 类型为 Job 时,item的结构如下:

  • 0 (Unsigned integer, 1 byte): Variable specification,确定项目结构的主要类型,通常为 0×12,代表变量规范;
  • 1 (Unsigned integer, 1 byte): Length of following address specification,本 Item 其余部分的长度;
  • 2 (Unsigned integer, 1 byte): Syntax Ids of variable specification,确定寻址模式和其余项目结构的格式;
  • 3(Unsigned integer, 1 byte): Transport sizes in item data,确定变量的类型和长度:
  • 4~5 (Unsigned integer ,2 byte): Request data length,请求的数据长度;
  • 6~7 (Unsigned integer, 2 byte): DB number,DB 模块的编号,如果访问的不是 DB 区域,此处为 0×0000;
  • 8 (Unsigned integer, 1 byte):: Area,区域类型:
  • 9~11(Unsigned integer, 3 byte): Address,地址。

PDU 类型为 Ack_Data 时,Data 结构如下:

  • 0 (Unsigned integer, 1 byte): Return code,返回代码:
  • 1 (Unsigned integer, 1 byte): Transport size,数据的传输尺寸:
  • 2~3 (Unsigned integer, 2 bytes): Length,数据的长度;
  • 4~4 length (?): Data,数据;
  • ? (Unsigned integer, 1 byte): Fill byte,填充字节。

写入值(Write Var 0x05)

Write Var 中 Parameter 的结构跟读取值(Read Var0x04)一样,但是 Write Var 还需写入值,所以 Write Var 比 Read Var 多了一个 Data 项。

Data 的结构为:

  • 0 (Unsigned integer, 1 byte): Return code,返回代码,这里是未定义,所以为 Reserved(0×00);
  • 1 (unsigned integer, 1 byte): Transport size,确定变量的类型和长度:
  • 2-3 (unsigned integer, 2 bytes): Length,写入值的数据长度;
  • 4 (1 byte): Data,写入的值;
  • 5 (unsigned integer, 1 byte): Fill byte,填充字节,如果数据的长度不足 Length 的话,则填充;

PDU 类型为 Ack_Data 时,Parameter 也只有 function、item count 两个字段。而 Data 中也只有一个 Return code 字段,其结构如下:

  • 0 (Unsigned integer, 1 byte): Return code,返回代码:

下载

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

在西门子设备中有8种不同类型的功能块,这些块在上/下载请求中用特殊的ASCII文件名寻址。这个文件名的结构如下:

  • 1 (1 byte): File identifier(ASCII),文件标识符。其有_ (Complete Module)、$ (Module header for up-loading)两种文件标识符;
  • 2 (2 bytes): Block type,块类型。
  • 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):既主既被文件系统

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

  • 请求下载(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,块类型。
    • 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)。 PDU 类型为 Ack_Data 时,Request download 0x1A 的 Parameter 中只有一个 function。

PDU 类型为 Ack_Data 时,Request download 0x1A 的 Parameter 中只有一个 function。

  • 下载块(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,块类型。
    • 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 的第一部分相同

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

  • 1 (1 byte): Function Status,功能码状态;
    • 1 (Unsigned integer, 2 bytes): Length,数据长度;
    • 2 (Unsigned integer, 2 bytes): Unknown byte(s) in blockcontrol,未知字节;
    • 3 (Label,data_length-4 bytes): Data,数据;
  • 下载结束(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,块类型。
    • 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)三种文件系统;

PDU 类型为 Ack_Data 时,Download ended 0x1C 的 Parameter 中只有一个 function。

上传

上传是 PLC 发送块数据给 Step7

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

上传有3种不同的功能类型

  • 开始上传(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,块类型。
    • 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)三种文件系统;

那 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,块的长度;
  • 上传(Upload 0x1E)

当 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;

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

  • 1 (1 byte): Function Status,功能码状态;
    • 1 (Unsigned integer, 2 bytes): Length,数据长度;
    • 2 (Unsigned integer, 2 bytes): Unknown byte(s) in blockcontrol,未知字节;
    • 3 (Label,data_length-4 bytes): Data,数据;
  • 上传结束(End upload 0x1F)

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

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

  • 1 (1 byte): Function Status,功能码状态;
  • 2 (2 bytes): Error code,错误代码:
  • 3 (4 bytes): 上传的会话ID,告诉 Step7 上传会话 ID; 那 PDU 类型为 Ack_Data 时,End upload 0x1F 的 Parameter 中只有一个 function。

程序调用服务(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,程序调用服务名

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,块类型。
  • 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)三种文件系统;

PLC STOP 0x29

PLC STOP 基本上跟程序调用服务(PI service 0x28)一致,唯一的区别就是它没有 Parameter block,而它的 PI service 为 P_PROGRAM。

流量包分析

下载 wireshark 官网公开的pcap包 https://wiki.wireshark.org/SampleCaptures?action=AttachFile&do=get&target=s7comm_downloading_block_db1.pcap

建立通信(Setup communication 0xF0)

  • 请求
  • 响应

其协商结果为:ACK 队列的大小为 1;最大 PDU 长度为 240。

请求下载(Request download 0x1A)

  • 请求
  • 响应

下载块(Download block 0x1B)

  • 请求
  • 响应

下载结束(Download ended 0x1C)

  • 请求
  • 响应

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

  • 请求
  • 响应

然后再下载这个流量包 https://github.com/ITI/ICS-Security-Tools/blob/master/pcaps/s7/snap7_s300_everything.pcapng

开始上传(Start upload 0x1D)

  • 请求
  • 响应

上传(Upload 0x1E)

  • 请求
  • 响应

上传结束(End upload 0x1F)

  • 请求
  • 响应

然后再下载这个流量包 https://wiki.wireshark.org/SampleCaptures?action=AttachFile&do=get&target=s7comm_varservice_libnodavedemo.pcap

读取值(Read Var 0x04)

  • 读值操作的作业请求
  • 响应

写入值(Write Var 0x05)

  • 向地址为 0×000020 的 Flags(M)写入 0×0103 的作业请求
  • 向地址为 0×000020 的 Flags(M)写入 0×0103 的确认响应

总结

本文,我们分析了S7comm协议的S7Comm Header、Job 和 Ack_Data机制,并通过wireshark抓包分析其请求和相应报文。下一篇文章,我们将继续学习S7comm协议Userdata 协议拓展部分。


本文作者 r0fus0d

0 人点赞