ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

工控取证实战:Modbus协议报文分析与现场排查指南

工控取证实战:Modbus协议报文分析与现场排查指南 第一次去工控现场做取证是什么感觉我当时盯着一份满是502端口报文的PCAP心里既兴奋又发怵。兴奋是因为Modbus协议明文传输寄存器地址、写入值全部裸奔在流量里攻击者干了什么几乎一眼就能看到发怵是因为这些报文背后的设备状态、工艺参数、从站映射我完全陌生单纯把包“提出来”并不等于拿到了结论。后来我把Modbus协议以及围绕它的流量取证、内存取证、主机痕迹排查完整梳理了一遍才慢慢摸到门道。这篇笔记就是那段时间的记录聊聊Modbus协议的结构、报文分析思路、上位机侧的取证手段以及传统IT取证思路在工控场景里为什么会失灵。1. 工控取证为什么绕不开Modbus先理解这个协议的“裸奔”本质1.1 一个上世纪70年代的老协议为什么现在还站在取证中心Modbus是Modicon公司在1979年提出的通信协议最初就是为了PLC之间、PLC和上位机之间通信而设计的。后来因为实现简单、协议开放几乎所有工控设备都支持它PLC、DCS、HMI、变频器、伺服驱动器、智能仪表基本都能找到Modbus接口它成了工控圈事实上的“通用语”。从分层角度看Modbus属于应用层协议下面可以跑串口RTU/ASCII也可以跑TCP/IP。这也是热搜里经常把“modbus协议分层”单独拿出来讲的原因。经典模型中有一个主站和多个从站主站通常是上位机或PLC主站从站是各种现场设备。主站通过功能码和寄存器地址去读写从站的“数据面”这个数据面分成四类线圈、离散输入、保持寄存器、输入寄存器。线圈和离散输入是位bit数据保持寄存器和输入寄存器是字word数据区别主要在于读写权限和数据类型。对取证来说这个协议有个非常“友好”的特点明文、无认证、无加密。Modbus出生在工业以太网还没普及的年代当时考虑的是可靠性和实时性压根没想过安全。所以报文里的一切都是明文的只要抓到包谁在读、谁在写、读了哪个地址、写了什么值全部摊在桌面上。这也是为什么Modbus取证的上手门槛不高但真正要做好上限又非常高——你不仅要懂协议还得懂现场工艺。1.2 取证对象不只是PCAP寄存器、线圈、设备状态才是终点很多人一听说“Modbus取证”第一反应就是掏出Wireshark抓包分析。这个思路没错但如果只停留在“抓到流量”这一步会丢掉一半证据。工控取证真正要回答的问题通常是某个寄存器在某个时间被谁写入了什么值这个值对应现场哪个物理量是否造成了设备动作动作后果是什么。所以PCAP只是证据链的起点。一个完整的Modbus取证项目数据源至少包括工业交换机镜像流量、工控防火墙/网关日志PLC程序备份和寄存器映射表也就是地址到物理量的对应关系上位机数据库历史、操作员日志、报警记录HMI工程文件、组态软件历史趋势上位机主机内存镜像和磁盘镜像现场设备快照比如PLC程序、诊断计数器、故障记录传统数字取证关注的是文件、进程、网络连接而工控取证还要把报文对应到物理世界。同样是写了一个寄存器值在IT系统里可能只是数据变化在工控场景里可能意味着电机转速突变、阀门开度被改、温度设定值被拉高。这是Modbus取证和普通网络取证最大的区别。1.3 简单协议带来的三个取证难点Modbus简单但“简单”不等于“好取”。我在实操里最头疼的是下面三件事。第一Modbus没有会话概念。它的请求和响应靠事务标识符一一配对如果中间经过网关做协议转换这个对应关系可能被重置。你会在流量里看到一堆孤零零的请求找不到响应或者响应乱序分析起来非常费劲。第二寄存器地址必须靠工程文件才能还原成物理量。报文里写的是0x0010这个地址在某个水处理项目里可能代表“加药泵频率设定”在另一个项目里可能完全是别的含义。没有寄存器映射表你只能判断“有人改了地址0x0010”无法判断“他把加药量改大了”。第三PLC和上位机的时钟往往不准。工控设备常年运行很多根本不启用NTP时间同步日志时间可能偏移几个小时甚至几天。如果一开始不记录设备时间与标准时间的偏差后面做时间线对齐会非常痛苦。2. 取证视角的Modbus报文拆解TCP与RTU该怎么看2.1 Modbus TCP的MBAP头事务标识符、长度字段和单元标识符Modbus TCP的报文结构是MBAP头加PDU。MBAP头一共7个字节PDU由功能码加数据组成。具体字段见下表。字段长度取证价值事务标识符 Transaction ID2字节请求与响应配对的关键大量“孤儿请求”说明可能有人伪造响应或中间设备做了缓存协议标识符 Protocol ID2字节正常必须为0如果出现非0值优先怀疑畸形报文探测长度 Length2字节等于单元标识符长度加PDU长度如果与实际载荷不符可能是畸形包或主动探测单元标识符 Unit ID1字节相当于从站地址很多系统默认是1扫描器会遍历1到247特征非常明显功能码 Function Code1字节本次操作的具体动作读写还是诊断数据 DataN字节寄存器地址、数量、写入值等核心内容实际报文长这样0x0001 0x0000 0x0006 0x01 0x03 0x0000 0x000A TransID ProtoID Length Unit FC StartAddr Quantity这个报文的含义是事务ID为1访问单元标识符为1的从站读取起始地址0x0000开始的10个保持寄存器。这里有个容易踩坑的细节协议里的寄存器地址是“零基地址”也就是从0开始算而很多HMI和组态软件里显示的是“PLC地址”比如保持寄存器40001。对应关系是协议地址0x0000等于PLC地址40001。做地址映射的时候这个“1的偏移”必须反复核对。为什么长度字段值得关注因为很多基于Modbus的漏洞利用或协议探测工具会故意构造长度不匹配的包。正常上位机组态软件发出来的报文格式非常规整长度字段几乎不会出错。一旦看到长度不对、协议标识符非0、事务标识符重复使用等情况就要提高警惕。2.2 Modbus RTU没有IP的串口总线CRC是重要抓手Modbus RTU跑在RS-232、RS-485这类串口总线上报文结构比TCP更加紧凑一个字节从站地址、一个字节功能码、N字节数据、两个字节CRC16校验。CRC采用Modbus CRC-16算法多项式0x8005初始值0xFFFF低字节在前。RTU报文示例01 06 20 00 00 64 [CRC_LOW] [CRC_HIGH]含义是从站地址01功能码06写单个保持寄存器寄存器地址0x2000写入数据0x0064。这串字节里的CRC校验位必须正确从站才会响应。串口总线没有IP地址取证时无法像TCP一样直接看到“源IP”。但有几个抓手从站地址可以区分设备比如地址01是1号伺服驱动器地址02是2号变频器。CRC校验可以判断报文是否完整也能用来推断攻击者是否使用了现成的Modbus调试工具。如果大量报文的CRC分布高度规整说明攻击者很可能不是手写字节而是用了某个工具库。RTU靠3.5个字符时间的静默间隔来分帧如果总线上出现异常的时间间隔可能存在数据插入或误码。现场抓RTU流量比TCP麻烦得多。RS-485总线没有交换机镜像口要监听就得用串口监听器、RS-485转USB的旁路设备或者直接看工业网关/串口服务器的日志。我在第五章会详细展开。2.3 功能码不是只有读写用“行为映射”快速定位攻击意图Modbus的核心功能码其实不多但每一个都对应一种操作能力。下面是我整理的功能码行为映射表取证时基本可以对照着看。功能码名称取证意义0x01读线圈读取开关状态扫描阶段常用0x02读离散输入读取外部输入状态侦查行为常用0x03读保持寄存器读取运行参数最常用的正常读操作0x04读输入寄存器读取模拟量输入扫描阶段常用0x05写单个线圈控制单个开关可能用于启停设备0x06写单个寄存器修改单个参数最常见的篡改行为0x0F写多个线圈批量控制开关可能用于区域断电或批量启停0x10写多个寄存器批量修改参数可能用于一次写入全部设定值0x07读异常状态读取从站异常状态字侦查行为0x08诊断子功能0x00回送查询常被用来探测设备在线情况0x2B封装接口传输常用于读设备标识可以获取厂商和产品型号攻击行为一般会呈现出三个阶段。侦察阶段多用0x03、0x04读操作扫描大量地址识别阶段用0x08、0x2B获取设备型号和诊断信息控制阶段才出现0x05、0x06、0x0F、0x10这类写操作。在分析的时候不要看到写功能码就认定是攻击先建立正常基线。正常上位机写参数往往集中在少数几个寄存器、周期固定、值范围有限异常写入则常常表现为突发、跨地址、非工作时间、数值超出物理范围。3. 流量取证实操从PCAP里还原一次Modbus攻击过程3.1 抓包位置选不对分析再细也白搭Modbus TCP默认跑在502端口抓包位置直接决定证据完整性。最理想的位置是上位机与PLC之间的链路上通过核心交换机的镜像端口或专用TAP抓包。如果现场有工控防火墙也尽量在防火墙的镜像口或旁路口抓这样既能看见原始请求也能对照防火墙日志里是否有拦截记录。一个容易被忽略的问题是抓包机的时间同步。很多工控现场为了稳定运行不允许随便接NTP那你至少要在开始抓包前记录抓包机与上位机、PLC之间的时间差。方法很简单同时看三台设备的时钟记下偏差。后面分析时会发现这几分钟的偏差足以让整个时间线乱掉。抓包时长也有讲究。如果是事后取证尽量从事故发生前48小时开始回溯抓包窗口开到足够大。如果是做安全建设平时就定期抓一份正常工况的基线流量存档关键时刻拿出来对比效果立竿见影。3.2 用Wireshark和tshark快速导出会话与功能码统计Wireshark自带Modbus解析器显示过滤器非常方便。常用的几条modbus tcp.port 502 modbus.func_code 0x06 modbus modbus.func_code 0x05第二条只看写操作第三条会把所有写单寄存器报文筛出来。如果条件允许我更推荐用tshark做批量提取尤其是PCAP文件很大的时候。下面这行命令可以把报文的字段全部导成CSVtshark -r capture.pcap -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.regnum \ -e modbus.value \ -E headery -E separator,这里提醒一句不同Wireshark版本里Modbus字段名可能略有差异比如写入值字段有的版本叫modbus.value有的叫modbus.value_16。最稳妥的办法是Wireshark里打开一条报文右键目标字段选择Copy Field Name以那个为准。导出CSV后用Excel透视表统计功能码分布几秒钟就能看出整体轮廓。正常系统里0x03占比通常极高因为上位机一直在周期轮询如果0x06、0x10突然变多或者出现了大量0x08、0x2B那就有问题了。3.3 一个完整的攻击链拆解扫描、写入、恢复举一个我在笔记里反复用来练手的例子。某个水处理厂正常上位机以5秒周期读保持寄存器0x0000到0x000F偶尔对0x0100地址写PID参数。某天凌晨流量出现了明显的三阶段模式。阶段时间行为特征扫描01:33:01一个新IP发大量0x03读请求寄存器地址从0x0000一路遍历到0x0100速度远超正常轮询写入01:34:22同IP发送0x06写请求寄存器地址0x0010写入值0xFFFF伪装01:34:23起恢复读请求频率和正常轮询接近试图融入基线流量用tshark把所有写操作提取出来tshark -r water.pcap -Y modbus.func_code 0x06 -T fields \ -e frame.time -e ip.src -e modbus.regnum -e modbus.value发现整个PCAP里只有两次写操作一次在01:34:22一次在02:12:47。对照寄存器映射表地址0x0010对应“2号沉淀池加药泵频率设定”正常设定值在0x0020到0x0060之间0xFFFF直接超出了物理量程。而且0xFFFF如果被解析为有符号数就是-1对变频器来说可能意味着反向最大频率或者错误状态无论哪种解释都指向恶意操作。这个判断不是靠单个报文得出来的而是靠“扫描特征加异常写入值加非工作时间”三件事叠加。3.4 从报文列表到案件叙事时间线重建技巧把报文变成时间线是流量取证里最体现功力的环节。我的做法分几步走。先把tshark导出的CSV按时间升序排列。然后标记五个锚点第一个来自陌生IP的TCP SYN包、第一次异常功能码、第一次写操作、最后一次交互、连接断开时间。接着把主机侧的日志拿过来以5分钟为窗口对齐。比如PCAP里01:34:22写入而Windows安全日志里01:34:20恰好有一条powershell.exe进程创建记录这个时间窗口就非常可疑。做时间线时要注意一个细节写寄存器指令发出后PLC要等下一个扫描周期才会真正执行现场设备动作还要再延迟几十到几百毫秒。所以当天的报警记录比PCAP里的写操作晚几秒完全正常别因为时间不完全一致就否认关联。提示做时间线时永远先统一时区。抓包机显示UTC时间上位机显示北京时间日志里再混进一个本地时间三个时间不做偏移直接对比必然乱套。4. 别只看网线HMI与上位机里的内存、进程和Windows痕迹4.1 内存取证的价值攻击者可能就坐在“发包进程”后面流量取证只能告诉你“某个IP在某个时间发了写寄存器报文”但它没法直接告诉你这个报文是哪个软件发的是操作员正常操作还是恶意脚本还是被人远程控制的上位机在自动发包很多攻击根本不会留下传统意义上的恶意文件。攻击者拿到一台HMI后可能会直接用系统里已经装好的Modbus调试工具去写寄存器也可能写一段PowerShell脚本调用第三方库发包脚本运行完就删掉。这时候你在磁盘上找不到病毒但内存里会留下线索进程命令行、网络连接、加载的DLL、打开的工程文件路径、甚至明文密码。所以工控取证不能只盯着PCAP。对HMI和上位机做内存取证往往才是把“流量里的IP”变成“坐在屏幕前的嫌疑人”的关键一步。取证时要注意工控主机通常配置老旧很多还在跑Windows 7甚至XP尽量用FTK Imager这类工具在线固定内存不要直接拔电避免丢失内存中的证据也避免生产中断。4.2 Volatility实操从镜像里挖出网络连接、进程和注入痕迹内存取证的主流工具还是Volatility。第二版需要先判断profile第三版用起来更顺手。下面几条命令是我每次必跑的vol -f memory.raw windows.info vol -f memory.raw windows.netscan vol -f memory.raw windows.pslist vol -f memory.raw windows.cmdline vol -f memory.raw windows.malfind vol -f memory.raw windows.filescanwindows.info用于确认镜像的操作系统版本和系统时间netscan会列出网络连接重点关注连接到远端502端口的established连接记录PID和创建时间pslist查这个PID对应的进程名cmdline看完整启动参数。如果看到powershell.exe后面跟着一长串-base64或-enc参数那基本可以直接判定为可疑。现在很多团队喜欢用可视化内存取证GUI就是热搜词里那种封装好的前端。我的建议是可以先靠GUI快速出报告但遇到关键结论一定要回到命令行手工验证。因为工控机上老系统很多profile匹配一旦出错GUI可能给出一个看似合理但完全错误的解析结果命令行至少能让你看到原始数据结构。4.3 Windows主机痕迹排查事件日志、Prefetch与组态工具内存取证跑完接下来就是Windows主机的痕迹排查也就是安全事件处置里最常见的主机安全取证。下面是我常用的检查清单。痕迹来源位置/事件ID取证价值登录日志安全日志4624、4648谁在什么时间登录过系统是否使用显式凭据进程创建安全日志4688哪个进程被启动命令行参数是什么服务创建System日志7045是否安装了新的可疑服务PowerShell日志事件日志4104记录PowerShell脚本块内容预读取文件C:\Windows\Prefetch*.pf程序运行痕迹能看出工具执行时间兼容性缓存Amcache.hve、Shimcache程序执行过的历史记录用户近期文件%UserProfile%\Recent最近打开过什么文档或工程文件持久化位置Run键、启动文件夹、计划任务攻击者是否设置了开机自启动对Modbus场景来说特别要关注一类痕迹Modbus调试工具的运行记录。Modbus Poll、ModScan、QModMaster这类工具很多是绿色免安装的不会出现在“安装程序列表”里但会留下Prefetch文件、Recent目录记录和文件系统残留。只要在上位机上找到这些工具的运行痕迹再结合流量分析里的IP和功能码分布基本就能锁定攻击者使用的手段。4.4 把PCAP、netscan和进程列表串成一条线这步是重点也是我一开始最常翻车的地方。假设抓包机用的是UTC时间上位机系统时间是UTC8内存镜像里的SystemTime显示的是本机时间。如果不对齐PCAP里的01:34:22和内存里的09:34:22看上去差了8小时你可能会误判为两个独立事件。对齐方法不复杂。先用windows.info确认镜像的SystemTime然后计算镜像时间与UTC的偏移量再把PCAP时间统一换算到同一时区。对齐之后重点看netscan结果里的时间和PCAP时间的对应关系。比如内存里PID 3888的进程从01:33:00开始不断连接PLC的502端口而PCAP里恰好01:33:01出现了来自该源IP的扫描报文这个进程就可以精确定位到攻击入口。接下来再用pslist和cmdline确认这个PID是哪个程序用4688日志看它什么时候被启动最后用Prefetch判断它是不是第一次运行。整个链条就闭环了。5. 串口侧的现场实录伺服电机控制里的Modbus RTU取证5.1 伺服控制场景的RTU报文长什么样伺服驱动器通过RS-485串口接入PLC或网关PLC用Modbus RTU轮询和写入这是非常常见的控制方式。控制的参数多数是保持寄存器速度设定、位置设定、加减速时间、使能状态等。典型报文如下01 06 20 00 00 64 [CRC_LOW] [CRC_HIGH]拆开看地址01是1号伺服驱动器功能码06是写单个保持寄存器寄存器地址0x2000数据0x0064。假设工程文档里把0x2000定义为“1号伺服速度设定值”单位是0.1转每分那0x0064等于100对应10转每分。正常工况下这个值会跟随工艺要求在合理范围内变动。一旦出现异常写入比如数据被改成0x7FFF或0xFFFF设备可能立刻发生转速突变甚至飞车。这类生产事故往往伴随设备报警和急停但报警记录通常只记录“设备故障”不会记录“谁写了什么地址”。这时候唯一能还原真相的就是总线侧数据。5.2 没有交换机镜像的总线怎样找到“谁写了速度设定值”RS-485总线没有交换机镜像口取证思路和以太网完全不同。我按优先级排一下可行方案。首选是Modbus网关或串口服务器的日志。如果PLC侧通过网关把RTU转成TCP网关通常会记录源IP、目标从站地址、功能码和数据。检查网关管理界面和syslog往往能直接找到关键报文。我曾处理过一个包装线伺服速度被篡改的案例最终就是通过网关日志看到UnitID2、功能码0x06、寄存器地址0x2001、值0x7FFF日志时间比设备报警早了3秒。再调出网关后台的记录发现源IP指向一台操作员站顺着这条线进入Windows取证。次选是PLC诊断缓冲区。很多PLC会保存最近一段时间的通信错误记录如果异常写入导致从站响应异常PLC的通信诊断可能留下痕迹。虽然这类日志通常很短但作为旁证非常有用。再次是现场旁路监听。用RS-485转USB监听设备并线抓包不中断生产。但要注意总线负载、终端电阻和电气隔离接错可能导致通信故障风险不小。如果条件允许优先选高阻抗的监听方案模拟成总线上的接收者不参与发送。最后是上位机侧排查。即使RTU报文里没有IP发起写操作的进程在主机内存里一定留有网络连接记录就算连接目标是网关而非PLC也能锁定操作来源。5.3 串口取证的边界只好认到从站地址不认IP串口取证的局限也很明显必须提前说清楚。RTU报文里能确定的只有“从站地址被写入”没有源地址概念因此无法直接从报文判断主站侧是谁在操作。要补全链条必须靠网关日志、PLC诊断日志或上位机进程记录。另一个痛点是没有时间戳。RTU帧本身不带时间时间只能来自监听设备或网关日志。如果现场没有网关也没提前做旁路监听事后想从裸字节流反推精确时间几乎不可能。所以对于纯串口直连的场景我的建议是优先做PLC诊断和上位机进程排查不要寄希望于事后能抓到总线流量。CRC在这里反而是一个有用的分析点。Modbus RTU的CRC计算有固定算法攻击者如果手搓报文几乎必然用现成工具库生成的CRC分布会有工具特征。通过比较CRC计算逻辑和报文生成规律可以反向推断攻击者用了哪一类调试工具这对缩小嫌疑人范围很有帮助。6. 我的Modbus取证工具链和踩坑清单6.1 现场取证工具箱整理一份工具清单都是我实际用过的。不一定都是重型工具但关键时刻都救过场。工具用途备注Wireshark / tshark流量解析、批量导出最核心的流量分析工具Scapy / pyshark脚本化处理大批量PCAP适合做自定义统计和关联Volatility 3 / 2内存镜像分析老系统记得配合profileMemProcFS在线内存分析适合Win10以上环境读内存像读文件FTK Imager内存与磁盘固定在线固定内存首选KAPEWindows痕迹快速收集一小时能收集完关键证据各类PLC工程软件读取寄存器映射、上传程序需要厂商授权提前准备Modbus Poll / ModScan复现验证、模拟主站取证时用于验证报文是否会导致设备动作HxD / 010 Editor十六进制查看分析原始帧和文件头串口监听工具 / 工业网关管理后台RTU现场取证主要走网关日志路线6.2 最容易翻车的五个细节第一是时钟偏移。抓包机、上位机、PLC三套时钟很可能都不一样。取证第一步先把时间偏差记录下来统一换算到UTC再做任何比对。第二是抓包点错误。曾经有一回我在核心交换机镜像口抓了一整天回来一分析发现相关设备全是串口接入的流量根本不上核心交换机等于白抓。去现场之前先梳理清楚通信拓扑哪些设备走以太网哪些走串口网关在哪里。第三是Wireshark字段名的版本差异。modbus.value在部分版本里叫modbus.value_16modbus.regnum也可能变成modbus.regnum_16。别死记字段名用鼠标右键复制最保险。第四是寄存器地址“1偏移”陷阱。HMI显示40001协议报文里是0x0000。做地址映射时一定要反复核对否则你会把“写入了40002”误判成“写入了40001”差了整整一个点。第五是现场操作规范。取证过程中尽量别直接改动设备运行状态。能备份就备份能拉镜像就拉镜像能把PLC程序上传出来就上传出来。一旦动了设备状态原始证据可能被破坏后面出具的结论就会被人质疑合法性。6.3 一个可以带走的小建议先建正常基线整理这篇笔记的过程中我最大的体会是“基线先行”。没有正常流量基线任何写操作都显得可疑有了基线你才能快速判断某次写入是工艺需要的例行操作还是真正的异常。建议每个维护团队都定期做两件事导出PLC寄存器映射表并存档抓一份正常工况的流量样本作为基线保存。这两样东西平时看起来不起眼真到取证那天它们比任何高级工具都值钱。后来我自己练习时通常用一台Windows虚拟机装Modbus Poll再配合虚拟串口软件连接一个模拟从站自己造几帧扫描和异常写入的流量一遍遍对比Wireshark和Volatility的输出。纸上得来终觉浅你自己亲手把异常报文造出来再回头去看那些协议细节很多疑问就自动解开了。
返回列表