带你走进 S7COMM 与 MODBUS 工控协议

本文首发先知社区:https://xz.aliyun.com/t/6603

0x01 S7COMM协议

S7COMM 全称S7 Communication,是西门子专有协议,在西门子S7-300 / 400系列的PLC之间运行,用于PLC编程,PLC之间的数据交换。S7协议被封装在TPKT和ISO-COTP协议中,这里就不对 TPKT 和 ISO-COTP 进行介绍了,仅仅说一些关于 S7Comm 协议部分。
S7Comm协议包含三部分:

  • Header
  • Parameter
  • Data
    Header 的内容包含8 个字段,如下图所示:

image.png

0 – 1 字节:

image.png

Protocol Id;即协议ID,通常为0x32;

1 – 2 字节:

image.png

ROSCTR:PDU type;即PDU的类型,一般有以下值:

  • 0x01:JOB
    即作业请求,如,读/写存储器,读/写块,启动/停止设备,设置通信
  • 0x02:ACK
    即确认相应,这是一个没有数据的简单确认
  • 0x03:ACK_DATA
    即确认数据相应,一般是响应JOB的请求
  • 0x07:USERDATA
    即扩展协议,其参数字段包含请求/响应ID,一般用于编程/调试、读取SZL等

2 – 4 字节:

image.png

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

4 - 6 字节:

image.png

Protocol Data Unit Reference,即协议数据单元参考,它通过请求事件增加;

6 – 8 字节:

image.png

Parameter length,即参数的总长度

8 – 10 字节:

Data length,即数据长度,如果读取PLC内部数据,此处为0x0000;而对于其他功能,则为Data部分的数据长度;

10 – 12 字节:

image.png

Error class,即错误类型,其错误代码对应意思如下表:

HexValue描述
0x00No error没有错误
0x81Application relationship应用关系
0x82Object definition对象定义
0x83No resources available没有可用资源
0x84Error on service processing服务处理中错误
0x85Error on supplies请求错误
0x87Access error访问错误

12 - 14 字节:

image.png

Error code,即错误码,其具体意思参考附录一:错误码具体含义

以上为 header 的全部内容,而 S7comm 协议的Parameter 部分与Data 部分,则是根据header 中PDU type的功能码的不同、协议扩展(Userdata)的内容不同而变得不同。这里仅对 PDU type 的不同功能码进行简单介绍,更为复杂的协议扩展内容,暂不做解释。

当PDU类型是JOB和ACK_DATA时,常见的功能码,如下表:

HexValue含义
0x00CPU servicesCPU服务
0xf0Setup communication建立通信
0x04Read Var读取值
0x05Write Var写入值
0x1aRequest download请求下载
0x1bDownload block下载块
0x1cDownload ended下载结束
0x1dStart upload开始上传
0x1eUpload上传
0x1fEnd upload上传结束
0x28PI-Service程序调用服务
0x29PLC Stop关闭PLC

以下内容会对这些功能码的结构进行简单介绍。

Setup communication(0xf0)

Setup communication会在每个会话开始时被发送,然后才可以交换任何其他消息,主要用于协商ACK队列的大小和最大PDU长度,双方声明它们的支持值(和计算机网络中的最大传送长度声明类似),ACK队列的长度决定了可以同时启动而不需要确认的并行作业的数量。PDU和队列长度字段都是大端。

Setup communication Parameter的结构如下:

image.png

image.png

0 – 1 字节:

Function: Setup communication (0xf0),即Parameter/data 的函数声明

1 – 2 字节:

Reserved: 0x00,保留字节,一般为 0x00

2 – 4 字节:

Max AmQ (parallel jobs with ack) calling

4 – 6 字节:

Max AmQ (parallel jobs with ack) called

6 – 8 字节:

PDU length,即协商PDU长度

如下图所示的是PDU 类型为 Job 和 Ack_Data的实例

image.png

image.png

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

Read Var(0x04)

读数据操作,通过指定变量的存储区域,地址(偏移量)及其大小或类型来执行。
Read Var 的Parameter的结构以及item的结构如下:

image.png

image.png

0 – 1 字节:

Function: Read Var (0x04),即Parameter/data 的函数声明

1 - 2 字节:

Item count,即 Item 的个数

2 – 14 字节:

Item [1],即第一个 Item

14+12n – 14+12(n+1)字节:

Item[n],即第 N 个 Item

一个item的结构如下:

0 – 1字节:

Variable specification,即结构标识,通常为 0x12 ,代表变量规范

1 – 2 字节:

Length of following address specification,即地址规范长度,主要是以此往后的地址长度

2 – 3 字节:

Syntax Id,全称Syntax Ids of variable specification,即IDS 的地址规范的格式类型,用于确定寻址模式和其余项目结构的格式。常见的变量的结构标识如下表所示:
Hex描述
0x10S7ANYAddress data S7-Any pointer-like DB1.DBX10.2
0x13PBC-R_IDR_ID for PBC
0x15ALARM_LOCKFREEAlarm lock/free dataset
0x16ALARM_INDAlarm indication dataset
0x19ALARM_ACKAlarm acknowledge message dataset
0x1aALARM_QUERYREQAlarm query request dataset
0x1cNOTIFY_INDNotify indication dataset
0xa2DRIVEESANYseen on Drive ES Starter with routing over S7
0xb21200SYMSymbolic address mode of S7-1200
0xb0DBREADKind of DB block read, seen only at an S7-400
0x82NCKSinumerik NCK HMI access

3 – 4 字节:

Transport size,即数据传输大小,常见值如下表:
Hex描述
0NULL
3BITbit access, len is in bits
4BYTE/WORD/DWORDbyte/word/dword access, len is in bits
5INTEGERinteger access, len is in bits
6DINTEGERinteger access, len is in bytes
7REALreal access, len is in bytes
9OCTET STRINGoctet string, len is in bytes

4 – 6 字节:

Length,即数据的长度

6 – 8 字节:

DB number,即 DB 编号,如果访问的不是DB区域,此处为0x0000,如下图所示:

image.png

8 – 9 字节:

