从Wireshark抓包到Modbus协议分析:实战解析工控流量中的隐藏数据

📅 2026/7/6 0:09:13 👁️ 阅读次数 📝 编程学习
从Wireshark抓包到Modbus协议分析:实战解析工控流量中的隐藏数据

1. 项目概述:一次从实战出发的工控协议分析之旅

最近在复盘一些网络安全竞赛的题目,其中一道来自CISCN2023的题目让我觉得特别有教学意义。它没有复杂的漏洞利用,也没有高深的逆向工程,而是聚焦在一个非常基础但又极其重要的领域:工控协议流量分析。题目以Modbus协议为载体,要求选手从Wireshark抓包文件中,通过过滤和协议解码,最终找到一个隐藏的Flag。这恰恰是很多刚接触工控安全或网络分析的新手最需要掌握的“硬功夫”。很多人一听到Modbus、Wireshark就觉得头大,觉得这是资深工程师的领域。其实不然,只要掌握了正确的分析思路和工具技巧,新手完全可以从零开始,一步步解开谜题。这道题就是一个完美的切入点,它串联起了Wireshark过滤语法Modbus协议结构解析Base32编码识别与解码这几个关键技能点。通过复现这道题的解题过程,我希望不仅能带你找到那个Flag,更能让你建立起一套处理类似网络协议分析问题的通用方法论。无论你是网络安全爱好者、工控领域的新人,还是对数据包分析感兴趣的学生,这篇内容都将为你提供一条清晰、可操作的路径。

2. 核心需求与工具准备:我们到底要解决什么问题?

在深入抓包文件之前,我们必须先明确目标。这道题的核心需求非常明确:从一个可能包含大量冗余流量的抓包文件(.pcapng格式)中,定位到与Modbus协议相关的特定数据包,并从中提取出经过编码的关键信息,最终解码获得Flag。这个过程可以分解为三个子任务:第一,从海量数据中快速筛选出目标协议(Modbus)的流量;第二,理解Modbus协议的数据单元结构,找到承载有效数据的部分;第三,识别数据的编码方式(本题为Base32)并进行正确解码。

工欲善其事,必先利其器。我们需要的工具很简单:

  1. Wireshark:网络协议分析的事实标准。请务必从官网下载最新稳定版,安装过程一路下一步即可。对于新手,我建议在安装时勾选所有组件,特别是Npcap(Windows下的抓包驱动),这是Wireshark能抓到网卡流量的基础。
  2. 文本编辑器或IDE:用于查看和整理解码前后的文本信息,如VS Code、Notepad++等。
  3. Python环境或在线解码工具:用于执行Base32解码。Python内置了base64模块,使用起来非常方便。如果不想写代码,也有很多可靠的在线Base32解码网站可供临时使用。

注意:在真实工控环境或竞赛中,拿到的抓包文件可能来自任何网络环境。第一步永远是在隔离的虚拟机或专用分析环境中打开文件,切勿在生产机或存有敏感信息的个人电脑上直接分析未知流量,这是最基本的安全操作规范。

3. 初探流量:使用Wireshark加载与初步观察

拿到抓包文件(假设名为ciscn2023_modbus.pcapng)后,用Wireshark打开。主界面通常分为三大部分:顶部的数据包列表、中部的协议分层详情、底部的原始数据字节流。

首先,我们不做任何过滤,直接浏览数据包列表。你可能会看到成千上万个数据包,协议类型五花八门,比如TCP、HTTP、TLS、ARP等等。我们的目标是Modbus,它是一种应用层协议,通常运行在TCP/502端口或作为串行链路协议(RTU/ASCII)。在工控网络尤其是模拟赛题环境中,Modbus over TCP(Modbus/TCP)更为常见。

此时,一个高效的技巧是查看Wireshark的“协议”列。如果该列没有显示,可以在列表栏右键点击,选择“列”,然后添加“Protocol”列。快速滚动,寻找显示为“MODBUS”的数据包。如果找到了,记下它的特征,比如目的端口是不是502。如果数据包太多看不清,我们就需要祭出Wireshark的核心功能之一:显示过滤器

4. Wireshark过滤语法精讲:精准定位目标数据

显示过滤器(Display Filter)是Wireshark的“搜索引擎”,它允许我们根据几乎任何条件筛选数据包,而不会修改原始文件。对于新手,掌握几个最常用的过滤语法足矣应对大部分场景。

