抓取和分析西门子S7COMM协议

近期在初步入门工控安全,进行了第一次协议分析,本文将介绍如何对 S7COMM 协议进行从零到一的分析。

因为是纯小白,加上几乎没人指引,又由于 S7COMM 是西门子的私有协议,本文的编写全靠个人的分析与网上的资料参考,无法保证编写的内容全部准确。

如何抓取到 S7COMM 协议

要想对一个协议进行分析,必须先能抓取到该协议。这里我目前尝试了两种方法,第一种是使用 Snap7 模拟器,优点是快捷,方便;第二种是搭建一整套仿真环境,优点是很充分的模拟出真实环境,如果还需要进行除协议外的其他研究,可能需要搭建出一套仿真环境。如果有设备的话,那就更加方便和真实了(不过我没有,所以这种情况就不做记录了)

使用 Snap7 模拟器抓取S7协议

关于下载和基本使用的话,这篇 文章 介绍的很清晰,下面直接介绍用 Wireshark 如何在 Snap7 模拟器上抓取到 S7COMM 流量包

这里直接写成 0.0.0.0 ,然后点 Start 如下

image-20230719100703797

然后这里选择 127.0.0.1 ,点击 Connect 如下

image-20230719100847381

Wireshark 中选择这个 Adapter for loopback traffic capture

image-20230719101031104

以向 PLC 发出 wirte 请求为例,我们在客户端做如下设置,然后点击 write

image-20230719103232717

请求发出后,我们用 Wireshark 就能抓取到这个数据包了,这里用 cotp 来过滤一下。这里只介绍协议的抓取,后面再做协议分析

image-20230719103942675

在仿真环境抓取S7协议

具体的搭建环境过程请参考这篇 文章 ,仿真软件的话我用的是 S7-PLCSIM Advanced v3.0,这里只介绍环境搭建后如何用 Wireshark 抓取到 S7COMM 流量包(如果安装不熟练的话,建议安装在虚拟机里面,笔记本能吃得消的话,内存最好配置成 16G ,不然会很卡 )

首先从控制面板里找到更改适配器这里,然后点击以太网的属性(如下)

image-20230719173807624

然后点击下面的版本协议

image-20230719173918379

然后把 IP 配置成 192.168.0.100 (也不是非要跟我这个一样)

image-20230719174001810

打开 S7-PLCSIM Advanced v3.0 仿真工具,然后配置就跟图上标注的一样即可,最后点击 Start

image-20230719174308726

此时正常效果应该是如下,有个黄色的灯亮了

image-20230719174707349

接下来配置博途,点这个创建新项目,项目名称随便取即可,最后点创建

image-20230719174904709

点击打开项目视图

image-20230719175012377

这里来添加新设备,随便选中一个 1500 型号的 CPU

image-20230719175103790

正常的话,应该会出现如下界面,点击红框中的这个地方,来配置PLCIP

image-20230719175230535

这里的 IP 一定要和仿真工具里面配置的 IP 一样(如下)

image-20230719175341919

下面这里需要选中 允许来自远程对象的 PUT/GET通信访问

image-20230719175551017

下面创建一个 DB

image-20230719175712502

然后点击这个 DB 块,写入信息如下图所示(至于变量类型和个数都无所谓,这里只是做一个演示)

image-20230719175829938

右键 DB 块,点击属性,然后就能看到下面的配置。把 优化的块访问 选项给取消掉

image-20230719180031028

项目这里,也就是下图的 抓取流量测试 的位置,右键选择属性,在保护这一栏勾选 块编译时支持仿真 选项

image-20230720092822790

接下来进行编译和下载,依次点击下面的 编译下载到设备 ,选中和图中一样的接口类型和接口(似乎默认就是这样),最后点击开始搜索

image-20230720093250332

如果前面的配置都正确的话,应该会出现下图的状态并且 下载 按钮也会亮起来(如果没亮的话,肯定是前面配置错了,大概率是 PLCIP 和仿真工具中的 IP 或者以太网卡的 IP 没有配置好),点击 下载 按钮

image-20230720093505604