Area,即区域,常见的区域如下表所示:
HexValue描述
0x03System info of 200 family200系列系统信息
0x05System flags of 200 family200系列系统标志
0x06Analog inputs of 200 family200系列模拟量输入
0x07Analog outputs of 200 family200系列模拟量输出
0x80Direct peripheral access (P)直接访问外设
0x81Inputs (I)输入(I)
0x82Outputs (Q)输出(Q)
0x83Flags (M)内部标志(M)
0x84Data blocks (DB)数据块(DB)
0x85Instance data blocks (DI)背景数据块(DI)
0x86Local data (L)局部变量(L)
0x87Unknown yet (V)全局变量(V)
0x1cS7 counters (C)S7计数器(C)
0x1dS7 timers (T)S7定时器(T)
0x1eIEC counters (200 family)IEC计数器(200系列)
0x1fIEC timers (200 family)IEC定时器(200系列)

9 – 12 字节:

Address,即地址。

举个简单的例子可以说明下 Item 数据包的核心内容:

image.png

如上图所示,这是一个 PDU 类型为 Job,功能码为 Read Var的数据包,其主要内容为 Item[1]读取DB1类型为 BYTE 的0x00000000地址的值。

需要注意的是,PDU 类型为 Ack_Data,功能码为 Read Var的数据包,其Parameter结构和PDU 类型为 Job,功能码为 Read Var的Parameter结构是不一样的。如下图所示:

image.png

可以看到,其Parameter只有function和item count两个字段,但其多了一部分内容,即 data,在 PDU 为 Job 的数据包中,是没有这个内容的。Data 的结构如下图所示:

image.png

0 – 1 字节:

Return code,即返回码,响应报文中Data部分的常见返回码如下表:
Hex描述
0x00Reserved未定义,预留
0x01Hardware error硬件错误
0x03Accessing the object not allowed对象不允许访问
0x05Invalid address无效地址,所需的地址超出此PLC的极限
0x06Data type not supported数据类型不支持
0x07Data type inconsistent日期类型不一致
0x0aObject does not exist对象不存在
0xffSuccess成功

1 - 2 字节:

Transport size,即数据传输大小,常见的 data 中数据传输大小的值如下表:
Hex描述
0NULL
3BITbit access, len is in bits
4BYTE/WORD/DWORDbyte/word/dword access, len is in bits
5INTEGERinteger access, len is in bits
6DINTEGERinteger access, len is in bytes
7REALreal access, len is in bytes
9OCTET STRINGoctet string, len is in bytes

2 – 4 字节:

 Length:,即数据的长度

4 – 4+length(未定义) 字节:

Data,即数据。

4+length(未定义) – 5 字节:

Fill byte,即填充字节,如果数据的长度不满足 2-4 字节中的Length的话,填充0x00

Write Var(0x05)

写数据操作,通过指定变量的存储区域,地址(偏移量)及其大小或类型来执行。Write Var中Parameter的结构和 Read Var 中的 parameter 的结构一致,但是由于是写数据,因此和读数据相比,多了写的data结构:

Write Var的结构如下:

image.png

如上图所示,这就是一个向地址为0x000000的Flags(M)写入0x77100002的作业请求。
与此对应,其Ack_Data 功能码结构也很类似:

image.png

与 Read Var 的结构相比,Write Var 在Data中只有一个Return code字段。
上图中的Item1,表示向地址为0x000000的Flags(M)写入0x77100002成功!

Request download(0x1a)

Request download,即请求下载功能码,说这个之前不得不说一下整个下载的流程。因为这个内容与 Download block、Download ended 内容其实是连贯的,三个功能码构成了一个完整的下载流程。
其流程如下:

image.png

说了上面的流程后,就可以来正式开始谈request download,Request download的Parameter的结构如下:

image.png

image.png

0 – 1 字节:

Function: Request download (0x1a),即Parameter/data 的函数声明

1 – 2 字节:

Function Status,即功能状态,包含错误是否发生、是否使用另一个检索块/文件来请求的更多数据

2 – 4 字节:

Unknown byte(s) in blockcontrol,无意义

4 – 8 字节:

Unknown byte(s) in blockcontrol,无意义,一般为0x00000000

8 – 9 字节:

Filename Length,即文件名长度

9 – 18 字节:

Filename,文件名,其结构如下:

image.png

0 – 1 字节:

File identifier,即文件标识符,有_ (Complete Module)、$ (Module header for up-loading)两种文件标识符;

1 – 3 字节:

Block type,即块类型,在西门子设备中有8种不同类型的功能块,如下表:
Hex类型描述
0x3038OB,ASCII为'08',组织块OB决定用户程序的结构
0x3039CMod,ASCII为'09'
0x3041DB,ASCII为'0A',数据块DB是用于存储用户数据的数据区域,除了指定给一个功能块的数据,还可以定义可以被任何块使用的共享数据
0x3042SDB,ASCII为'0B',系统和数据块由编程软件自动生成主要存放PLC的硬件组态等信息,用户无法直接打开和更改
0x3043FC,ASCII为'0C',功能FB、FC本质都是一样的,都相当于子程序,可以被其他程序调用(也可以调用其他子程序),FC使用的是共享数据块
0x3044SFC,ASCII为'0D',系统功能SFB和SFC集成在S7 CPU中可以让你访问一些重要的系统功能
0x3045FB,ASCII为'0E',功能块,带背景数据块FB、FC本质都是一样的,都相当于子程序,可以被其他程序调用(也可以调用其他子程序),FB使用的是背景数据块
0x3046SFB,ASCII为'0F',系统功能块SFB和SFC集成在S7 CPU中可以让你访问一些重要的系统功能
OB、FB、SFB、FC和SFC都包含部分程序,因此也称作逻辑块。每种块类型所允许的块的数量以及块的长度视CPU而定。

3 – 8 字节:

Block number,即请求的块编号

8 – 9 字节:

Destination filesystem,即请求目标的文件系统,有三种:
  • P(Passive (copied, but not chained) module):被动文件系统
  • A (Active embedded module):主动文件系统
  • B (Active as well as passive module):既主既被文件系统

18 – 19 字节:

Length part ,即参数的第二部分长度,也就是接下来的字段长度;

19 – 20 字节:

Unknown char before load mem,即加载mem之前的未知字符

20 – 26 字节:

