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

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

前言

在上一篇文章中,我们介绍了S7Comm协议的S7Comm Header和Job 和 Ack_Data机制。本篇文章,我们将继续介绍S7Comm协议的Userdata 协议拓展并结合pcap流量包实际分析。


Userdata 协议拓展

UserData 用于编程/调试、读取 SZL、安全功能、时间设置,循环读取等。

Parameter 结构如下:

  • 1 (3 bytes):参数头(Parameter head);
  • 2 (1 byte):参数长度(Parameter length),它的可能是8字节或12字节;
  • 3 (1 byte):未知定义;
  • 4 (1/2 byte,高位):参数类型(Type);
  • 5 (1/2 byte,Low nibble):功能组(Function group);
  • 6 (1 byte):子功能码(SubFunction);
  • 7 (1 byte):序号。

转换工作模式(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 模式。

程序员命令(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;

请求报文的结构如下:

  • 1 (1 byte) : 返回码;
  • 2 (1 byte) :Transport sizes,指的数据类型,通常有 bit、byte等;
  • 3 (2 bytes) : 往后的数据长度;
  • 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) : 返回码;
  • 2 (1 byte) :数据类型(Transport sizes),通常有 bit、byte 等;
  • 3 (2 bytes) : 往后的数据长度;
  • 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) : 返回码;
  • 2 (1 byte) :数据类型(Transport sizes),通常有 bit、byte 等;
  • 3 (2 bytes) : 往后的数据长度;
  • 4 (varibale bytes) : Data。
  • n (varibalebytes) : Item n;

块功能(Block functions 0x3)

块功能(Block functions)是用于操作块,所有的子功能码有:

  • 0x01:列举所有块(List blocks);
  • 0x02:列举块类型(List blocks of type);
  • 0x03:读取块的信息(Get block info)。

CPU功能(CPU functions 0x4)

CPU 功能(CPU functions)是用于操作块,所有的子功能码有:

  • 0x01:读系统状态列表(Read SZL);
  • 0x02:消息服务(Message service);
  • 0x03:诊断消息(Diagnostic message),PLC 的诊断消息;
  • 0x05:ALARM_8 显示(ALARM_8 indication), PLC 使用ALARM_8 SFBs 来显示报警消息;
  • 0x06:NOTIFY 显示(NOTIFY indication),PLC 使用 NOTIFY SFBs 来显示 NOTIFY 消息;
  • 0x07:ALARM_8 锁定(ALARM_8 lock), 需要通过 HMI/SCADA 锁定ALARM 消息;
  • 0x08:ALARM_8 取消锁定(ALARM_8 unlock), 需要通过 HMI/SCADA 取消锁定 ALARM 消息;
  • 0x09:SCAN 显示(SCAN indication),PLC 显示 SCAN 消息;
  • 0x0b:ALARM 确认(ALARM ack),报警信息已在 HMI/SCADA 中得到确认;
  • 0x0c:ALARM 确认显示(ALARM ack indication), 从 CPU 到 HMI 的确认报警显示;
  • 0x0d:ALARM 锁定显示(ALARM lock indication),从 CPU 到 HMI 的锁定报警显示;
  • 0x0e:ALARM 取消锁定显示(ALARM unlock indication),从 CPU 到 HMI 的取消锁定报警显示;
  • 0x11:ALARM_SQ 显示(ALARM_SQ indication),PLC 使用 ALARM_SQ/ALARM_DQ SFCs 来显示 ALARM 消息;
  • 0x12:ALARM_S 显示(ALARM_S indication),PLC 使用 ALARM_S/ALARM_D SFCs 来显示 ALARM 消息;
  • 0x13:ALARM 查询(ALARM query),HMI/SCADA 查询 ALARM;
  • 0x16:NOTIFY_8 显示(NOTIFY_8 indication)。

子功能码有4种不同的功能类型:

  • 系统状态列表(SZL)

系统状态列表(德语:System-ZustandsListen,英语:System Status Lists)用于描述可编程逻辑控制器的当前状态。SZL的内容只能通过信息功能进行读取,而不能修改。换言之,部分列表是虚拟列表,只在有特殊请求时由CPU的操作系统所创建。