正常的话,应该是要先点一下 装载,然后选择 启动模块 ,点击 完成(如果跟下面一样,直接到这个界面的话,就不需要再管装载了,点击完成就行)

image-20230720093750806

至此全部完成的话,那么应该和下图一样,从之前的黄灯变成绿灯。

image-20230720093953441

并且也是能够直接 pingPLC 的(如下)

image-20230720094130178

最后我使用的是 python 中的 snap7 模块,对 PLC 发出读写请求。从而用 Wireshark 抓取通信时的流量,用 S7COMM 来过滤一下,可以发现成功的抓取到了 S7COMM 协议

image-20230720094448068

这里我写的脚本代码如下,如果没不太理解函数作用的话,可以阅读一下 官方文档

import snap7
from snap7 import util
plc = snap7.client.Client()

# 连接至PLC
plc.connect('192.168.0.11', 0, 1)
print(f"连接状态: {plc.get_connected()}")

# 读取数据
recv_data=plc.read_area(snap7.client.Areas.DB,1,0,28)

list_data=[]
cnt=0
for i in range(0,int(len(recv_data)),2):
list_data.append(int.from_bytes(recv_data[i:i+2],byteorder='big'))
cnt=cnt+1

cnt=0
for i in list_data:
print("The {} set of data parsed {}".format(cnt,list_data[cnt]))
cnt=cnt+1

print("="*40)
num=bytearray(2)
for i in range(14):
print("write {} to DB.1 block offset of {}".format(i*4,i*2))
util.set_int(num,0,i*4)
plc.db_write(1,i*2,num)

# 关闭连接
#plc.disconnect()
print(f"连接状态: {plc.get_connected()}")

因为是初学者,有一些问题在上网查过资料后没有找到想要的答案,在下面记录一下(希望有明白的朋友能告知一下)

  1. 配置的 PLCS7-1500 ,为什么发出读写请求抓到的是 S7COMM 协议,而不是 S7 COMM PLUS 协议
  2. 除了用 python 脚本直接对 PLC 发出请求之外,还有哪些方式能抓取到与 PLC 通信的流量(我发现在 TIA 中的上传和下载也有通信的流量)
  3. python-snap7 给出了 plc_stop() 函数,该函数可以直接向 PLC 发出关机请求,我使用了之后发现没有用,是哪里进行了阻止呢?或者是仿真环境无法实现的原因?

S7COMM协议分析

ISO-OSI参考模型 S7以太网协议模型
7-应用层 6-S7 Communication
6-表示层 S7 communication(COTP)
5-会话层 S7 communication(TPKT)
4-传输层 3-TCP(102端口)
3-网络层 IP
2-数据链路层 1-工业以太网
1-物理层

这里我用 Snap7 模拟器来抓取一个 S7COMM 的流量进行分析,先介绍一下 Snap7 客户端中的 Area DB Number Start Amount WordLen 这五部分(如下图)

image-20230720100018356
  1. Area 表示数据区域类型,常见区域如下

    Hex Value 描述
    0x03 System info of 200 family 200系列系统信息
    0x05 System flags of 200 family 200系列系统标志
    0x06 Analog inputs of 200 family 200系列模拟量输入
    0x07 Analog outputs of 200 family 200系列模拟量输出
    0x80 Direct peripheral access (P) 直接访问外设
    0x81 Inputs (I) 输入(I)
    0x82 Outputs (Q) 输出(Q)
    0x83 Flags (M) 内部标志(M)
    0x84 Data blocks (DB) 数据块(DB)
    0x85 Instance data blocks (DI) 背景数据块(DI)
    0x86 Local data (L) 局部变量(L)
    0x87 Unknown yet (V) 全局变量(V)
    0x1c S7 counters (C) S7计数器(C)
    0x1d S7 timers (T) S7定时器(T)
    0x1e IEC counters (200 family) IEC计数器(200系列)
    0x1f IEC timers (200 family) IEC定时器(200系列)
  2. DB Number 代表数据块编号,如果访问的不是数据库则该位置为 0

  3. Start 表示从指定数据块的起始位置写入数据,Start4 ,那么则会从编号为 4(第五个字节处)开始写入或读出数据

  4. Amount 表示写入或读出的数据量(这单位不一定是字节,取决于 WordLen 字段的类型)

  5. WordLen 表示单位长度,可以选择 Byte Word DWord 等等