Length of load memory,即装载长度

26 – 32 字节:

Length of MC7 code,即 MC7 代码长度

以上即为 request download的结构,其实也就是要告诉 PLC,我要下载块了,如下实例:

image.png

上图中,请求的文件标识是_ (Complete Module),请求块类型为DB,块的编号为00001,目标块的文件系统是P (Passive (copied, but not chained) module),所以文件名为_0A00001P,用于将DB1复制到被动文件系统或从被动文件系统复制。

与读写文件类似,这里PDU类型是Job ,而PDU类型是 Ack_Data时,Request download 的结构如下:

image.png

可以看到,其 Parameter 内容仅有一个function 确认,因此在 request download 的job和 ack_data 完成后,就可以进行download block了。

Download block(0x1b)

Download block的Parameter的结构如下:

image.png

可以看到,Download blockParameter内容是和request download的内容重合的,只不过与后者相比,少了18 – 32 字节的内容(Length part、Unknown char before load mem、Length of load memory、Length of MC7 code),上图的内容也就是下载块 _0A00001P 的作业请求。

PDU类型为Ack_Data时,其Parameter的结构如下图所示:

image.png

Parameter 内容仅有function确认和function 状态,但是多出了 Data 结构,内容主要为数据长度、未知字节以及数据内容,数据内容的长度。

需要注意的是,一个完整的下载块,可能会通过几次请求,如下所示:

image.png

Download ended(0x1c)

Download ended的Parameter的结构如下:

image.png

从上图中可以看到Download endedDownload blockParameterRequest downloadParameter的第一部分相同,仅是少了18 – 32 字节的内容(Length part、Unknown char before load mem、Length of load memory、Length of MC7 code),这里的意思也就是结束下载0A00001P的作业请求。

当PDU类型为Ack_Data时,Download ended的Parameter结构如下:

image.png

只有一个function确认,返回后即确定结束下载_0A00001P的响应,整个下载过程也就完成了。

Start upload(0x1d)

Start upload,即开始上传功能码,和请求下载功能码类似,说这个之前也不得不说一下整个上传的流程。因为这个内容与 Upload、End upload 内容其实是连贯的,三个功能码构成了一个完整的上传流程。

其流程如下:

image.png

说了上面的流程后,就可以来正式开始谈Start upload,Start upload的Parameter的结构如下:

image.png

可以看到,Start upload 的结构和Request Download 的前部分结构一致,如上图所示的内容,其实就是告诉 PLC 一个文件名,文件标识是 _ (Complete Module),块类型为0B(SDB),块的编号为00000,目标块的文件系统是A (Active embedded module),所以文件名为_0B00000A

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

image.png

image.png

0 – 1 字节:

Function: Start upload (0x1d),即Parameter/data 的函数声明

1 – 2 字节:

Function Status,即功能状态,包含错误是否发生、是否使用另一个检索块/文件来请求

2 – 4 字节:

Unknown byte(s) in blockcontrol,即blockcontrol中的所有未知字节

4 – 8 字节:

UploadID,即上传文件会话的 ID

8 – 9 字节:

Blocklengthstring Length,即块长字符串后的字节长度

9 – 16 字节:

Blocklength,完整上传块的长度(以字节为单位),可以拆分为多个PDU

Upload(0x1e)

当PDU类型为Job时,Upload 结构中没有Data,其Parameter的结构,如下:Upload的Parameter的结构如下:

image.png

image.png

0 - 1 字节:

Function: Upload (0x1e),功能码状态;

1 – 2 字节:

Function Status,即功能状态,包含错误是否发生、是否使用另一个检索块/文件来请求

2 – 4 字节:

Unknown byte(s) in blockcontrol,blockcontrol中的所有未知字节

4 – 8 字节:

UploadID,即上传的会话ID,主要是告诉Step7上传会话ID;

这是 JOB 型的结构,当PDU类型为Ack_Data时,Upload有Data,其Parameter的结构,如下:

image.png

在parameter 中仅有 function确认以及 function status,多出的 data 中字节长度主要看 data 的长度,这个结构和 download block 是一致的。

End upload(0x1f)

上传结束的会话过程,当所有上传块上传结束后,Step7对 PLC发送结束上传的作业请求,PLC收到后就关闭会话,然后返回一个Ack_Data响应。

End upload的Parameter的结构如下:

image.png

结构比较简单,没有什么特殊地方,共有8 字节内容,UploadID 最大,有4 个字节。

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

image.png

仅有一个功能确认的结构。到这里,一个上传的过程就结束了。

PI-Service(0x28)

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

PI-Service的Parameter的结构如下:

image.png

image.png

0 – 1 字节:

Function: PI-Service (0x28),即功能码状态

1 – 8 字节:

Unknown bytes,即未知字节

8 – 10 字节:

Parameter block length,即参数块长度

10 -12 字节:

Parameter block,即参数块

12 -13 字节:

String length,即PI service的字符串长度

13 -22 字节:

PI (program invocation) Service,程序调用服务名,具体见附录二

需要注意的是,如果服务调用的参数是块的话,Parameter block的结构是不同的,如下图所示:

image.png

上图中主要含义如下:

服务名称:_INSE
参数:0800001P [OB 1]
请求内容:激活OB 1

请求的结果如下图所示:

image.png

function确认,请求成功

PLC Stop(0x29)

PLC STOP其实和PI -Service 是一致的,PLC Stop的Parameter的结构如下:

image.png

唯一和 PI-Service有区分的地方可能就是 PLC Stop 中不存在Parameter block结构,其他结构,包括 PDU类型为Ack_Data时也是一样,如下图所示:

image.png

小结

上面介绍的S7Comm中PDU类型为JOB和ACK_DATA的相关知识,还有最复杂的UserData的内容就不再介绍了(因为真的太多,太复杂,有兴趣可以自己查询相关资料,这里仅作JOB 和 ACK_DATA 的入门内容介绍),它的用处很广,比如TIME functions、NC programming、CPU functions、Cyclic data、Security等。

0x02 MODBUS 协议

Modbus是Modicon公司推出的协议,ModbusRTU和Modbus ASCII诞生于此。后来施耐德电气(SchneiderElectric)收购了Modicon公司,并在1997年推出了ModbusTCP协议。