系统状态列表包含下列内容的有关信息:

  • 系统数据
  • CPU 中的模块状态数据
  • 模块的诊断数据
  • 诊断缓冲区

如果要读取系统状态列表,则需要使用参数 SZL-ID 和 INDEX 指定要读取的内容。比如:读取 PLC 的名称,那 SZL-ID 是 W#16#011C,INDEX 是 W#16#0001

每个部分系统状态列表都有一个编号。可以根据编号输出完整的部分列表或摘录。预定义了可能的部分列表摘录,并由一个数字标识。SZL-ID 由部分列表的编号、部分列表摘录的编号和模块等级组成。

请求报文的 Data 结构如下:

  • 1 (1 byte) : 返回码(return code);
  • 2 (1 byte) : 数据传输大小(Transport sizes);
  • 3 (2 bytes) : 以此往后的数据长度;
  • 4 (2 bytes):SZL-ID;
  • 5 (2 bytes):SZL-Index;

响应报文 Data 结构如下:

  • 1 (1 byte) : 返回码(return code);
  • 2 (1 byte) : 数据传输大小(Transport sizes);
  • 3 (2 bytes) : 以此往后的数据长度;
  • 4 (2 bytes):SZL-ID,部分列表摘录的 SZL-ID,以 W#16#xy1C 为例,常见的 SZL-ID 如下:
    • W#16#001C:所有组件的标识;
    • W#16#011C:一个组件的标识;
    • W#16#021C:H系统中一个CPU的所有组件的标识;
    • W#16#031C:H系统中所有冗余CPU的一个组件的标识;
    • W#16#0F1C:仅限SSL部分列表报头信息;
  • 5 (2 bytes):SZL-Index,不同的 SZL-ID 那 SZL-Index 不一样。以 W#16#011C 和 W#16#031C 的部分列表摘录的组件标识:
    • W#16#0001:自动化系统的名称;
    • W#16#0002:模块名称;
    • W#16#0003:模块的设备标识;
    • W#16#0004:版权;
    • W#16#0005:模块的序列号;
    • W#16#0007:模块类型名称;
    • W#16#0008:存储卡的序列号在不能插入存储卡的模块中,不提供 数据记录;
    • W#16#0009:CPU 模块的制造商和配置文件;
    • W#16#000A:模块的 OEM ID(仅限 S7-300);
    • W#16#000B:模块的位置指定;
  • 6 (2 bytes):部分列表的长度(SZL partial list length in bytes),不同的 SZL-ID 那长度不一样;
  • 7 (2 bytes):部分列表的个数(SZL partial list count);
  • 8 (34 bytes):SZL 1;
  • n (34 bytes):SZL n;
  • 消息服务(Message service)

消息服务(Message service)主要用于订阅事件,比如:切换工作模式事件、系统诊断事件等。

请求报文的 Data 结构如下:

  • 1 (1 byte) : 返回码(return code);
  • 2 (1 byte) : 数据传输大小(Transport sizes);
  • 3 (2 bytes) : 以此往后的数据长度;
  • 4 (1 byte):订阅事件(Subscribed events),常见的事件有:
    • 0x01(MODE):切换工作模式;
    • 0x02(SYS):系统诊断;
    • 0x04(USR):用户定义的诊断消息;
    • 0x08:未知;
    • 0x10:未知;
    • 0x20:未知;
    • 0x40:未知;
    • 0x80(ALM):程序块消息,附加字段中的消息类型;
  • 5 (1 byte):未知(Unknown);
  • 6 (varibale bytes):用户名(Username);

响应报文 Data 结构如下:

  • 1 (1 byte) : 返回码(return code);
  • 2 (1 byte) : 数据传输大小(Transport sizes);
  • 3 (2 bytes) : 以此往后的数据长度;
  • 4 (1 byte):订阅的结果:
  • n (4 bytes):预留(Reserved 2)
  • 诊断消息(Diagnostic message)

诊断消息(Diagnostic message)通常是诊断缓冲区中的数据