接下来点击 Write ,此时抓取一个 S7COMM 的报文

image-20230711155651147

image-20230711160356351

S7 Communication
Header: (Job)
Protocol Id: 0x32
ROSCTR: Job (1)
Redundancy Identification (Reserved): 0x0000
Protocol Data Unit Reference: 1792
Parameter length: 14
Data length: 44
Parameter: (Write Var)
Function: Write Var (0x05)
Item count: 1
Item [1]: (DB 1.DBX 4.0 WORD 20)
Variable specification: 0x12
Length of following address specification: 10
Syntax Id: S7ANY (0x10)
Transport size: WORD (4)
Length: 20
DB number: 1
Area: Data blocks (DB) (0x84)
Address: 0x000020
.... .000 0000 0000 0010 0... = Byte Address: 4
.... .... .... .... .... .000 = Bit Address: 0
Data
Item [1]: (Reserved)
Return code: Reserved (0x00)
Transport size: BYTE/WORD/DWORD (0x04)
Length: 40
Data: 010366778899000011000000000000000000000000000000000000ff0000000000000000…

S7COMM 的完整数据如上,下面针对 Header Parameter Data 三部分的各个字段分别做出解释

S7 Communication
Header: (Job)
Protocol Id: 0x32
ROSCTR: Job (1)
Redundancy Identification (Reserved): 0x0000
Protocol Data Unit Reference: 1792
Parameter length: 14
Data length: 44

Protocol Id 为协议 ID ,属于常量为 0x32

ROSCTR 表示 PDU (协议数据单元)的类型,有以下值

类型 解释
0x01 Job 主站发送的作业请求(例如读/写存储器,读/写块,启/停设备,通信设置)
0x02 Ack 没有数据字段的简单确认
0x03 Ack_Data 带有数据字段的应答,一般来响应 Job 请求
0x07 Userdata 扩展协议,其参数字段包含请求/响应ID,一般用于编程/调试、读取SZL

Redundancy Identification (Reserved) : 冗余数据,通常为 0x0000

Protocol Data Unit Reference : 确保通信中每个 PDU 都有唯一的标识,发送方会为每个发送的 PDU 分配一个独立的协议数据单元参考值。接收方在接收到PDU时会检查该字段,以确认该 PDU 是否是之前接收过的重复 PDU ,或者是一个新的 PDU Job 报文和 Ack_Data 报文的protocol data unit reference 值是一样的,个人猜测是为了确保 Ack_Data 是对应于特定的 Job 报文相应

Parameter length :参数 Parameter 字段的长度

Data length :数据 Data 字段的长度

Error class :仅存在于 Ack-Data 报文中,可能的错误常量查看协议常量

Error code :仅存在于 Ack-Data 报文中,可能的错误常量查看协议常量

Parameter

Parameter: (Write Var)
Function: Write Var (0x05)
Item count: 1
Item [1]: (DB 1.DBX 4.0 WORD 20)
Variable specification: 0x12
Length of following address specification: 10
Syntax Id: S7ANY (0x10)
Transport size: WORD (4)
Length: 20
DB number: 1
Area: Data blocks (DB) (0x84)
Address: 0x000020
.... .000 0000 0000 0010 0... = Byte Address: 4
.... .... .... .... .... .000 = Bit Address: 0

上面 Header 部分提到了 Parameter 字段的长度为 14 ,通过下图观察,得以验证

image-20230711164349761

Function :功能码,当 PDU 类型为 Job 或者 Ack_Data 类型时常见的功能码如下:

Hex Value 含义
0x00 CPU services CPU服务
0xf0 Setup communication 建立通信
0x04 Read Var 读取值
0x05 Write Var 写入值
0x1a Request download 请求下载
0x1b Download block 下载块
0x1c Download ended 下载结束
0x1d Start upload 开始上传
0x1e Upload 上传
0x1f End upload 上传结束
0x28 PI-Service 程序调用服务
0x29 PLC Stop 关闭PLC