2004年,中国国家标准委员会正式把Modbus作为了国家标准,开启了Modbus为中国工业通信做贡献的时代。

MODBUS是一种应用层消息传递协议,位于OSI模型的第7级,提供在不同类型的总线或网络上连接的设备之间的客户端/服务器通信。自1979年以来,作为业界系列上的标准,MODBUS继续使数百万自动化设备能够进行通信。

MODBUS是一种请求/回复协议,提供功能代码指定的服务。 MODBUS功能代码是MODBUS请求/回复PDU的元素。 本文不对 modbus 协议的传输方式、错误检测方法、物理层方面等进行描述,仅对MODBUS内使用的功能代码进行简单介绍。

MODBUS功能码主要分为有效功能码、异常功能码和错误功能码。有效功能码有二十几种,但是一般使用上都以1、2、3、4、5、6、15、16等八种最为常用,以及另外特殊使用的20、21两种,此为General Reference Register,绝大部份的Modbus设备并不会提供此Register。

MODBUS 本来想说很多的,但是发现官方有这方面的文档,因此就不在细说了

文档下载地址:见最下方链接
英文版:

image.png

中文版

image.png

这个里面详细说明了 modbus 的功能码,相关工作原理等,有兴趣的可以下载下来研究一下。

0x03 实例分析

实例一:S7comm 协议分析

题目说明

分析 S7 流量中的异常,在异常数据包中找到 flag

image.png

题解

打开文件如下图:

image.png

由于题目已经说明是 S7 协议,因此可以排除掉非 S7 协议的内容:

image.png

由于包很多,不能确定使用了哪些PDU 类型,将该分组导出:

image.png

用以下脚本来判断使用的 PDU 类型和次数:

#!/usr/bin/env python
#encoding=utf-8

import pyshark

captures = pyshark.FileCapture("cotp.pcapng")
pdu_types = {}
for c in captures:
    for pkt in c:
        if pkt.layer_name == "cotp":
            if hasattr(pkt, "type"):
                type = pkt.type
                if type in pdu_types:
                    pdu_types[type] += 1
                else:
                    pdu_types[type] = 1
print(pdu_types)

image.png

确定使用了三种 PDU 类型功能码,分别为 0x0f(3696 次),0x0e(12 次),0x0d(8次)

上文中没有对 cotp 进行介绍,但其实也仅仅是功能和传输格式上的区别,这三种功能码说明如下:

PDU Type: DT Data (0x0f):

image.png

主要用于数据传输,Parameter中含有 data 结构

PDU Type: CR Connect Request (0x0e)

image.png

主要用于发起连接请求

PDU Type: CC Connect Confirm (0x0d)

image.png

主要用于连接确认

从三种 PDU 协议功能码的出现频率来看,0x0d 和0x0e出现 flag 的概率最大,因此来分析这两种功能码:

image.png

注意到:

每一次发起请求连接对应着一次请求确认,在前三个协议包中,连续三次发起了请求,可能存在问题,查看发现:

在第三个包中,发现有意义的内容:NESSUS

image.png

又看了其他数据包的内容,并未发现有意义内容,因此该字符串即为 flag

实例二:modbus 协议分析

题目说明

image.png

没有给什么提示,就是单纯的协议分析题

题解

打开文件发现:

image.png

非正常的协议包,对比正常协议包发现是文件头尾被篡改,因此使用010 Editor编辑修改文件头和文件尾:

正常 pcap 包

image.png

下载文件的 pcap 包:

image.png

image.png

删除文件的头三个尾三个字节,修改后打开:

image.png

因为协议很多,其他协议内容不是本文的重点,这里只对 modbus 协议进行分析(实际上 flag 也就在 modbus里面),使用筛选器筛选出 modbus 协议,然后分组导出,使用以下脚本确定存在的功能码:

#!/usr/bin/env python
#encoding=utf-8

import pyshark

captures = pyshark.FileCapture("modbus-ics.pcapng")
modbus_func_code = {}
for c in captures:
    for modbus in c:
        if modbus.layer_name == "modbus":
            if hasattr(modbus, "func_code"):
                type = modbus.func_code
                if type in modbus_func_code:
                    modbus_func_code[type] += 1
                else:
                    modbus_func_code[type] = 1
print(modbus_func_code)

image.png

发现只存在功能码为 3 的协议包,共计 274 条。
上文中也提到了,modbus 协议以1、2、3、4、5、6、15、16等八种最为常用,
下表为 modbus 的功能码简单说明:

image.png

功能码为3 的主要作用是读取寄存器,因此我们只需要确定读取寄存器的内容,即可确定 flag。
观察流量包:


发现只要寄存器1的数据在不断变化,因此按照传输顺序提取寄存器1的数据并转成ASCII码,得到数据:

0x39,0x65,0x32,0x33,0x32,0x61,0x62,0x39,0x65,0x30,0x63,0x32,0x65,0x31,0x37,0x33,0x35,0x39,0x64,0x37,0x61,0x64,0x37,0x61,0x64,0x65,0x61,0x30,0x61,0x33,0x30,0x37,0x31,0x38,0x65,0x00,0x33,0x35,0x32,0x66,0x63,0x36,0x31,0x31,0x33,0x66,0x64,0x62,0x61,0x33,0x32,0x36,0x64,0x34,0x39,0x38,0x37,0x37,0x63,0x37,0x33,0x38,0x33,0x34,0x35,0x34,0x65,0x37,0x00,0x61,0x65,0x62,0x66,0x62,0x61,0x34,0x35,0x32,0x33,0x63,0x64,0x66,0x30,0x33,0x64,0x64,0x66,0x65,0x38,0x65,0x66,0x38,0x64,0x66,0x36,0x32,0x30,0x66,0x66,0x35,0x30,0x00,0x61,0x33,0x64,0x65,0x62,0x39,0x65,0x65,0x32,0x37,0x32,0x35,0x37,0x36,0x33,0x33,0x66,0x35,0x39,0x30,0x35,0x37,0x63,0x35,0x36,0x35,0x34,0x64,0x66,0x66,0x65,0x36

使用以下脚本将其转化为字符串:

#!/usr/bin/env python
#encoding=utf-8
data = 0x39,0x65,0x32,0x33,0x32,0x61,0x62,0x39,0x65,0x30,0x63,0x32,0x65,0x31,0x37,0x33,0x35,0x39,0x64,0x37,0x61,0x64,0x37,0x61,0x64,0x65,0x61,0x30,0x61,0x33,0x30,0x37,0x31,0x38,0x65,0x00,0x33,0x35,0x32,0x66,0x63,0x36,0x31,0x31,0x33,0x66,0x64,0x62,0x61,0x33,0x32,0x36,0x64,0x34,0x39,0x38,0x37,0x37,0x63,0x37,0x33,0x38,0x33,0x34,0x35,0x34,0x65,0x37,0x00,0x61,0x65,0x62,0x66,0x62,0x61,0x34,0x35,0x32,0x33,0x63,0x64,0x66,0x30,0x33,0x64,0x64,0x66,0x65,0x38,0x65,0x66,0x38,0x64,0x66,0x36,0x32,0x30,0x66,0x66,0x35,0x30,0x00,0x61,0x33,0x64,0x65,0x62,0x39,0x65,0x65,0x32,0x37,0x32,0x35,0x37,0x36,0x33,0x33,0x66,0x35,0x39,0x30,0x35,0x37,0x63,0x35,0x36,0x35,0x34,0x64,0x66,0x66,0x65,0x36

ret=''
for i in data:
    if i == 0:
        ret+='\n'
    else:
        ret += chr(i)

print (ret)

image.png

可以得到四行字符串:

9e232ab9e0c2e17359d7ad7adea0a30718e
352fc6113fdba326d49877c7383454e7
aebfba4523cdf03ddfe8ef8df620ff50
a3deb9ee27257633f59057c5654dffe6

其中后面3个字符串可以直接破解md5,为_love_this_game!
第一个发现多出来三个字符,观察发现在第 19 位置到第 24 位置,存在重复,删除多余的d7a,解密md5 可得pcl_i
最终得到 flag 为 pcl_i_love_this_game!

实例三:工控系统操作1

image.png

要求

  • 调节某阀门开度,使其数值从35变到大于60,数值稳定、持续时间大于3秒。
  • 完成时间:30分钟

已知条件

  • 工控设备(虚拟机)的IP地址。
  • 设备支持Modbus通信协议,端口号502
  • PLC上有以下寄存器和线圈:
    ** 状态寄存器 0x0000~0x0009,供监视用。

** 控制寄存器 0x000A~0x0014,供控制用。
** 状态线圈 0x0000~0x0009,供监视用。
** 控制线圈 0x000A~0x0014,供控制用。
** 附加寄存器 0x1000~0x1063,供提交FLAG用。

  • 阀门开度对应的状态寄存器为0x0008,对应的控制寄存器为0x0010。

完成标准

操作成功后,附加寄存器0x1000~0x1007中会自动出现一组(8个)非0数值,这些数值对应的ASCII码即为任务完成的FLAG

题解

因为现在已经没有环境可以复现,因此只做简单说明。

这题刚开始很久没人做出来,后来官方给出了 tips:

  • 设定值的前提条件之一是系统处于维护模式
  • 控制寄存器0x00B是系统工作模式,值为3代表维护模式、值为0代表运行模式。
  • 为防止误操作,正常运行时工作模式是锁定的。如要改变工作模式,须先操作某控制线圈来进行解锁。

根据提示,我们首先要将进入系统维护模式,测试发现可改的线圈只有 15,因此需要调整 15 线圈,将其写入 Bool 型数据 true,然后调整工作模式,由11 切换到 3 ,再对控制器进行操作,在地址16写入值 为 100(其他也行,但是要大于 64),最后将系统的工作模式复原,先将线圈 11 改为 0,然后把控制器 15 改成 false 即可。
最后可以从4096[+10]得到ascii,如下图:

image.png

转换后得到flag: 06E6B72D

实例三:工控系统操作2

要求

  • 以正常通信方式打开PLC设备的报警使能开关;
  • 读取PLC系统生成的一条日志,分析日志中记录的报警时间。
  • 完成时间:30分钟。

已知条件

  • 工控设备(虚拟机)的IP地址。
  • 设备支持Modbus通信协议,端口号502。
  • PLC上有以下寄存器和线圈:
    ** 状态寄存器 0x0000~0x0009,供监视用。

** 控制寄存器 0x000A~0x0014,供控制用。
** 状态线圈 0x0000~0x0009,供监视用。
** 控制线圈 0x000A~0x0014,供控制用。
** 状态寄存器 0x100~0x163,供报警日志用。

  • 报警日志使能开关的对应的控制线圈地址为0x000D

完成标准

取得报警时间后,该时间值即为任务完成FLAG,格式为yyyy-mm-dd hh:mm:ss

题解

这题完成的基础是在上题的基础上。
根据已知条件,首先打开日志使能开关,即将线圈13 改为 true
Fuzzing了几个寄存器后,发现256[+100]能看到报错数值,因此读取该地址的数值:

[18:25:10] [256] 25
[18:25:12] [257] 13
[18:25:16] [258] 84 T
[18:25:19] [259] 224
[18:25:23] [260] 92 /

[18:25:26] [261] 115 s
[18:25:29] [262] 119 w
[18:25:32] [263] 105 i
[18:25:34] [264] 116 t
[18:25:37] [265] 99 c
[18:25:39] [266] 104 h
[18:25:41] [267] 32 space
[18:25:44] [268] 49 1
[18:25:46] [269] 51 3
[18:25:50] [263] 105 i
[18:25:56] [270] 49 1
[18:25:59] [271] 32 space
[18:26:02] [272] 116 t
[18:26:06] [273] 114 r
[18:26:08] [274] 105 i
[18:26:10] [275] 112 p
[18:26:13] [276] 32 space
[18:26:15] [277] 111 o
[18:26:18] [278] 102 f
[18:26:20] [279] 102 f
[18:26:23] [280] 46 .
[18:26:26] [281] 0
[18:26:28] [282] 76 L
[18:26:33] [283] 0 
[18:26:37] [284] 0
[18:26:39] [285] 0

通过对上面的数据分析,发现190d54e05c可能是时间戳。