请求报文的 Data 结构如下:

  • 1 (1 byte) : 返回码(return code);
  • 2 (1 byte) : 数据传输大小(Transport sizes);
  • 3 (2 bytes) : 以此往后的数据长度;
  • 4 (2 bytes):事件 ID(Event ID);
  • 5 (1 byte):优先级(Priority class);
  • 6 (1 byte):OB 编号(OB number);
  • 7 (2 bytes):DatID;
  • 8 (2 bytes):附加信息(Additional information 1);
  • 9 (4 bytes):附加信息(Additional information 2);
  • 10 (8 bytes):时间戳;

每个事件都分配有事件 ID,而事件 ID 的结构如图

那事件 ID 的内容,包括:

  • 0-7位:事件编号(Event Number);
  • 8-11位:识别(IDs),标识符用于区分事件的类型。
  • 12-15位:事件等级(Event Class)
  • 告警消息及告警查询

在这里,把 ALARM_8 显示(0x05)、NOTIFY 显示(0x06)、ALARM_8 锁定(0x07)、ALARM_8 取消锁定(0x08)、SCAN 显示(0x09)、ALARM 确认(0x0b)、ALARM 确认显示(0x0c)、ALARM 锁定显示(0x0d)、ALARM 取消锁定显示(0x0e)、ALARM_SQ 显示(0x11)、ALARM_S 显示(0x12)、ALARM 查询(0x13)、NOTIFY_8 显示(0x16)共13个子功能归纳为告警信息。

  • ALARM 查询(0x13)

请求报文的 Data 结构如下:

代码语言:txt复制
* 1 (1 byte) : 返回码(return code);
* 2 (1 byte) : 数据传输大小(Transport sizes);
* 3 (2 bytes) : 以此往后的数据长度;
* 4 (1 byte):功能标识(Function identifier);
* 5 (1 byte):消息对象个数(Number of message objects);
* 6 (varibale byte):Message Object 1;
  * 1 (1 byte):Variable specification;
  * 2 (1 byte):以下规范地址的长度(Length of following address specification);
  * 3 (1 byte):Syntax Id;
  * 4 (1 byte):Unknown;
  * 5 (1 byte):查询类型(Querytype),类型有:
    * 0x01:告警类型(ByAlarmtype);
    * 0x03:事件ID(ByEventID);
    * 0x08:Unknown;
    * 0x09:Unknown;
  * 6 (1 byte):Unknown;
  * …
* …

响应报文 Data 结构如下:

代码语言:txt复制
* 1 (1 byte) : 返回码(Return code);
* 2 (1 byte) : 数据传输大小(Transport sizes);
* 3 (2 bytes) : 以此往后的数据长度;
* 4 (1 byte):功能标识(Function identifier);
* 5 (1 byte):消息对象个数(Number of message objects);
* 6 (1 byte) : 数据传输大小(Transport sizes);
* 7 (2 bytes) : 完整数据长度,也就是以此往后的数据长度;
* 8 (varibale byte):Message Object 1;
  * 1 (1 byte) :长度(Length of dataset);
  * 2 (2 bytes):Unknown;
  * 3 (1 byte):告警类型(Alarmtype);
  * 4 (4 bytes):事件ID;
  * 5 (1 byte):Unknown;
  * 6 (1 byte):事件状态(EventState);
  * 7 (1 byte):AckState going;
  * 8 (1 byte):AckState coming;
* …
* n (varibale byte):Message Object n;ALARM 显示、ALARM 锁定/解锁、ALARM 确认、NOTIFY 显示

往往这类报文都是以PUSH的形式存在, Data 结构如下:

代码语言:txt复制
* 1 (1 byte) : 返回码(Return code),具体的可参考6.6;
* 2 (1 byte) : 数据传输大小(Transport sizes),具体可参考6.4.2;
* 3 (2 bytes) : 以此往后的数据长度;
* 4 (8 bytes):事件时间,如果subfunc是0x09,那长度为2 bytes;
* 5 (1 byte):功能标识(Function identifier);
* 6 (1 byte):消息对象个数(Number of message objects);
* 7 (varibale byte):Message Object 1;
  * 1 (1 byte):Variable specification;
  * 2 (1 byte):长度(Length of following address specification);
  * 3 (1 byte):Syntax Id,常见的结构标识可参考6.5;
  * 4 (1 byte):相关值数目(Number of associated values);
  * 5 (4 bytes):事件ID;
  * `6 (1 byte):事件状态(EventState);`
  * `7 (1 byte):状态(State);`
  * `8 (1 byte):AckState going;`
  * `9 (1 byte):AckState coming;`
  * `10 (varibale bytes):Associated value 1;`
    * `1 (1 byte) : 返回码(Return code);`
    * `2 (1 byte) : 数据传输大小(Transport sizes);`
    * `3 (2 bytes) : 长度;`
    * `4 (varibale bytes):Data;`
  * `...`
  * `n (varibale bytes):Associated value n;`
