S7comm 协议
概述
S7comm 是西门子专有的工业控制协议,主要用于西门子S7-300/400系列PLC(可编程逻辑控制器)的编程、数据交换和诊断
报文结构
一个典型的 S7comm 报文由三部分组成:
Header (头部):固定 12 字节,包含协议标识、PDU 类型等关键信息。
Parameter (参数):长度可变,包含具体的功能码(如读取、写入)及其参数
Data (数据):长度可变,在写入等操作中携带实际数据
通信过程
Wireshark 可以清晰捕捉到 S7 通信的建立过程,分为三步:
TCP 三次握手:客户端通过 TCP 端口 102 与 PLC 建立连接
COTP 层连接:双方交换 COTP 连接请求和确认报文,协商通信参数
S7comm 层会话建立:客户端发送功能码为
0xf0的 “建立通信” 请求,双方协商 S7comm 层参数,完成连接
核心功能码
| 功能码 | 描述 | 说明 |
|---|---|---|
| 0x00 | CPU 服务 | 用于执行与 CPU 本身相关的系统级功能 |
| 0xF0 | 建立通信 | 在数据传输前,用于协商通信参数,如 PDU 大小 |
| 0x04 | 读取变量 | 请求读取 PLC 中一个或多个变量的值 |
| 0x05 | 写入变量 | 请求向 PLC 写入一个或多个变量的值 |
| 0x1A | 请求下载 | 开始下载程序块或数据块前的请求 |
| 0x1B | 下载块 | 传输程序块或数据块的实际数据内容 |
| 0x1C | 下载结束 | 通知 PLC 下载过程已全部完成 |
| 0x1D | 开始上传 | 开始从 PLC 上传程序块或数据块 |
| 0x1E | 上传 | 传输从 PLC 上传的数据内容 |
| 0x1F | 上传结束 | 通知上位机上传过程已全部完成 |
| 0x28 | PLC 控制 | 用于远程控制 PLC 的运行模式,例如启动(Start) |
| 0x29 | 停止 PLC | 远程停止 PLC 的运行 |
实战案例
S7 协议恶意攻击分析(S7comm 协议)

S7comm(S7 Communication)是西门子专有的工业控制协议,主要功能码如下:
| 功能码 | 描述 | 说明 |
|---|---|---|
| 0x00 | CPU 服务 | 用于执行与 CPU 本身相关的系统级功能 |
| 0xF0 | 建立通信 | 在数据传输前,用于协商通信参数,如 PDU 大小 |
| 0x04 | 读取变量 | 请求读取 PLC 中一个或多个变量的值 |
| 0x05 | 写入变量 | 请求向 PLC 写入一个或多个变量的值 |
| 0x1A | 请求下载 | 开始下载程序块或数据块前的请求 |
| 0x1B | 下载块 | 传输程序块或数据块的实际数据内容 |
| 0x1C | 下载结束 | 通知 PLC 下载过程已全部完成 |
| 0x1D | 开始上传 | 开始从 PLC 上传程序块或数据块 |
| 0x1E | 上传 | 传输从 PLC 上传的数据内容 |
| 0x1F | 上传结束 | 通知上位机上传过程已全部完成 |
| 0x28 | PLC 控制 | 用于远程控制 PLC 的运行模式,例如启动(Start) |
| 0x29 | 停止 PLC | 远程停止 PLC 的运行 |
题目说了是 “突然发生停机事件”,所以推测是 0x29 或者 0x28,直接过滤出来
P_PROGRAM 是实现 PLC 模式转换的关键服务。它主要包含两种操作:
启动:将 PLC 从停止状态切换至运行状态
停止:将 PLC 从运行状态切换至停止状态

本题的 flag 是 flag{3201414d}
上位机通讯异常分析(S7comm 协议)

题目说 “无法查询控制设备异常情况”
观察数据包发现功能码都是 0x04,所以判断是因为响应不正常才导致的 “无法查询”

我们随便一个响应来分析一下结构

📦 1. Header (头部) — 确认响应类型
| 字段 | 值 | 说明 |
|---|---|---|
| Protocol Id | 0x32 | S7 协议固定标识,表明这是 S7comm 报文 |
| ROSCTR | Ack_Data (3) | 表示这是一个数据确认包,即对请求的响应并携带数据 |
| PDU Reference | 52417 | 用于匹配请求与响应的流水号 |
| Parameter length | 2 | 参数部分的长度为 2 字节(仅包含 “Item count”) |
| Data length | 6 | 数据部分的长度为 6 字节(包含 1 个数据项的元信息+实际值) |
| Error class / code | 0x00 / 0x00 | 无错误,表示操作成功 |
📋 2. Parameter (参数) — 确认读取内容
| 字段 | 值 | 说明 |
|---|---|---|
| Function | Read Var (0x04) | 响应的是 “读取变量” 请求 |
| Item count | 1 | 数据部分包含 1个 数据项,与请求中的项数对应 |
💾 3. Data (数据) — 实际读取到的值
| 字段 | 值 | 说明 |
|---|---|---|
| Return code | Success (0xff) | 该项读取成功 |
| Transport size | BYTE/WORD/DWORD (0x04) | 表示数据长度以字节为单位,且 “Length” 字段按字节计数 |
| Length | 2 | 数据长度为 2个字节 |
| Data | 01 00 | 实际读取到的原始值,按大端序存储 |
可以看到 Return code 的值决定是否读取成功,成功的话为 0xff
我们右键这个值,选择 “准备作为过滤器” –> “选中”

然后加上 ! 筛选出没有读取成功的流量包

其 data 内容就是 flag{010400100100}

工控协议数据分析(S7comm 协议)

题目说 “获取到了哪些信息”,过滤协议可以看到全部都是 0x04 和 0x05 的流量

过滤 s7comm.param.func == 0x05 可以看到多次写入,每个写入包的 data 字段里都有类似内容:
1 | 01100110 |

提取出来拼接转成 ASCII 码得到 `flag{flag_is_here}
异常的 S7 数据(S7comm 协议)
大概看了一下,全是 0x05 功能码

绝大多数写入数据都以 ffff 开头

右键作为过滤器应用

WireShark 生成的语法是 s7comm.resp.data == ff:ff:96:e7:8f:51:c4:d0:3a:2b
我们更改为 s7comm.resp.data[0:2] != ff:ff,筛选出数据包

所以 flag 是 flag{FFAD28A0CE69DB34751F}
黑客的 Fuzz(S7comm 协议)

打开流量包过滤 s7comm,全是写入和读取变量
题目说的是 FUZZ,所以重点应该看读取的流量,功能码为 0x04
FUZZ 的参数每次请求肯定都是不一样的
最后比对发现就 Address 字段的最后一个字节每次不一样

编写脚本提取
1 | import struct |
最后得到 flag:flag{50f84daf3a6dfd6a9f20c9f8ef428942}
被篡改的数据(S7comm 协议)

“被大量修改的数据” 对应的写入内容,也就是功能码 0x05
1 | import struct |
得到 flag{931377ad4a}