拆分后大小端重排为‭5CE0540D‬,然后转换为10进制 ‭1558205453‬,转换为unix时间戳:‬‬‬‬

image.png

提交flag: 2019-5-19 2:50:53

其实还有第三题工控系统操作,但是由于到最后我们没时间计算校验码,因此没有拿到 flag,这里也就不说了

0x04 总结

S7协议没有公开的官方文档,在S7协议方面也没有官方术语,我了解的也不算深入,只是通过网上一些分享结合自己理解写成的一篇入门文章,如有错误,还望指出,另外,工控安全其实很吃工控设备,如果能有实际/仿真/模拟设备,对于工控安全的学习很有帮助。此文仅作工控安全入门参考文章,希望对你有所帮助。

0x05 参考

西门子通信协议S7Comm
https://laucyun.com/3aa43ada8cfbd7eca51304b0c305b523.html#6-8

Siemens SIMATIC Step 7 Programmer's Handbook
http://www.plcdev.com/book/export/html/373

S7 Communication (S7comm)
https://wiki.wireshark.org/S7comm

wireshark dissector plugin sources
http://gmiru.com/resources/s7proto/constants.txt

PI service names
https://laucyun.com/static/upload/file/2018/01/PI_service_names.txt

工业控制系统安全之——Modbus学习笔记
https://www.freebuf.com/articles/ics-articles/148637.html

Modbus TCP流量分析
http://www.vanimpe.eu/2015/12/07/introduction-to-modbus-tcp-traffic/

0x06 附录一:错误码具体含义