Item count :表示一个请求中包含的数据项 Item 的数量

Item :数据项,Item [1] 表示第一个数据项。它描述了要操作的数据的详细信息,包括数据类型、长度和位置等。

Item 结构又包含如下字段:

Variable specification :变量规范字段,一般为 0x12

Length of following address specification :地址规范长度,主要是以此往后的地址长度(通过下面这个图看的可能比较直观)

image-20230711171959489

Syntax Id :为 IDS 的地址规范格式类型,主要用来确定寻址模式。常见值如下

Hex 描述
0x10 S7ANY Address data S7-Any pointer-like DB1.DBX10.2
0x13 PBC-R_ID R_ID for PBC
0x15 ALARM_LOCKFREE Alarm lock/free dataset
0x16 ALARM_IND Alarm indication dataset
0x19 ALARM_ACK Alarm acknowledge message dataset
0x1a ALARM_QUERYREQ Alarm query request dataset
0x1c NOTIFY_IND Notify indication dataset
0xa2 DRIVEESANY seen on Drive ES Starter with routing over S7
0xb2 1200SYM Symbolic address mode of S7-1200
0xb0 DBREAD Kind of DB block read, seen only at an S7-400
0x82 NCK Sinumerik NCK HMI access

Transport size :指的是数据传输的大小或类型,它用于定义在进行数据交换时所使用的数据单元大小。

Hex
2 BYTE
3 CHAR
4 WORD
5 INT
6 DWORD
7 DINT
8 REAL

Length 指的是写/读 数据的长度,就是模拟器中的 Amount 字段

image-20230713103518022

DB number : DB块编号,如果访问的不是DB区域,此处为0x0000

Area :代表要操作的区域类型,如下表

Hex Value 描述
0x03 System info of 200 family 200系列系统信息
0x05 System flags of 200 family 200系列系统标志
0x06 Analog inputs of 200 family 200系列模拟量输入
0x07 Analog outputs of 200 family 200系列模拟量输出
0x80 Direct peripheral access (P) 直接访问外设
0x81 Inputs (I) 输入(I)
0x82 Outputs (Q) 输出(Q)
0x83 Flags (M) 内部标志(M)
0x84 Data blocks (DB) 数据块(DB)
0x85 Instance data blocks (DI) 背景数据块(DI)
0x86 Local data (L) 局部变量(L)
0x87 Unknown yet (V) 全局变量(V)
0x1c S7 counters (C) S7计数器(C)
0x1d S7 timers (T) S7定时器(T)
0x1e IEC counters (200 family) IEC计数器(200系列)
0x1f IEC timers (200 family) IEC定时器(200系列)

Data

Data
    Item [1]: (Reserved)
        Return code: Reserved (0x00)
        Transport size: BYTE/WORD/DWORD (0x04)
        Length: 40
        Data: 010366778899000011000000000000000000000000000000000000ff0000000000000000…

因为 Wirte 操作要写入值,所以它比 Read 操作多一个 Data 部分。

return code :返回码,用来标识 job 操作后的结果(这里的 Return code0x00 ,因为这里截取的是请求报文,它对应的响应报文中的 return code 字段是 0xff

Hex 描述
0x00 Reserved 未定义,预留
0x01 Hardware error 硬件错误
0x03 Accessing the object not allowed 对象不允许访问
0x05 Invalid address 无效地址,所需的地址超出此PLC的极限
0x06 Data type not supported 数据类型不支持
0x07 Data type inconsistent 日期类型不一致
0x0a Object does not exist 对象不存在
0xff Success 成功

Transport size :数据传输的大小或类型,上面已经介绍过

Length :这里的 Length 指的是只有 Data (下面这个字段)的长度,并不是这个 Data 部分的长度

Data :需要写入的数据

参考文章

https://xz.aliyun.com/t/6603?accounttraceid=336501792de944e4807240804083f4f3zheo#toc-2

https://blog.csdn.net/oliver223/article/details/118107094

https://blog.csdn.net/weixin_43158056/article/details/104298156