4.1 基于协议的过滤这是最直接的过滤方式。在过滤器栏输入:

modbus

Wireshark会只显示协议栈中包含Modbus协议的数据包。如果过滤后没有任何显示,可能的原因有:1)确实没有Modbus流量;2)Modbus运行在非标准端口上。对于第二种情况,我们可以尝试基于端口过滤。

4.2 基于端口的过滤Modbus/TCP的官方指定端口是502。我们可以过滤TCP端口号:

tcp.port == 502

或者更精确地,过滤目标端口为502的流量(通常是客户端发给Modbus服务器的请求):

tcp.dstport == 502

也可以过滤源端口为502的流量(服务器响应):

tcp.srcport == 502

有时,题目可能会将Modbus服务部署在其他端口以增加隐蔽性。如果你用modbustcp.port == 502都过滤不出东西,一个办法是观察哪些TCP会话在频繁地进行“请求-响应”式通信(数据包长度规律),然后尝试过滤该端口。

4.3 基于数据包内容的过滤(进阶)Modbus协议数据包中,从TCP载荷开始,其结构是:事务标识符(2字节)+ 协议标识符(2字节,Modbus为0x0000)+ 长度字段(2字节)+ 单元标识符(1字节)+ 功能码(1字节)+ 数据。 如果我们想筛选执行了特定操作的数据包,比如“写单个寄存器”(功能码06)或“读保持寄存器”(功能码03),可以使用modbus.func_code过滤器。

modbus.func_code == 0x03

在本题的上下文中,Flag信息很可能被写入或读出寄存器,因此关注读写寄存器的功能码(03, 06, 10, 16等)是关键。

4.4 组合过滤过滤器支持逻辑运算符,如and(与)、or(或)、not(非)。例如,我们想查看所有发往Modbus服务器(端口502)的“写寄存器”请求:

tcp.dstport == 502 and modbus.func_code == 0x06

掌握这些过滤语法,你就能像使用搜索引擎一样,在数据包的海洋中快速定位到感兴趣的“岛屿”。在本题中,我们首先尝试modbustcp.port == 502,应该能立刻将数据包数量从数万减少到几十或几百个,大大降低了分析难度。

5. Modbus协议关键字段解析:数据藏在哪里?

过滤出Modbus数据包后,我们需要读懂它。点击一个Modbus数据包,在中部详情窗格中展开“Modbus”协议层。你会看到类似下面的结构:

  • Transaction Identifier: 事务ID,用于匹配请求和响应。
  • Protocol Identifier: 协议ID,Modbus为0。
  • Length: 后续字节的长度。
  • Unit Identifier: 从站地址(Slave ID)。
  • Function Code: 功能码,这是核心,定义了操作类型(如 03: 读保持寄存器, 06: 写单个寄存器, 10: 写多个寄存器)。
  • Data: 具体的数据内容,其格式由功能码决定。

对于这道题,Flag很可能隐藏在Data字段中。但Flag通常是一串可读的字符串,而Modbus寄存器存储的是16位(2字节)无符号整数。那么字符串如何存储呢?常见的方式有两种:

  1. ASCII码直接存储:每个寄存器存两个ASCII字符(高字节和低字节)。例如,字符串“AB”的ASCII码是0x41和0x42,可能存储在一个寄存器值为0x4142。
  2. 编码后存储:先将Flag用Base32、Base64等编码成纯ASCII或十六进制字符串,再将编码后的每个字符(或字节)存入寄存器。

