前言
在上一篇文章中,我们介绍了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