错误码含义
0x0000没有错误
0x0110块号无效
0x0111请求长度无效
0x0112参数无效
0x0113块类型无效
0x0114找不到块
0x0115块已存在
0x0116块被写保护
0x0117块/操作系统更新太大
0x0118块号无效
0x0119输入的密码不正确
0x011APG资源错误
0x011BPLC资源错误
0x011C协议错误
0x011D块太多(与模块相关的限制)
0x011E不再与数据库建立连接,或者S7DOS句柄无效
0x011F结果缓冲区太小
0x0120块结束列表
0x0140可用内存不足
0x0141由于缺少资源,无法处理作业
0x8001当块处于当前状态时,无法执行请求的服务
0x8003S7协议错误:传输块时发生错误
0x8100应用程序,一般错误:远程模块未知的服务
0x8104未在模块上实现此服务或报告了帧错误
0x8204对象的类型规范不一致
0x8205复制的块已存在且未链接
0x8301模块上的内存空间或工作内存不足,或者指定的存储介质不可访问
0x8302可用资源太少或处理器资源不可用
0x8304无法进一步并行上传。存在资源瓶颈
0x8305功能不可用
0x8306工作内存不足(用于复制,链接,加载AWP)
0x8307保持性工作记忆不够(用于复制,链接,加载AWP)
0x8401S7协议错误:无效的服务序列(例如,加载或上载块)
0x8402由于寻址对象的状态,服务无法执行
0x8404S7协议:无法执行该功能
0x8405远程块处于DISABLE状态(CFB)。该功能无法执行
0x8500S7协议错误:帧错误
0x8503来自模块的警报:服务过早取消
0x8701寻址通信伙伴上的对象时出错(例如,区域长度错误)
0x8702模块不支持所请求的服务
0x8703拒绝访问对象
0x8704访问错误:对象已损坏
0xD001协议错误:非法的作业号
0xD002参数错误:非法的作业变体
0xD003参数错误:模块不支持调试功能
0xD004参数错误:作业状态非法
0xD005参数错误:作业终止非法
0xD006参数错误:非法链路断开ID
0xD007参数错误:缓冲区元素数量非法
0xD008参数错误:扫描速率非法
0xD009参数错误:执行次数非法
0xD00A参数错误:非法触发事件
0xD00B参数错误:非法触发条件
0xD011调用环境路径中的参数错误:块不存在
0xD012参数错误:块中的地址错误
0xD014参数错误:正在删除/覆盖块
0xD015参数错误:标签地址非法
0xD016参数错误:由于用户程序错误,无法测试作业
0xD017参数错误:非法触发号
0xD025参数错误:路径无效
0xD026参数错误:非法访问类型
0xD027参数错误:不允许此数据块数
0xD031内部协议错误
0xD032参数错误:结果缓冲区长度错误
0xD033协议错误:作业长度错误
0xD03F编码错误:参数部分出错(例如,保留字节不等于0)
0xD041数据错误:非法状态列表ID
0xD042数据错误:标签地址非法
0xD043数据错误:找不到引用的作业,检查作业数据
0xD044数据错误:标签值非法,检查作业数据
0xD045数据错误:HOLD中不允许退出ODIS控制
0xD046数据错误:运行时测量期间非法测量阶段
0xD047数据错误:“读取作业列表”中的非法层次结构
0xD048数据错误:“删除作业”中的非法删除ID
0xD049“替换作业”中的替换ID无效
0xD04A执行'程序状态'时出错
0xD05F编码错误:数据部分出错(例如,保留字节不等于0,...)
0xD061资源错误:没有作业的内存空间
0xD062资源错误:作业列表已满
0xD063资源错误:触发事件占用
0xD064资源错误:没有足够的内存空间用于一个结果缓冲区元素
0xD065资源错误:没有足够的内存空间用于多个结果缓冲区元素
0xD066资源错误:可用于运行时测量的计时器被另一个作业占用
0xD067资源错误:“修改标记”作业过多(特别是多处理器操作)
0xD081当前模式下不允许使用的功能
0xD082模式错误:无法退出HOLD模式
0xD0A1当前保护级别不允许使用的功能
0xD0A2目前无法运行,因为正在运行的函数会修改内存
0xD0A3I / O上活动的“修改标记”作业太多(特别是多处理器操作)
0xD0A4'强制'已经建立
0xD0A5找不到引用的作业
0xD0A6无法禁用/启用作业
0xD0A7无法删除作业,例如因为当前正在读取作业
0xD0A8无法替换作业,例如因为当前正在读取或删除作业
0xD0A9无法读取作业,例如因为当前正在删除作业
0xD0AA处理操作超出时间限制
0xD0AB进程操作中的作业参数无效
0xD0AC进程操作中的作业数据无效
0xD0AD已设置操作模式
0xD0AE作业是通过不同的连接设置的,只能通过此连接进行处理
0xD0C1访问标签时至少检测到一个错误
0xD0C2切换到STOP / HOLD模式
0xD0C3访问标记时至少检测到一个错误。模式更改为STOP / HOLD
0xD0C4运行时测量期间超时
0xD0C5块堆栈的显示不一致,因为块被删除/重新加载
0xD0C6作业已被删除,因为它所引用的作业已被删除
0xD0C7由于退出了STOP模式,因此作业被自动删除
0xD0C8由于测试作业和正在运行的程序之间不一致,“块状态”中止
0xD0C9通过复位OB90退出状态区域
0xD0CA通过在退出前重置OB90并访问错误读取标签退出状态范围
0xD0CB外设输出的输出禁用再次激活
0xD0CC调试功能的数据量受时间限制
0xD201块名称中的语法错误
0xD202函数参数中的语法错误
0xD205RAM中已存在链接块:无法进行条件复制
0xD206EPROM中已存在链接块:无法进行条件复制
0xD208超出模块的最大复制(未链接)块数
0xD209(至少)模块上找不到给定块之一
0xD20A超出了可以与一个作业链接的最大块数
0xD20B超出了一个作业可以删除的最大块数
0xD20COB无法复制,因为关联的优先级不存在
0xD20DSDB无法解释(例如,未知数)
0xD20E没有(进一步)阻止可用
0xD20F超出模块特定的最大块大小
0xD210块号无效
0xD212标头属性不正确(与运行时相关)
0xD213SDB太多。请注意对正在使用的模块的限制
0xD216无效的用户程序 - 重置模块
0xD217不允许在模块属性中指定的保护级别
0xD218属性不正确(主动/被动)
0xD219块长度不正确(例如,第一部分或整个块的长度不正确)
0xD21A本地数据长度不正确或写保护错误
0xD21B模块无法压缩或压缩早期中断
0xD21D传输的动态项目数据量是非法的
0xD21E无法为模块(例如FM,CP)分配参数。系统数据无法链接
0xD220编程语言无效。请注意对正在使用的模块的限制
0xD221连接或路由的系统数据无效
0xD222全局数据定义的系统数据包含无效参数
0xD223通信功能块的实例数据块错误或超出最大背景数据块数
0xD224SCAN系统数据块包含无效参数
0xD225DP系统数据块包含无效参数
0xD226块中发生结构错误
0xD230块中发生结构错误
0xD231至少有一个已加载的OB无法复制,因为关联的优先级不存在
0xD232加载块的至少一个块编号是非法的
0xD234块在指定的内存介质或作业中存在两次
0xD235该块包含不正确的校验和
0xD236该块不包含校验和
0xD237您将要加载块两次,即CPU上已存在具有相同时间戳的块
0xD238指定的块中至少有一个不是DB
0xD239至少有一个指定的DB在装载存储器中不可用作链接变量
0xD23A至少有一个指定的DB与复制和链接的变体有很大不同
0xD240违反了协调规则
0xD241当前保护级别不允许该功能
0xD242处理F块时的保护冲突
0xD250更新和模块ID或版本不匹配
0xD251操作系统组件序列不正确
0xD252校验和错误
0xD253没有可用的可执行加载程序; 只能使用存储卡进行更新
0xD254操作系统中的存储错误
0xD280在S7-300 CPU中编译块时出错
0xD2A1块上的另一个块功能或触发器处于活动状态
0xD2A2块上的触发器处于活动状态。首先完成调试功能
0xD2A3块未激活(链接),块被占用或块当前被标记为删除
0xD2A4该块已被另一个块函数处理
0xD2A6无法同时保存和更改用户程序
0xD2A7块具有“未链接”属性或未处理
0xD2A8激活的调试功能阻止将参数分配给CPU
0xD2A9正在为CPU分配新参数
0xD2AA当前正在为模块分配新参数
0xD2AB当前正在更改动态配置限制
0xD2AC正在运行的激活或取消激活分配(SFC 12)暂时阻止R-KiR过程
0xD2B0在RUN(CiR)中配置时发生错误
0xD2C0已超出最大工艺对象数
0xD2C1模块上已存在相同的技术数据块
0xD2C2无法下载用户程序或下载硬件配置
0xD401信息功能不可用
0xD402信息功能不可用
0xD403服务已登录/注销(诊断/ PMC)
0xD404达到的最大节点数。不再需要登录诊断/ PMC
0xD405不支持服务或函数参数中的语法错误
0xD406当前不可用的必需信息
0xD407发生诊断错误
0xD408更新已中止
0xD409DP总线错误
0xD601函数参数中的语法错误
0xD602输入的密码不正确
0xD603连接已合法化
0xD604已启用连接
0xD605由于密码不存在,因此无法进行合法化
0xD801至少有一个标记地址无效
0xD802指定的作业不存在
0xD803非法的工作状态
0xD804非法循环时间(非法时基或多个)
0xD805不能再设置循环读取作业
0xD806引用的作业处于无法执行请求的功能的状态
0xD807功能因过载而中止,这意味着执行读取周期所需的时间比设置的扫描周期时间长
0xDC01日期和/或时间无效
0xE201CPU已经是主设备
0xE202由于闪存模块中的用户程序不同,无法进行连接和更新
0xE203由于固件不同,无法连接和更新
0xE204由于内存配置不同,无法连接和更新
0xE205由于同步错误导致连接/更新中止
0xE206由于协调违规而拒绝连接/更新
0xEF01S7协议错误:ID2错误; 工作中只允许00H
0xEF02S7协议错误:ID2错误; 资源集不存在

0x07 附录二:PI server name

