这几年做工业控制系统相关的安全事件调查,我几乎每次都要和Modbus协议打交道。不管是能源行业的老旧PLC,还是产线上的HMI、伺服驱动器,Modbus几乎是默认配置。它足够简单,也足够古老,但就是这份“朴素”,让它在取证分析时既让人省心又让人头疼——省心的是包结构一目了然,头疼的是很多现场根本没有完整的日志,设备里的历史数据也不够细。这篇文章是我自己整理的一份学习笔记,核心围绕Modbus协议基础、流量取证、内存取证以及主机痕迹排查来展开,适合做安全事件处置、数字取证,或者只是需要对工控现场做巡检排查的人参考。
1. Modbus协议基础:从PLC到传感器都绕不开的“通用话术”
1.1 为什么Modbus能在工控场景里活这么多年
Modbus是Modicon公司在1979年提出的通信协议,最初就是为了让PLC之间、PLC和外部设备之间能以最简方式交换数据。它的核心设计思路特别直白:一个主站发起请求,一个或多个从站响应请求,一问一答,几乎没有额外的握手和状态机。正是这种“没有花活”的设计,让它在工业现场拥有极强的兼容性和可移植性,从锅炉控制到水处理,再到生产线上的伺服电机调整,到处都能看到它的身影。
从取证的角度看,协议简单反而是一个优势。只要现场保留了一份抓包,或者设备崩溃后留下了内存镜像,就能从里面还原出大量通信过程。没有加密、没有认证是Modbus根子上的问题,这也意味着通信内容是“裸奔”的,任何人拿到流量都能看懂请求了哪些寄存器、写了什么数据。但同样因为“裸奔”,我们在取证时必须格外小心,不能默认所有流量都可信,要结合设备台账和业务逻辑来判断哪些指令是合理的,哪些明显越界。
另外需要提醒一点,很多老设备即便运行了十年二十年,固件从不更新,却依然承担着关键生产工艺控制。遇到这类环境,取证前一定要先和生产运维确认设备名单,别因为抓包、断线或者误操作影响了正常生产。这个原则我在后面的实操部分还会反复强调。
1.2 Modbus RTU与Modbus TCP:串口时代和以太网时代的分野
Modbus家族里最常见的是两种形态:Modbus RTU和Modbus TCP。很多新人会混淆,其实它们的底层链路完全不同。
Modbus RTU跑在RS-232、RS-485这类串行链路上,常见于伺服电机控制、老旧仪表采集等场景。它的帧结构是:从站地址+功能码+数据+CRC校验。因为串口是共享总线,同一时刻只能有一个主站发起通信,所以通信节奏相对固定,调试时也经常能看到一个轮询程序反复读取一组寄存器。RS-485总线支持多从站,用地址区分设备。
Modbus TCP则跑在以太网上,默认端口是502,使用TCP作为传输层。它在RTU格式之上加了一个MBAP头,总共7个字节,包含事务标识符、协议标识符、长度和单元标识符。相比RTU,它不需要CRC校验,因为TCP本身保障了传输可靠性。Modbus TCP的最大优点是便于组网和远程监控,但它也把工控网络直接暴露在TCP/IP协议栈之上,取证时信息维度更加丰富:能看到IP地址、端口、TCP会话状态。
实操中,遇到RTU场景时,普通网络抓包工具往往派不上用场,需要串口抓包工具或者在上位机侧记录通信日志;遇到TCP场景时,交换机镜像口一接,Wireshark一开,就能拿到很完整的会话数据。
1.3 功能码、寄存器与数据模型:取证必须记住的“地址族谱”
Modbus协议最核心的模型是四类数据对象:线圈、离散输入、输入寄存器、保持寄存器。它们分别对应不同的功能码和地址范围,取证时如果你不熟悉这层映射关系,很容易把报文含义理解错。
| 数据对象 | 地址范围(PLC侧) | 读写特性 | 常见功能码 |
|---|---|---|---|
| 线圈 | 00001-09999 | 可读可写 | 01读线圈,05写单线圈,15写多线圈 |
| 离散输入 | 10001-19999 | 只读 | 02读离散输入 |
| 输入寄存器 | 30001-39999 | 只读 | 04读输入寄存器 |
| 保持寄存器 | 40001-49999 | 可读可写 | 03读保持寄存器,06写单寄存器,16写多寄存器 |
拿最常见的“读保持寄存器”举例,功能码是03,报文中会带上起始地址和寄存器数量。在Wireshark里,你看到请求报文里的字段是modbus.func_code == 3,数据部分包含起始地址和数量;响应报文里会先返回字节数,再返回实际的寄存器值。很多工程师习惯把PLC侧的地址直接写成40001,但协议报文里的地址其实是从0x0000开始的偏移量,也就是40001对应的偏移地址是0x0000。这个映射关系排查时经常踩坑,后面我会专门讲。
从安全事件分析的角度,我一般会重点盯几类功能码:05写单线圈、06写单寄存器、15写多线圈、16写多寄存器。这些功能码会改变设备输出状态或工艺参数,一旦异常出现,影响可能直接推送到物理设备上。比如一个自动灌装系统,如果保持寄存器里存储灌装量设定值,突然出现来自非预期主机的06功能码写操作,那就要立刻当安全事故来调查。
2. 从流量层面盯住Modbus通信:抓包、解析与异常模式
2.1 先分清正常与异常:一次完整的读写长什么样
我自己判断Modbus流量异常前,一定会先花时间看正常基线。所谓基线,就是某个设备在正常运行时的通信特征:哪个主站IP在轮询哪几个从站,用了哪些功能码,轮询间隔大概多少,读数范围是多少。没有基线,就没法判断“突然出现的大量写操作”是不是问题。
一个正常的Modbus TCP读取请求,MBAP头里的事务标识符每次递增,协议标识符固定为0,请求PDU里是功能码和数据字段。比如读取保持寄存器03,请求字段是:起始地址、寄存器数量;响应字段是:字节数、N个寄存器数据。整个过程非常规律,间隔相对稳定,而且大多数现场是周期性轮询。
我在分析时会把流量按会话聚合,统计每个IP对的功能码分布。正常情况通常是读功能码占绝大多数,写功能码很少。如果一个主站IP过去一周都没写过任何寄存器,突然在某天凌晨执行了大量05、06、15、16写操作,这就足够让人警觉了。还有一类情况,请求报文里访问了该从站根本不存在的寄存器地址,导致从站返回异常响应码02(非法数据地址)或03(非法数据值),这也说明主站配置有问题,或者通信行为不合规。
2.2 Wireshark和tshark提取Modbus通信关键信息
流量取证最常用的工具就是Wireshark,它内置了完整的Modbus解析器,可以直接识别Modbus TCP报文里的功能码、寄存器地址、数据内容。图形界面适合交互分析,但如果你想快速从大量pcap里批量提取字段,我建议直接用tshark,效率高得多。
tshark -r scene.pcapng -Y "modbus" -T fields \ -e frame.time \ -e ip.src \ -e ip.dst \ -e tcp.srcport \ -e tcp.dstport \ -e modbus.func_code \ -e modbus.reginfo \ -e modbus.data这条命令会把每个Modbus报文的时间、源目IP、端口、功能码、寄存器信息和数据字段打成一列,方便导入Excel或抽样统计。你也可以用tcp.port == 502做初步过滤,再叠加modbus.func_code == 6来看写单寄存器的操作记录。
分析时别忘了设置好Wireshark的偏好,把Modbus TCP解析的端口默认指向502。如果现场工控机组用了非标准端口,需要在解码设置里手动指定。还有一个老生常谈的细节:抓包位置决定了你能看到多少东西。能接交换机镜像口最好;如果现场没有镜像口,可以考虑在网关设备侧做端口映射,但绝对不要在业务链路上擅自串联设备,搞不好会断网或者丢包,影响生产。
2.3 哪些流量特征提示“出事”了
结合我自己处理过的案例,下面这几种流量模式出现时,基本可以判定通信环境异常:
- 从未出现过的写操作。比如一台只读数据的采集网关,路由日志显示它在凌晨对PLC发送了大量06写单寄存器请求,这属于典型异常。
- 请求频率骤增。比如从原来每10秒轮询一次,变成每秒几十次,这种扫描式遍历行为通常是攻击者或恶意脚本在探测寄存器点位。
- 异常功能码组合。比如在一个从未使用过15功能码的产线上,突然看到15写多线圈,需要第一时间关联工艺状态确认是否有现场操作。
- 非预期来源。办公网段IP、陌生厂区IP、甚至外部IP直接连接PLC的502端口,超过正常白名单范围。
- error response/异常响应码陡增。说明主站试图访问不存在的地址或写入非法值,可能是恶意探测,也可能是某台上位机配置错乱。
流量取证的产出物不只是几张截图,我建议把关键包导出成CSV或生成哈希值,并记录保留现场pcap的时间范围。之后无论做事件报告还是提交司法鉴定,这些留痕都很重要。
3. 内存取证与主机痕迹排查:把Modbus通信“拼”回现场
3.1 内存取证能拿到什么:进程、网络连接、动态加载的模块
流量只是证据链中的一环。很多情况下,攻击脚本或不合规工具运行完就退出,文件也删了,只有内存里还可能保留蛛丝马迹。内存取证的核心目的,就是把当时运行在主机上的进程、网络连接和内存字符串恢复出来。像Volatility 3就是这类任务的可靠选择。
在Windows主机上,我通常先做三件事:进程列表、网络连接、命令行历史。常用的命令如下:
vol -f memory.dmp windows.pslist vol -f memory.dmp windows.netscan vol -f memory.dmp windows.cmdlinewindows.netscan这类网络扫描模块在工控取证里特别有用,它会从内存中恢复TCP/UDP连接状态,你就能看到哪台主机在当时连接了哪个PLC的502端口。即使进程已经退出,如果连接记录还残留在内核结构中,依然可能被提取到。
拿到进程列表后,重点看进程名、路径、父进程、启动时间。如果发现一个名为svchost.exe但路径在临时目录的进程,或者一个叫update.exe却频繁访问502端口的进程,这就非常可疑。我还会用dump命令把可疑进程的内存镜像导出来,再用strings检索Modbus相关的功能码序列、寄存器地址和设备ID,有时候能直接还原出攻击者操作的上下文。
3.2 Windows主机痕迹:事件日志、服务、注册表与文件痕迹
内存取证解决的是“正在发生或刚发生”的问题,主机痕迹排查解决的则是“谁配置了什么、谁执行了什么、留下了什么”的问题。Windows环境里我一般按这个顺序排查:
- 事件日志:登录成功和失败记录,尤其是管理员组的非工作时间登录;进程创建事件(如果开启了审计);计划任务的创建和执行记录。
- 服务与驱动:查自启动服务,特别关注服务名称伪装、执行路径在Temp目录或非系统盘根目录的情况。很多工控采集程序和恶意工具都会注册成服务,随系统启动。
- 注册表启动项:
Run、RunOnce、Wow6432Node下的可疑键值,排查有没有指向陌生程序启动。 - 文件系统痕迹:近期访问的PLC配置文件、HMI工程文件、历史趋势备份、.log日志,这些文件往往包含设备IP、寄存器点表、用户名密码等信息,是定位来源和影响范围的重要资料。
对于工控现场,我还会额外看一眼上位机上装了什么软件,比如Modbus Poll、ModScan、PLC编程软件、网关配置工具。这些工具本身合法,但如果出现在不该出现的机器上,或者运行记录异常,也可能是不合规行为的线索。排查时优先做完整磁盘镜像,再针对可疑文件计算哈希,避免直接操作原始介质破坏证据。
3.3 综合关联:流量、内存、主机痕迹如何串成一条线
取证的最终结论不能只靠单一数据源,必须把流量、内存和主机痕迹串起来。我举个例子你就明白了。
假设某天产线设备状态异常,现场工程师发现PLC的保持寄存器被改写了。流量取证的结果显示,在改写发生的时间段内,有一台非业务网段IP的主机通过502端口发送了多个功能码为06的请求。进一步从这台主机的内存镜像里,netscan显示该主机正好建立了到PLC IP的TCP连接,进程列表里有一个名为monitor_svc.exe的可疑进程,父进程是服务管理器。接着做主机痕迹排查,发现这个服务注册在HKLM\SYSTEM\CurrentControlSet\Services下,运行路径是C:\Users\Public\monitor_svc.exe,且创建时间恰好和流量异常时间吻合。
这时候证据链就闭环了:非预期主机发起写请求,内存证据显示具体进程,主机痕迹说明持久化方式。后续再结合事件日志中的登录记录,就能进一步判断是谁安装了这个服务。整个过程听上去不算复杂,但实际排查时很考验对协议功能码和系统痕迹的熟悉程度。
4. 常见问题与排查技巧实录
4.1 Modbus协议解析时容易踩的坑
- 把Modbus TCP和RTU的帧格式混在一起解析。TCP报文里有MBAP头,没有CRC;RTU则有CRC16校验。用错解析方式,会看到一堆“错乱”的十六进制数据。
- 寄存器地址偏移没搞清楚。报文里的地址是偏移量,工程师口里的地址是40001之类的逻辑地址,两者相差1。比如读取保持寄存器40001,报文里起始地址通常写成0x0000。分析时先确认点位表。
- 字节序问题。同一个寄存器值,不同设备可能采用大端或小端存储。高位在前还是低位在前,直接决定解析出来的数值对不对,尤其是浮点数,还得注意单精度/双精度和字序。取证报告里如果不标注字节序,会让人误读数据。
- 只抓单侧流量。有些环境只在主站侧抓包,抓不到从站响应,就会出现“只看到请求看不到应答”的偏见,影响判断。
- 忽略MBAP的事务标识符。请求和响应的对应关系靠它匹配,没有它,很多并发请求根本对不上。
4.2 取证操作与现场处置的注意事项
- 现场记录要先做。设备的指示灯状态、线缆连接、显示屏数值,拍照录像留底。别小看这一步,事后写报告时很多细节只能靠当时记录。
- 抓包前评估链路影响。优先用交换机镜像口,避免在链路上串联设备。如果是串口链路,要有专门设备做监听,不能破坏原有接线。
- 不要轻易对运行中的PLC下发指令。有些排查动作可能会改变设备状态,轻则影响生产,重则造成安全风险。任何写操作都要先获得批准,并在充分备份当前参数后进行。
- 能采集的不止是磁盘。内存、易失数据、网络连接、ARP缓存,这些都可能在重启后丢失,处理Windows主机时优先做live acquisition,再考虑关停镜像。
- 取证过程要留痕。谁在什么时间执行了什么命令、把哪些文件拷贝到哪里,都要有记录,保证证据链完整可追溯。关键文件记得用哈希固定原始状态。
4.3 实操心得:从一次工控环境异常调查得到的经验
去年我参与一起厂区数据采集服务异常报警的调查。最初的线索是管理员发现某台数据采集服务器在非业务时段频繁连接PLC的502端口,但现场没人执行任何操作。我先把采集服务器的pcap导出来,过滤出Modbus报文,发现功能码集中在03和06上,而且06写寄存器的目标地址全是某条生产线的温度设定值。随后对该服务器做内存取证,用windows.netscan看到了指向PLC的活跃连接,进程列表中出现了一个名字看起来很像系统服务的进程,但它的路径却在C:\Windows\Temp下。顺着这个路径继续排查,最终确认是第三方运维人员安装的临时脚本,因为缺乏上线审批流程被误当成正常任务注册成了服务。
这起事件虽然没有导致实际财产损失,但暴露了几个问题:通信白名单缺失、服务安装审批不严格、设备台账和正常通信基线不完整。从那以后,我在处理类似环境时都会先建议客户补两样东西:一是关键设备IP、MAC、端口的台账,二是按业务时段整理的Modbus通信基线。有了这两样,流量、内存和主机痕迹的关联分析会快很多。
5. 工具准备与基线采集:取证前最好先做这些事
5.1 常用取证工具选型与准备
工控环境取证和传统IT取证最大的区别是“设备不能随便停”,所以你带的工具必须支持在线采集和最小侵入。流量侧,Wireshark和tshark基本足够;内存侧,Volatility 3配合Windows镜像采集是通用组合;主机痕迹侧,我习惯备一套Sysinternals工具集,用来查进程、服务、网络连接和启动项。
如果你的环境有比较规范的管理要求,可以考虑用DCS/SCADA系统自带的历史数据库做辅助取证。很多PLC的数据本身会上传到历史库,寄存器变化记录可能保存几个月甚至几年,这比事后抓包更能还原时间线。但要注意历史库的存储周期和采集精度,每台设备不一样,取证前先确认范围。
另外,取证机的系统盘容量一定要准备够。工控现场的镜像文件通常很大,特别是要采集一个128G甚至更大的Windows系统盘时,存储介质不够会很被动。建议额外准备只读锁和写保护设备,确保采集介质不被改动。
5.2 建立Modbus通信基线清单
如果你正在为一个工厂或园区做长期安全运维,我强烈建议提前建立一份Modbus通信基线清单,至少要包含下面这些字段:
| 字段 | 说明 |
|---|---|
| 设备名称 | PLC、HMI、网关、服务器等 |
| MAC/IP地址 | 需要和交换机端口绑定记录 |
| 监听端口 | 多数为502,但可能有自定义端口 |
| 对端设备 | 谁和谁在通信,是否在预期白名单内 |
| 功能码使用范围 | 该设备正常使用哪些功能码 |
| 寄存器点表 | 读写地址范围、数据类型、字节序 |
| 轮询/通信周期 | 正常情况下的请求频率 |
| 维护窗口与联系人 | 异常时找谁确认生产状态 |
有了这份清单,你在流量取证时就能快速判断一个写操作到底是业务需要还是异常行为。对内存取证和主机痕迹排查来说,这份清单也能极大缩短范围排查时间。我个人的经验是,花半天时间梳理一张Excel表格,后面各种安全事件处置都会顺畅很多。
Modbus协议本身并不复杂,真正考验人的是结合现场环境、业务逻辑和证据链做综合分析。平时多看几次正常通信,多存几份基线,真出事的时候才不会手忙脚乱。