S7COMM 全称S7 Communication,是西门子专有协议,在西门子S7-300 / 400系列的PLC之间运行,用于PLC编程,PLC之间的数据交换。S7协议被封装在TPKT和ISO-COTP协议中,这里就不对 TPKT 和 ISO-COTP 进行介绍了,仅仅说一些关于 S7Comm 协议部分。
S7Comm协议包含三部分:
0 – 1 字节:
Protocol Id;即协议ID,通常为0x32;
1 – 2 字节:
ROSCTR:PDU type;即PDU的类型,一般有以下值:
2 – 4 字节:
Redundancy Identification (Reserved),即冗余数据,通常为0x0000;
4 - 6 字节:
Protocol Data Unit Reference,即协议数据单元参考,它通过请求事件增加;
6 – 8 字节:
Parameter length,即参数的总长度
8 – 10 字节:
Data length,即数据长度,如果读取PLC内部数据,此处为0x0000;而对于其他功能,则为Data部分的数据长度;
10 – 12 字节:
Error class,即错误类型,其错误代码对应意思如下表:
Hex | Value | 描述 |
---|---|---|
0x00 | No error | 没有错误 |
0x81 | Application relationship | 应用关系 |
0x82 | Object definition | 对象定义 |
0x83 | No resources available | 没有可用资源 |
0x84 | Error on service processing | 服务处理中错误 |
0x85 | Error on supplies | 请求错误 |
0x87 | Access error | 访问错误 |
12 - 14 字节:
Error code,即错误码,其具体意思参考附录一:错误码具体含义
以上为 header 的全部内容,而 S7comm 协议的Parameter 部分与Data 部分,则是根据header 中PDU type的功能码的不同、协议扩展(Userdata)的内容不同而变得不同。这里仅对 PDU type 的不同功能码进行简单介绍,更为复杂的协议扩展内容,暂不做解释。
当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 |
以下内容会对这些功能码的结构进行简单介绍。
Setup communication会在每个会话开始时被发送,然后才可以交换任何其他消息,主要用于协商ACK队列的大小和最大PDU长度,双方声明它们的支持值(和计算机网络中的最大传送长度声明类似),ACK队列的长度决定了可以同时启动而不需要确认的并行作业的数量。PDU和队列长度字段都是大端。
Setup communication Parameter的结构如下:
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的实例
上图中的协商结果为:ACK队列的大小为1;最大PDU长度为240。
读数据操作,通过指定变量的存储区域,地址(偏移量)及其大小或类型来执行。
Read Var 的Parameter的结构以及item的结构如下:
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 | 值 | 描述 |
---|---|---|
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 |
3 – 4 字节:
Transport size,即数据传输大小,常见值如下表:
Hex|值|描述
| :----- | :----- | :----- |
| 0| NULL |
| 3 | BIT | bit access, len is in bits
| 4 | BYTE/WORD/DWORD | byte/word/dword access, len is in bits
| 5 | INTEGER | integer access, len is in bits
| 6 | DINTEGER | integer access, len is in bytes
| 7 | REAL | real access, len is in bytes
| 9 | OCTET STRING | octet string, len is in bytes
4 – 6 字节:
Length,即数据的长度
6 – 8 字节:
DB number,即 DB 编号,如果访问的不是DB区域,此处为0x0000,如下图所示:
8 – 9 字节:
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系列) |
9 – 12 字节:
Address,即地址。
举个简单的例子可以说明下 Item 数据包的核心内容:
如上图所示,这是一个 PDU 类型为 Job,功能码为 Read Var的数据包,其主要内容为 Item[1]读取DB1类型为 BYTE 的0x00000000地址的值。
需要注意的是,PDU 类型为 Ack_Data,功能码为 Read Var的数据包,其Parameter结构和PDU 类型为 Job,功能码为 Read Var的Parameter结构是不一样的。如下图所示:
可以看到,其Parameter只有function和item count两个字段,但其多了一部分内容,即 data,在 PDU 为 Job 的数据包中,是没有这个内容的。Data 的结构如下图所示:
0 – 1 字节:
Return code,即返回码,响应报文中Data部分的常见返回码如下表:
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 | 成功 |
1 - 2 字节:
Transport size,即数据传输大小,常见的 data 中数据传输大小的值如下表:
Hex | 值 | 描述 |
---|---|---|
0 | NULL | |
3 | BIT | bit access, len is in bits |
4 | BYTE/WORD/DWORD | byte/word/dword access, len is in bits |
5 | INTEGER | integer access, len is in bits |
6 | DINTEGER | integer access, len is in bytes |
7 | REAL | real access, len is in bytes |
9 | OCTET STRING | octet string, len is in bytes |
2 – 4 字节:
Length:,即数据的长度
4 – 4+length(未定义) 字节:
Data,即数据。
4+length(未定义) – 5 字节:
Fill byte,即填充字节,如果数据的长度不满足 2-4 字节中的Length的话,填充0x00
写数据操作,通过指定变量的存储区域,地址(偏移量)及其大小或类型来执行。Write Var中Parameter的结构和 Read Var 中的 parameter 的结构一致,但是由于是写数据,因此和读数据相比,多了写的data结构:
Write Var的结构如下:
如上图所示,这就是一个向地址为0x000000的Flags(M)写入0x77100002的作业请求。
与此对应,其Ack_Data 功能码结构也很类似:
与 Read Var 的结构相比,Write Var 在Data中只有一个Return code字段。
上图中的Item1,表示向地址为0x000000的Flags(M)写入0x77100002成功!
Request download,即请求下载功能码,说这个之前不得不说一下整个下载的流程。因为这个内容与 Download block、Download ended 内容其实是连贯的,三个功能码构成了一个完整的下载流程。
其流程如下:
说了上面的流程后,就可以来正式开始谈request download,Request download的Parameter的结构如下:
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,文件名,其结构如下:
0 – 1 字节:
File identifier,即文件标识符,有_ (Complete Module)、$ (Module header for up-loading)两种文件标识符;
1 – 3 字节:
Block type,即块类型,在西门子设备中有8种不同类型的功能块,如下表:
Hex | 类型 | 描述 |
---|---|---|
0x3038 | OB,ASCII为'08',组织块 | OB决定用户程序的结构 |
0x3039 | CMod,ASCII为'09' | |
0x3041 | DB,ASCII为'0A',数据块 | DB是用于存储用户数据的数据区域,除了指定给一个功能块的数据,还可以定义可以被任何块使用的共享数据 |
0x3042 | SDB,ASCII为'0B',系统和数据块 | 由编程软件自动生成主要存放PLC的硬件组态等信息,用户无法直接打开和更改 |
0x3043 | FC,ASCII为'0C',功能 | FB、FC本质都是一样的,都相当于子程序,可以被其他程序调用(也可以调用其他子程序),FC使用的是共享数据块 |
0x3044 | SFC,ASCII为'0D',系统功能 | SFB和SFC集成在S7 CPU中可以让你访问一些重要的系统功能 |
0x3045 | FB,ASCII为'0E',功能块,带背景数据块 | FB、FC本质都是一样的,都相当于子程序,可以被其他程序调用(也可以调用其他子程序),FB使用的是背景数据块 |
0x3046 | SFB,ASCII为'0F',系统功能块 | SFB和SFC集成在S7 CPU中可以让你访问一些重要的系统功能 |
OB、FB、SFB、FC和SFC都包含部分程序,因此也称作逻辑块。每种块类型所允许的块的数量以及块的长度视CPU而定。 |
3 – 8 字节:
Block number,即请求的块编号
8 – 9 字节:
Destination filesystem,即请求目标的文件系统,有三种:
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,我要下载块了,如下实例:
上图中,请求的文件标识是_ (Complete Module),请求块类型为DB,块的编号为00001,目标块的文件系统是P (Passive (copied, but not chained) module),所以文件名为_0A00001P,用于将DB1复制到被动文件系统或从被动文件系统复制。
与读写文件类似,这里PDU类型是Job ,而PDU类型是 Ack_Data时,Request download 的结构如下:
可以看到,其 Parameter 内容仅有一个function 确认,因此在 request download 的job和 ack_data 完成后,就可以进行download block了。
Download block的Parameter的结构如下:
可以看到,Download block
的Parameter
内容是和request download
的内容重合的,只不过与后者相比,少了18 – 32 字节的内容(Length part、Unknown char before load mem、Length of load memory、Length of MC7 code)
,上图的内容也就是下载块 _0A00001P
的作业请求。
PDU类型为Ack_Data时,其Parameter的结构如下图所示:
Parameter 内容仅有function确认和function 状态,但是多出了 Data 结构,内容主要为数据长度、未知字节以及数据内容,数据内容的长度。
需要注意的是,一个完整的下载块,可能会通过几次请求,如下所示:
Download ended的Parameter的结构如下:
从上图中可以看到Download ended
跟Download block
的Parameter
和Request download
的Parameter
的第一部分相同,仅是少了18 – 32 字节的内容(Length part、Unknown char before load mem、Length of load memory、Length of MC7 code)
,这里的意思也就是结束下载0A00001P
的作业请求。
当PDU类型为Ack_Data时,Download ended的Parameter结构如下:
只有一个function确认,返回后即确定结束下载_0A00001P的响应,整个下载过程也就完成了。
Start upload,即开始上传功能码,和请求下载功能码类似,说这个之前也不得不说一下整个上传的流程。因为这个内容与 Upload、End upload 内容其实是连贯的,三个功能码构成了一个完整的上传流程。
其流程如下:
说了上面的流程后,就可以来正式开始谈Start upload,Start upload的Parameter的结构如下:
可以看到,Start upload
的结构和Request Download
的前部分结构一致,如上图所示的内容,其实就是告诉 PLC 一个文件名,文件标识是_ (Complete Module)
,块类型为0B(SDB)
,块的编号为00000
,目标块的文件系统是A (Active embedded module)
,所以文件名为_0B00000A
PDU类型为Ack_Data时,其结构如下图:
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
当PDU类型为Job时,Upload 结构中没有Data,其Parameter的结构,如下:Upload的Parameter的结构如下:
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的结构,如下:
在parameter 中仅有 function确认以及 function status,多出的 data 中字节长度主要看 data 的长度,这个结构和 download block 是一致的。
上传结束的会话过程,当所有上传块上传结束后,Step7对 PLC发送结束上传的作业请求,PLC收到后就关闭会话,然后返回一个Ack_Data响应。
End upload的Parameter的结构如下:
结构比较简单,没有什么特殊地方,共有8 字节内容,UploadID 最大,有4 个字节。
当PDU类型为Ack_Data时,其结构如下:
仅有一个功能确认的结构。到这里,一个上传的过程就结束了。
PI-Service 即程序调用服务 ,它用于PLC修改执行/内存状态的日常工作。这些命令可以用于启动或停止PLC控制程序、激活或删除程序块。
PI-Service的Parameter的结构如下:
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的结构是不同的,如下图所示:
上图中主要含义如下:
服务名称:_INSE
参数:0800001P [OB 1]
请求内容:激活OB 1
请求的结果如下图所示:
function确认,请求成功
PLC STOP其实和PI -Service 是一致的,PLC Stop的Parameter的结构如下:
唯一和 PI-Service有区分的地方可能就是 PLC Stop 中不存在Parameter block结构,其他结构,包括 PDU类型为Ack_Data时也是一样,如下图所示:
上面介绍的S7Comm中PDU类型为JOB和ACK_DATA的相关知识,还有最复杂的UserData的内容就不再介绍了(因为真的太多,太复杂,有兴趣可以自己查询相关资料,这里仅作JOB 和 ACK_DATA 的入门内容介绍),它的用处很广,比如TIME functions、NC programming、CPU functions、Cyclic data、Security
等。
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 本来想说很多的,但是发现官方有这方面的文档,因此就不在细说了
文档下载地址:见最下方链接
英文版:
中文版
这个里面详细说明了 modbus 的功能码,相关工作原理等,有兴趣的可以下载下来研究一下。
分析 S7 流量中的异常,在异常数据包中找到 flag
打开文件如下图:
由于题目已经说明是 S7 协议,因此可以排除掉非 S7 协议的内容:
由于包很多,不能确定使用了哪些PDU 类型,将该分组导出:
用以下脚本来判断使用的 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)
确定使用了三种 PDU 类型功能码,分别为 0x0f(3696 次),0x0e(12 次),0x0d(8次)
上文中没有对 cotp 进行介绍,但其实也仅仅是功能和传输格式上的区别,这三种功能码说明如下:
主要用于数据传输,Parameter中含有 data 结构
主要用于发起连接请求
主要用于连接确认
从三种 PDU 协议功能码的出现频率来看,0x0d 和0x0e出现 flag 的概率最大,因此来分析这两种功能码:
注意到:
每一次发起请求连接对应着一次请求确认,在前三个协议包中,连续三次发起了请求,可能存在问题,查看发现:
在第三个包中,发现有意义的内容:NESSUS
又看了其他数据包的内容,并未发现有意义内容,因此该字符串即为 flag
没有给什么提示,就是单纯的协议分析题
打开文件发现:
非正常的协议包,对比正常协议包发现是文件头尾被篡改,因此使用010 Editor编辑修改文件头和文件尾:
正常 pcap 包
下载文件的 pcap 包:
删除文件的头三个尾三个字节,修改后打开:
因为协议很多,其他协议内容不是本文的重点,这里只对 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)
发现只存在功能码为 3 的协议包,共计 274 条。
上文中也提到了,modbus 协议以1、2、3、4、5、6、15、16
等八种最为常用,
下表为 modbus 的功能码简单说明:
功能码为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)
可以得到四行字符串:
9e232ab9e0c2e17359d7ad7adea0a30718e
352fc6113fdba326d49877c7383454e7
aebfba4523cdf03ddfe8ef8df620ff50
a3deb9ee27257633f59057c5654dffe6
其中后面3个字符串可以直接破解md5,为_love_this_game!
第一个发现多出来三个字符,观察发现在第 19 位置到第 24 位置,存在重复,删除多余的d7a
,解密md5 可得pcl_i
最终得到 flag 为pcl_i_love_this_game!
这题刚开始很久没人做出来,后来官方给出了 tips:
根据提示,我们首先要将进入系统维护模式,测试发现可改的线圈只有 15,因此需要调整 15 线圈,将其写入 Bool 型数据 true,然后调整工作模式,由11 切换到 3 ,再对控制器进行操作,在地址16写入值 为 100(其他也行,但是要大于 64),最后将系统的工作模式复原,先将线圈 11 改为 0,然后把控制器 15 改成 false 即可。
最后可以从4096[+10]得到ascii,如下图:
转换后得到flag: 06E6B72D
取得报警时间后,该时间值即为任务完成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时间戳:
提交flag: 2019-5-19 2:50:53
其实还有第三题工控系统操作,但是由于到最后我们没时间计算校验码,因此没有拿到 flag,这里也就不说了
S7协议没有公开的官方文档,在S7协议方面也没有官方术语,我了解的也不算深入,只是通过网上一些分享结合自己理解写成的一篇入门文章,如有错误,还望指出,另外,工控安全其实很吃工控设备,如果能有实际/仿真/模拟设备,对于工控安全的学习很有帮助。此文仅作工控安全入门参考文章,希望对你有所帮助。
西门子通信协议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/
错误码 | 含义 |
---|---|
0x0000 | 没有错误 |
0x0110 | 块号无效 |
0x0111 | 请求长度无效 |
0x0112 | 参数无效 |
0x0113 | 块类型无效 |
0x0114 | 找不到块 |
0x0115 | 块已存在 |
0x0116 | 块被写保护 |
0x0117 | 块/操作系统更新太大 |
0x0118 | 块号无效 |
0x0119 | 输入的密码不正确 |
0x011A | PG资源错误 |
0x011B | PLC资源错误 |
0x011C | 协议错误 |
0x011D | 块太多(与模块相关的限制) |
0x011E | 不再与数据库建立连接,或者S7DOS句柄无效 |
0x011F | 结果缓冲区太小 |
0x0120 | 块结束列表 |
0x0140 | 可用内存不足 |
0x0141 | 由于缺少资源,无法处理作业 |
0x8001 | 当块处于当前状态时,无法执行请求的服务 |
0x8003 | S7协议错误:传输块时发生错误 |
0x8100 | 应用程序,一般错误:远程模块未知的服务 |
0x8104 | 未在模块上实现此服务或报告了帧错误 |
0x8204 | 对象的类型规范不一致 |
0x8205 | 复制的块已存在且未链接 |
0x8301 | 模块上的内存空间或工作内存不足,或者指定的存储介质不可访问 |
0x8302 | 可用资源太少或处理器资源不可用 |
0x8304 | 无法进一步并行上传。存在资源瓶颈 |
0x8305 | 功能不可用 |
0x8306 | 工作内存不足(用于复制,链接,加载AWP) |
0x8307 | 保持性工作记忆不够(用于复制,链接,加载AWP) |
0x8401 | S7协议错误:无效的服务序列(例如,加载或上载块) |
0x8402 | 由于寻址对象的状态,服务无法执行 |
0x8404 | S7协议:无法执行该功能 |
0x8405 | 远程块处于DISABLE状态(CFB)。该功能无法执行 |
0x8500 | S7协议错误:帧错误 |
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 | 目前无法运行,因为正在运行的函数会修改内存 |
0xD0A3 | I / 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 | 函数参数中的语法错误 |
0xD205 | RAM中已存在链接块:无法进行条件复制 |
0xD206 | EPROM中已存在链接块:无法进行条件复制 |
0xD208 | 超出模块的最大复制(未链接)块数 |
0xD209 | (至少)模块上找不到给定块之一 |
0xD20A | 超出了可以与一个作业链接的最大块数 |
0xD20B | 超出了一个作业可以删除的最大块数 |
0xD20C | OB无法复制,因为关联的优先级不存在 |
0xD20D | SDB无法解释(例如,未知数) |
0xD20E | 没有(进一步)阻止可用 |
0xD20F | 超出模块特定的最大块大小 |
0xD210 | 块号无效 |
0xD212 | 标头属性不正确(与运行时相关) |
0xD213 | SDB太多。请注意对正在使用的模块的限制 |
0xD216 | 无效的用户程序 - 重置模块 |
0xD217 | 不允许在模块属性中指定的保护级别 |
0xD218 | 属性不正确(主动/被动) |
0xD219 | 块长度不正确(例如,第一部分或整个块的长度不正确) |
0xD21A | 本地数据长度不正确或写保护错误 |
0xD21B | 模块无法压缩或压缩早期中断 |
0xD21D | 传输的动态项目数据量是非法的 |
0xD21E | 无法为模块(例如FM,CP)分配参数。系统数据无法链接 |
0xD220 | 编程语言无效。请注意对正在使用的模块的限制 |
0xD221 | 连接或路由的系统数据无效 |
0xD222 | 全局数据定义的系统数据包含无效参数 |
0xD223 | 通信功能块的实例数据块错误或超出最大背景数据块数 |
0xD224 | SCAN系统数据块包含无效参数 |
0xD225 | DP系统数据块包含无效参数 |
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 | 更新已中止 |
0xD409 | DP总线错误 |
0xD601 | 函数参数中的语法错误 |
0xD602 | 输入的密码不正确 |
0xD603 | 连接已合法化 |
0xD604 | 已启用连接 |
0xD605 | 由于密码不存在,因此无法进行合法化 |
0xD801 | 至少有一个标记地址无效 |
0xD802 | 指定的作业不存在 |
0xD803 | 非法的工作状态 |
0xD804 | 非法循环时间(非法时基或多个) |
0xD805 | 不能再设置循环读取作业 |
0xD806 | 引用的作业处于无法执行请求的功能的状态 |
0xD807 | 功能因过载而中止,这意味着执行读取周期所需的时间比设置的扫描周期时间长 |
0xDC01 | 日期和/或时间无效 |
0xE201 | CPU已经是主设备 |
0xE202 | 由于闪存模块中的用户程序不同,无法进行连接和更新 |
0xE203 | 由于固件不同,无法连接和更新 |
0xE204 | 由于内存配置不同,无法连接和更新 |
0xE205 | 由于同步错误导致连接/更新中止 |
0xE206 | 由于协调违规而拒绝连接/更新 |
0xEF01 | S7协议错误:ID2错误; 工作中只允许00H |
0xEF02 | S7协议错误:ID2错误; 资源集不存在 |
服务名称 | 值(描述)
| :----- | :-----|
| UNKNOWN | PI-Service目前不详
| _INSE | PI-Service _INSE(激活PLC模块)
| _DELE | PI-Service _DELE(从PLC的被动文件系统中删除模块)
| P_PROGRAM | PI-Service P_PROGRAM(PLC启动/停止)
| _MODU | PI-Service _MODU(PLC Copy Ram to Rom)
| _GARB | PI-Service _GARB(压缩PLC内存)
| _NLOGIN | PI-Service _NLOGIN(登录)
| _N_LOGOUT | PI-Service _N_LOGOUT(退出)
| _N_CANCEL | PI-Service _N_CANCEL(取消NC报警)
| _N_DASAVE | PI-Service _N_DASAVE(用于将数据从SRAM复制到FLASH的PI-Service)
| _N_DIGIOF P| I-Service _N_DIGIOF(关闭数字化)
| _N_DIGION | PI-Service _N_DIGION(打开数字化)
| _NDZERO | PI-Service _NDZERO(设置所有D nos。对于函数无效\“唯一D号。\”)
| _N_ENDEXT | PI-Service _N_ENDEXT()
| _N_F_OPER | PI-Service _N_F_OPER(以只读方式打开文件)
| _N_OST_OF | PI-Service _N_OST_OF(Overstore OFF)
| _N_OST_ON | PI-Service _N_OST_ON(Overstore ON)
| _NSCALE | PI-Service _NSCALE(测量单位设置(公制< - > INCH))
| _N_SETUFR | PI-Service _N_SETUFR(激活用户帧)
| _N_STRTLK | PI-Service _N_STRTLK(设置全局启动禁用)
| _N_STRTUL | PI-Service _N_STRTUL(重置全局启动禁用)
| _N_TMRASS | PI-Service _N_TMRASS(重置活动状态)
| _N_F_DELE | PI-Service _N_F_DELE(删除文件)
| _N_EXTERN | PI-Service _N_EXTERN(选择外部程序执行)
| _N_EXTMOD | PI-Service _N_EXTMOD(选择外部程序执行)
| _N_F_DELR | PI-Service _N_F_DELR(即使没有访问权限也删除文件)
| _N_F_XFER | PI-Service _N_F_XFER(选择要上传的文件)
| _NLOCKE | PI-Service _NLOCKE(锁定活动文件以进行编辑)
| _N_SELECT| PI-Service _N_SELECT(选择要执行的程序)
| _N_SRTEXT | PI-Service _N_SRTEXT(文件正在/ _N_EXT_DIR中标记)
| _N_F_CLOS | PI-Service _N_F_CLOS(关闭文件)
| _N_F_OPEN| PI-Service _N_F_OPEN(打开文件)
| _N_F_SEEK| PI-Service _N_F_SEEK(定位文件搜索指针)
| _N_ASUP | PI-Service _N_ASUP (分配中断)
| _N_CHEKDM | PI-Service _N_CHEKDM(对D号码启动唯一性检查)
| _N_CHKDNO | PI-Service _N_CHKDNO(检查工具是否具有唯一的D编号)
| _N_CONFIG | PI-Service _N_CONFIG(重新配置机器数据)
| _N_CRCEDN | PI-Service _N_CRCEDN(通过指定边数来创建切削刃)
| _N_DELECE | PI-Service _N_DELECE(删除最前沿)
| _N_CREACE | PI-Service _N_CREACE(创造最前沿)
| _N_CREATO| PI-Service _N_CREATO(创建工具)
| _N_DELETO| PI-Service _N_DELETO(删除工具)
| _N_CRTOCE| PI-Service _N_CRTOCE(生成具有指定边数的工具)
| _N_DELVAR | PI-Service _N_DELVAR(删除数据块)
| _N_F_COPY| PI-Service _N_F_COPY(复制NCK中的文件)
| _N_F_DMDA | PI-Service _N_F_DMDA(删除MDA内存)
| _N_F_PROT | PI-Service _N_F_PROT(为文件指定保护级别)
| _N_F_RENA | PI-Service _N_F_RENA(重命名文件)
| _N_FINDBL | PI-Service _N_FINDBL(激活搜索)
| _N_IBN_SS | PI-Service _N_IBN_SS(设置设置开关)
| _N_MMCSEM | PI-Service _N_MMCSEM(MMC-Semaphore)
| _N_NCKMOD | PI-Service _N_NCKMOD(正在设置NCK工作的模式)
| _N_NEWPWD | PI-Service _N_NEWPWD(新密码)
| _N_SEL_BL | PI-Service _N_SEL_BL(选择新块)
| _N_SETTST | PI-Service _N_SETTST(激活替换工具组的工具)
| _N_TMAWCO | PI-Service _N_TMAWCO(在一个杂志中设置有效磨损组)
| _N_TMCRTC| PI-Service _N_TMCRTC(创建具有指定边数的工具)
| _N_TMCRTO | PI-Service _N_TMCRTO(在工具管理中创建工具)
| _N_TMFDPL | PI-Service _N_TMFDPL(搜索空白处加载)
| _N_TMFPBP | PI-Service _N_TMFPBP(搜索空位)
| _N_TMGETT | PI-Service _N_TMGETT(使用Duplono确定特定工具ID的T编号)
| _N_TMMVTL| PI-Service _N_TMMVTL(加载或卸载工具)
| _N_TMPCIT| PI-Service _N_TMPCIT(设置计件器的增量值)
| _N_TMPOSM | PI-Service _N_TMPOSM(定位杂志或工具)
| _N_TRESMO | PI-Service _N_TRESMO(重置监控值)
| _N_TSEARC| PI-Service _N_TSEARC(通过搜索屏幕进行复杂搜索)
缩写|含义
| :----- | :-----|
|ADU | 应用数据单元
| HDLC | 高级数据链路控制HMI人机界面
| IETF | 互联网工程任务组
| I / O | 输入/输出
| IP | 互联网协议
| MAC | 媒体访问控制
| MB | MODBUS协议
| MBAP | MODBUS应用协议
| PDU | 协议数据单元
| PLC | 可编程逻辑控制器
| TCP | 传输控制协议
工控数据包:
链接:https://pan.baidu.com/s/1lkr4bsoCJTACzVwzgHcgdQ
密码:tp1u
modbus 资料:
见附件