* …
* n (varibale byte):Message Object n;

安全功能(Security 0x5)

安全功能(Security)是用于安全设置,比如:设置 PLC 密码,所有的子功能码有:

  • 0x01:PLC 密码(PLC password);
  • 0x02:清除密码(Clear PLC password)。

请求报文的 Data 结构如下:

  • 1 (1 byte) : 返回码(return code);
  • 2 (1 byte) : 数据传输大小(Transport sizes);
  • 3 (2 bytes) : 数据长度;
  • 4 (varibale byte):Data;

响应报文 Data 结构如下:

  • 1 (1 byte) : 返回码(Return code);
  • 2 (1 byte) : 数据传输大小(Transport sizes);
  • 3 (2 bytes) : 以此往后的数据长度;

PBC BSEND/BRECV 0x6

  • PBC:Programmable Block Functions,可编程块函数,比如:SFB/FB;
  • BSEND/BRCV:到通信伙伴的固定数据块传送。也就是说,在通信伙伴中的接收函数(BRCV)接受该数据之前,数据传送不会结束。

时间功能(Time functions 0x7)

时间功能(Time functions)是用于时间设置,比如设置时间,所有的子功能码有:

  • 0x01:读时间(Read clock);
  • 0x02:设置时间(Set clock);
  • 0x03:读时间(Read clock (following));
  • 0x04:设置时间(Set clock)。

读取时间的 Data 结构如下:

  • 1 (1 byte) : 返回码(return code);
  • 2 (1 byte) : 数据传输大小(Transport sizes),通常有 bit、byte 等;
  • 3 (2 bytes) : 以此往后的数据长度。

设置时间的 Data 结构如下:

  • 1 (1 byte) : 返回码(return code);
  • 2 (1 byte) : 数据传输大小(Transport sizes),通常有 bit、byte 等;
  • 3 (2 bytes) : 以此往后的数据长度;
  • 4 (10 bytes):时间戳。流量包分析

这里我们使用snap7本地模拟相应的请求流量,并抓包查看。

块功能(Block functions 0x3)

  • 列举所有块(List blocks)
    • 请求
  • 响应
  • 列举块类型(List blocks of type)
    • 请求
  • 响应
  • 读取模块的信息(Get block info)
    • 请求

CPU功能(CPU functions 0x4)

  • 系统状态列表(SZL)
    • 请求
  • 响应

安全功能(Security 0x5)

  • PLC密码(PLC password)
  • 请求

Data 是 64 67 02 06 62 65 17 10

代码语言:txt复制
* 第1位:0x64 ^ 0x55 = 0x31,则值是1;
* 第2位:0x67 ^ 0x55 = 0x32,则值是2;
* 第3位:0x02 ^ 0x55 ^ 0x64 = 0x33,则值是3;
* 第4位:0x06 ^ 0x55 ^ 0x67 = 0x34,则值是4;
* 第5位:0x62 ^ 0x55 ^ 0x02 = 0x35,则值是5;
* 第6位:0x65 ^ 0x55 ^ 0x06 = 0x36,则值是6;
* 第7位:0x17 ^ 0x55 ^ 0x62 = 0x20,则值是 (空);
* 第8位:0x10 ^ 0x55 ^ 0x65 = 0x20,则值是 (空);
  • 响应

时间功能(Time functions 0x7)

  • 读时间(Read clock);
    • 请求
  • 响应

总结

本文,我们分析了S7Comm协议的Userdata 协议拓展部分。通过与pcap流量包的结合分析,可以更加直观的了解其原理和交互过程,学习S7Comm协议对于工控安全非常重要,在之后的文章里,我们还将继续学习modbus和Ethernet/IP协议相关内容。


本文作者 r0fus0d

0 人点赞