如何判断?我们需要查看Data部分的原始字节。在Wireshark底部“数据包字节”窗格,选中Modbus数据部分对应的十六进制字节,观察其内容。如果看到像66 6c 61 67 7b(对应ASCII “flag{”)这样的规律字节,可能是直接存储。如果看到一串由A-Z、2-7、=组成的字符(Base32特征),或者A-Z、a-z、0-9、+、/组成的字符(Base64特征),那就是编码后存储了。

在本题中,根据题目提示和常见出题思路,我们需要关注写寄存器(Function Code 06 或 16)的请求包,因为出题人可能模拟了向寄存器写入Flag编码数据的过程。或者关注读寄存器(Function Code 03)的响应包,因为可能模拟了从寄存器读取Flag的过程。我们需要在过滤后的数据包中,逐个检查这些数据包的Data字段。

6. 实战演练:从过滤到发现编码数据

假设我们使用modbus过滤器后,得到了50个数据包。通过观察功能码,我们发现其中有一系列连续的“写多个寄存器”(Function Code 16, 十进制22)请求。这非常可疑,因为写入大量数据正是隐藏信息的常用手段。

我们选中其中一个功能码为16的请求包,在详情面板中找到Data字段。在“数据包字节”窗格,对应数据部分的十六进制显示可能如下(示例):

00 01 00 10 4e 47 4a 58 65 4f 4e 42 47 6c 59 57 35 30 49 47 46 75 5a 47 55 67 55 32 39 75 64 57 31 6c 49 47 35 68 62 57 55 67 55 33 52 76 64 47 46 73 5a 41 3d 3d

这看起来不像直接的ASCII文本。我们将其转换为ASCII字符看看(可以在Wireshark中右键点击十六进制部分,选择“复制”->“...作为Hex Stream”,然后借助外部工具转换,或用Python脚本)。转换后发现,它是一串像NGJXeONBGlYW50IGFuZGUgU29tZSBWYXJpYWJsZVN0cmluZz==的字符串。

仔细观察这串字符:

  • 字符集范围主要是大写字母(A-Z)和数字(2-7),末尾有等号=
  • 这是Base32编码的典型特征!Base32编码表使用A-Z和2-7共32个字符,编码后的数据长度通常是8的倍数,不足部分用=填充。

至此,我们完成了关键一步:通过Wireshark过滤定位到可疑的Modbus写数据包,并从其数据载荷中识别出一段Base32编码的字符串

7. Base32解码原理与实操:还原隐藏信息

Base32是一种用32个可打印字符(A-Z, 2-7)表示二进制数据的编码方式。每5个字节(40位)的二进制数据被分成8组5位,每组5位(值范围0-31)映射到一个编码表字符。解码是其逆过程。

我们不需要手动计算,使用工具即可。最推荐使用Python,因为它精准且可离线操作。

7.1 使用Python解码打开Python解释器或创建一个.py文件。

import base64 # 这是我们从Wireshark中提取的Base32编码字符串(示例) encoded_str = "NGJXeONBGlYW50IGFuZGUgU29tZSBWYXJpYWJsZVN0cmluZz==" # Base32解码 try: # base64.b32decode 会自动处理末尾的等号填充 decoded_bytes = base64.b32decode(encoded_str) # 将解码后的字节转换为字符串(假设是UTF-8文本) decoded_str = decoded_bytes.decode('utf-8') print("解码后的字符串:", decoded_str) except Exception as e: print("解码出错:", e)

执行这段代码,输出很可能就是我们要找的Flag,格式可能为flag{...}FLAG{...}

7.2 解码过程中的注意事项

  1. 字符集问题:Base32标准编码表是大写A-Z和数字2-7。但有些变种(如Crockford‘s Base32)会使用不同的映射或允许小写。Wireshark中提取的字符串通常是大写标准格式。如果解码失败,首先检查字符串是否混入小写字母、数字0/1/8/9或特殊字符。必要时先进行upper()转换并剔除非法字符。
  2. 填充等号==是填充字符,用于确保编码字符串长度为8的倍数。Python的base64.b32decode函数能自动处理填充。但如果手动去除填充,解码时需要确保数据长度正确。
  3. 提取完整性:一个Flag可能被分割到多个Modbus数据包中。我们需要按照数据包的顺序(参考Wireshark的No.序号或Modbus事务ID、寄存器地址)将所有相关数据部分的Base32字符串拼接起来,再进行整体解码。在本题中,我们之前发现了一系列连续的“写多个寄存器”请求,很可能每个请求的数据部分都包含一段Base32字符串,需要按顺序拼接。

8. 完整解题流程复盘与技巧总结

让我们从头到尾梳理一遍这道题的理想解题路径:

  1. 打开文件,宏观观察:用Wireshark打开pcapng文件,查看协议统计(Statistics->Protocol Hierarchy)快速了解协议分布,确认Modbus协议存在。
  2. 应用过滤,缩小范围:在显示过滤器栏输入modbustcp.port == 502,聚焦目标流量。
  3. 分析协议,定位数据:在过滤后的数据包列表中,根据Function Code排序或浏览,重点关注功能码为06(写单个寄存器)、16(写多个寄存器)或03(读保持寄存器)的数据包。查看这些包的Data字段详情。
  4. 识别编码,提取字符串:在数据包字节流中,找到Modbus数据部分对应的十六进制,将其转换为ASCII字符串。观察字符串是否具有Base32特征(A-Z, 2-7, =)。将可疑的字符串完整复制出来。如果数据分散在多个包,按顺序拼接。
  5. 执行解码,获取Flag:使用Python脚本或可靠的在线工具,对提取出的Base32字符串进行解码。输出结果即为Flag。

8.1 避坑技巧与心得

  • 技巧一:善用Wireshark的“追踪流”功能。在某个Modbus TCP数据包上右键,选择“追踪流” -> “TCP流”,Wireshark会重组这个TCP会话的所有数据,并以ASCII和十六进制对照的形式展示。这能让你更清晰地看到完整的请求-响应对话,更容易发现跨多个数据包的完整编码字符串。
  • 技巧二:使用“导出分组字节流”。当你确定了一段包含编码数据的TCP载荷范围(在字节流视图中选中),可以右键选择“导出分组字节流”,将其保存为二进制文件。然后用文本编辑器打开,或者用Python以二进制模式读取后再进行解码操作,避免复制粘贴过程中引入错误。
  • 技巧三:注意字节序(Endianness)。Modbus协议本身使用大端序(Big-Endian,即高位在前)。但有些设备或出题人可能会在小端序系统上生成数据。如果你发现解码后的字符串是乱码,但看起来像是字节顺序错了(比如“flag”变成了“galf”),可以尝试将原始字节对调后再进行解码。在Python中,可以用bytes(reversed(decoded_bytes))进行简单的字节反转尝试。
  • 心得:工控协议分析,耐心和细心往往比掌握高深技巧更重要。一道题目的突破口,常常就藏在某个数据包某个字段的细微异常里。养成记录和假设的习惯,比如“这个寄存器地址为什么跳变了?”、“这个数据长度为什么不符合常规?”,并动手去验证,是快速提升分析能力的不二法门。

9. 举一反三:Beyond the CTF – 真实场景中的Modbus分析

CTF题目简化了场景,但技能是相通的。在真实的工业控制系统安全评估或故障诊断中,Modbus流量分析同样至关重要。

9.1 安全审计中的应用

  • 异常指令检测:过滤并检查所有非标准的Modbus功能码。标准功能码范围是1-127,其中公共代码是1-64。如果出现超出范围或生僻的功能码,可能意味着自定义功能或恶意指令。
  • 寄存器范围审计:监控对关键控制寄存器(例如,控制电机启停的线圈、设置温度阈值的保持寄存器)的写操作(功能码05, 06, 15, 16)。在正常工况下,这些操作应有固定的模式或仅来自特定的主站(Master)。异常的写操作可能是攻击迹象。
  • 扫描与爆破识别:攻击者可能对从站地址(Unit ID)或功能码进行扫描。在Wireshark中,可以构造过滤器如modbus && !(tcp.srcport == 502)来查找所有非Modbus服务器端发出的Modbus请求(即攻击者发起的扫描流量),并观察其目标Unit ID和功能码的分布。

9.2 故障排查中的应用

  • 通信超时与重传:通过tcp.analysis.retransmissiontcp.analysis.ack_rtt过滤器,可以查看是否存在TCP重传或高延迟的ACK,这可能是网络拥堵或设备响应慢的表现。
  • 协议解析错误:Wireshark的Modbus解析器非常成熟。如果某个数据包被标记为“Malformed Packet”(畸形包),或者协议树无法正确展开,很可能数据包本身不符合Modbus协议规范,可能是底层传输错误、设备固件bug或恶意篡改的结果。
  • 数据一致性检查:对于读响应,检查返回的字节数是否与请求中要求的寄存器数量匹配。例如,请求读10个保持寄存器(20字节),响应数据长度字段应为23(2事务ID+2协议ID+2长度+1单元ID+1功能码+1字节计数+20数据),如果不对,则通信异常。

掌握从CTF中练就的这套“过滤-定位-解析-解码”组合拳,你就能在面对真实的、噪音更大的工控网络流量时,保持清晰的思路,快速定位到问题的核心。这不仅仅是解一道题,更是培养一种解决实际问题的思维能力。