服务名称值(描述)
UNKNOWNPI-Service目前不详
_INSEPI-Service _INSE(激活PLC模块)
_DELEPI-Service _DELE(从PLC的被动文件系统中删除模块)
P_PROGRAMPI-Service P_PROGRAM(PLC启动/停止)
_MODUPI-Service _MODU(PLC Copy Ram to Rom)
_GARBPI-Service _GARB(压缩PLC内存)
N_LOGINPI-Service _N_LOGIN_(登录)
_N_LOGOUTPI-Service _N_LOGOUT(退出)
_N_CANCELPI-Service _N_CANCEL(取消NC报警)
_N_DASAVEPI-Service _N_DASAVE(用于将数据从SRAM复制到FLASH的PI-Service)
_N_DIGIOF PI-Service _N_DIGIOF(关闭数字化)
_N_DIGIONPI-Service _N_DIGION(打开数字化)
N_DZEROPI-Service _N_DZERO_(设置所有D nos。对于函数无效“唯一D号。”)
_N_ENDEXTPI-Service _N_ENDEXT()
_N_F_OPERPI-Service _N_F_OPER(以只读方式打开文件)
_N_OST_OFPI-Service _N_OST_OF(Overstore OFF)
_N_OST_ONPI-Service _N_OST_ON(Overstore ON)
N_SCALEPI-Service _N_SCALE_(测量单位设置(公制< - > INCH))
_N_SETUFRPI-Service _N_SETUFR(激活用户帧)
_N_STRTLKPI-Service _N_STRTLK(设置全局启动禁用)
_N_STRTULPI-Service _N_STRTUL(重置全局启动禁用)
_N_TMRASSPI-Service _N_TMRASS(重置活动状态)
_N_F_DELEPI-Service _N_F_DELE(删除文件)
_N_EXTERNPI-Service _N_EXTERN(选择外部程序执行)
_N_EXTMODPI-Service _N_EXTMOD(选择外部程序执行)
_N_F_DELRPI-Service _N_F_DELR(即使没有访问权限也删除文件)
_N_F_XFERPI-Service _N_F_XFER(选择要上传的文件)
N_LOCKEPI-Service _N_LOCKE_(锁定活动文件以进行编辑)
_N_SELECTPI-Service _N_SELECT(选择要执行的程序)
_N_SRTEXTPI-Service _N_SRTEXT(文件正在/ _N_EXT_DIR中标记)
_N_F_CLOSPI-Service _N_F_CLOS(关闭文件)
_N_F_OPENPI-Service _N_F_OPEN(打开文件)
_N_F_SEEKPI-Service _N_F_SEEK(定位文件搜索指针)
N_ASUP_PI-Service _N_ASUP __(分配中断)
_N_CHEKDMPI-Service _N_CHEKDM(对D号码启动唯一性检查)
_N_CHKDNOPI-Service _N_CHKDNO(检查工具是否具有唯一的D编号)
_N_CONFIGPI-Service _N_CONFIG(重新配置机器数据)
_N_CRCEDNPI-Service _N_CRCEDN(通过指定边数来创建切削刃)
_N_DELECEPI-Service _N_DELECE(删除最前沿)
_N_CREACEPI-Service _N_CREACE(创造最前沿)
_N_CREATOPI-Service _N_CREATO(创建工具)
_N_DELETOPI-Service _N_DELETO(删除工具)
_N_CRTOCEPI-Service _N_CRTOCE(生成具有指定边数的工具)
_N_DELVARPI-Service _N_DELVAR(删除数据块)
_N_F_COPYPI-Service _N_F_COPY(复制NCK中的文件)
_N_F_DMDAPI-Service _N_F_DMDA(删除MDA内存)
_N_F_PROTPI-Service _N_F_PROT(为文件指定保护级别)
_N_F_RENAPI-Service _N_F_RENA(重命名文件)
_N_FINDBLPI-Service _N_FINDBL(激活搜索)
_N_IBN_SSPI-Service _N_IBN_SS(设置设置开关)
_N_MMCSEMPI-Service _N_MMCSEM(MMC-Semaphore)
_N_NCKMODPI-Service _N_NCKMOD(正在设置NCK工作的模式)
_N_NEWPWDPI-Service _N_NEWPWD(新密码)
_N_SEL_BLPI-Service _N_SEL_BL(选择新块)
_N_SETTSTPI-Service _N_SETTST(激活替换工具组的工具)
_N_TMAWCOPI-Service _N_TMAWCO(在一个杂志中设置有效磨损组)
_N_TMCRTCPI-Service _N_TMCRTC(创建具有指定边数的工具)
_N_TMCRTOPI-Service _N_TMCRTO(在工具管理中创建工具)
_N_TMFDPLPI-Service _N_TMFDPL(搜索空白处加载)
_N_TMFPBPPI-Service _N_TMFPBP(搜索空位)
_N_TMGETTPI-Service _N_TMGETT(使用Duplono确定特定工具ID的T编号)
_N_TMMVTLPI-Service _N_TMMVTL(加载或卸载工具)
_N_TMPCITPI-Service _N_TMPCIT(设置计件器的增量值)
_N_TMPOSMPI-Service _N_TMPOSM(定位杂志或工具)
_N_TRESMOPI-Service _N_TRESMO(重置监控值)
_N_TSEARCPI-Service _N_TSEARC(通过搜索屏幕进行复杂搜索)

0x08 Modbus 中一些缩写含义

缩写含义
ADU应用数据单元
HDLC高级数据链路控制HMI人机界面
IETF互联网工程任务组
I / O输入/输出
IP互联网协议
MAC媒体访问控制
MBMODBUS协议
MBAPMODBUS应用协议
PDU协议数据单元
PLC可编程逻辑控制器
TCP传输控制协议

0x07 附录三:一些工控协议包及 modbus 的资料

工控数据包:

链接:https://pan.baidu.com/s/1lkr4bsoCJTACzVwzgHcgdQ
密码:tp1u

modbus 资料:
链接:https://pan.baidu.com/s/1Au0HBlGNHGN0JIzl5Iwf8g 密码:gp3b

「一键投喂 软糖/蛋糕/布丁/牛奶/冰阔乐!」

panda

(๑>ڡ<)☆谢谢老板~

使用微信扫描二维码完成支付

本文共有37253个字。标签:S7commmodbus工控协议
版权声明:本文为作者原创,如需转载须联系作者本人同意,未经作者本人同意不得擅自转载。
添加新评论
已有 2 条评论
  1. bingo:

    膜dalao,求好友位一起交流工控~

    1. panda: 回复 @bingo

      可以加我WX:cn_lenyue