ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

工控CTF实战复盘:从Modbus流量到组态与固件的攻击链还原

工控CTF实战复盘:从Modbus流量到组态与固件的攻击链还原

从上个月的工控安全专项演练回来之后,我一直在整理这套内部代号“玄机”的工控题。说实话,一开始我是抱着“Web题刷了小半年,工控题还能翻出什么花”的心态去上手的,结果第一道题就把我卡到怀疑人生。它不是让你挖个漏洞、拿个shell、读个flag就完事,而是模拟了一次完整的工控现场异常事件,需要你一层层从流量、组态、固件里把事件还原出来。这篇文章就是把整套题的拆解过程、思路变化和坑位全部复述一遍,适合正在接触工控CTF、或者对工控安全感兴趣的人参考。所有分析都基于主办方提供的离线模拟靶场,设备全部是Demo镜像,不碰任何真实生产系统,这点先说明白。

1. 工控题的门槛比想象中高:先忘掉Web题的肌肉记忆

1.1 当“找Flag”变成了“还原现场”

我刚开始看到题目的第一反应是:先用nmap扫一扫,看看上位机、PLC的开放端口,再挂个漏扫找找有没有可以直接利用的Web漏洞。实际上这套题里确实有上位机,但它的攻击面完全不在HTTP层。你打开流量包,满眼都是Modbus/TCP的轮询报文,请求———响应、请求——响应,规律得像心电图一样。真正的“问题点”就藏在这些看起来毫不起眼的周期流量里,一旦你还在用Web题的思路去翻目录、扫路径,立刻就会迷失方向。

工控题的核心并不是“找一个漏洞打进去”,而是“理解现场、复现异常”。你面对的不再是一个运行着Nginx和MySQL的服务器,而是一条控制链路:上位机下发指令,PLC执行逻辑,传感器反馈状态。如果某个环节被悄悄动过手脚,你要做的是把这条链路上的痕迹一点点找出来,还原攻击者当时做了什么、为什么能做成,然后才能看到埋在最后面的Flag。这种思维方式,比单纯的漏洞利用训练更接近真实安全工作。

1.2 工控三件套:协议、组态、固件

这套“玄机”工控题让我彻底认清了一个事实:工控安全分析的常规套路,基本就是三条线——一看协议流量,二看组态工程,三看设备固件。协议流量解决的是“发生了什么”,组态工程解决的是“底层逻辑为什么这样设计”,固件解决的是“设备里还藏着什么”。三道关卡层层递进,缺一环都会卡住。

协议层面,最常见的是Modbus、S7系列私有协议以及其他现场总线协议。它们的报文不像HTTP那样有丰富的方法、路径和头字段,而是靠功能码、寄存器地址、数据值这些紧凑字段表达意图。组态层面,你面对的是工程师站导出的工程文件、变量表、梯形图逻辑,需要读懂一个生产流程是怎么被组织起来的。固件层面,则是把设备内部的文件系统解开,找出出厂时、维护时或事故后被人为改动的痕迹。这套题就是把这三种能力串成了一条完整的攻击链复盘场景。

2. 玄机这套题到底在考什么:交付物、目标与思路陷阱

2.1 题面速览:一座模拟水厂加药单元的“事故”

题面背景设定在一个模拟水处理厂区的加药单元,核心设备是一台负责加药泵频率控制的PLC,通过上位机进行日常监控和参数整定。某天凌晨,值班记录显示加药泵频率被异常抬高到55Hz,远超45Hz的安全上限,幸好联锁机制在数分钟后触发了停机。现场工程师排查时发现,上位机操作日志里并没有任何人工操作记录,于是把整套现场数据封存提交,作为这次工控挑战的题面。

参赛者拿到的是一个精心设计过的“事故现场快照”:一组捕获好的网络流量文件、一份加密的组态工程备份包、一份PLC固件镜像。没有多余的背景提示,所有答案都必须靠从这三样东西里自己挖出来。题目要求最终回答三个问题:攻击路径从哪里进入、哪些工艺参数被改动、设备里是否存在隐藏触发条件,然后在完成这几步后拼接出Flag。

2.2 交付物清单与Flag规则

我梳理了一下本次题目的交付物,正好对应着三层分析:

交付物说明分析方向
cap.pcapng加药单元上位机与PLC之间的网络抓包协议流量分析
engineer_project.7z加密的组态工程备份包配置、逻辑与凭据分析
plc_fw_v2.binPLC固件镜像文件固件文件系统与隐藏服务排查

题目没有直接给出明显的Flag位置,而是要求把三层线索中的关键信息拼在一起。比如流量里发现的异常写入寄存器地址、组态包里定位到的变量名、固件里发现的隐藏标识,最终组合成类似仅包含字母和数字的序列作为提交答案。这种设计很聪明,因为单看任何一层都只是碎片,只有全部串联起来,才能理解整套攻击链。

2.3 第一眼的劝退点

我第一次拿到交付物的时候,内心是相当绝望的。cap.pcapng打开之后是几千条Modbus/TCP轮询记录,过滤器拉出来全是0x03读保持寄存器请求;engineer_project.7z带密码,直接解压报错;plc_fw_v2.bin用binwalk扫了一下,又同时报了好几种文件系统特征。任何一个环节单独拎出来,都像一座小迷宫。后来复盘才明白,这些“劝退点”本身就是出题人故意设置的陷阱:流量不能只看有没有攻击报文,组态包不能只靠爆破密码,固件也不能只binwalk一把梭。所有表面障碍背后,都有更省力的正门。

3. 第一关卡在流量里:Modbus报文中的异常写入指纹

3.1 先建立通信基线,再谈异常

看工控流量包和看Web流量包最大的区别,就是你必须先理解“正常长什么样”。我把cap.pcapng拖进Wireshark,先不看具体报文,而是打开统计菜单,看协议分层和端点信息。很快确认,这台加药单元的主要通信是上位机192.0.2.10对PLC 192.0.2.110的502端口,持续发送Modbus/TCP请求。其中绝大多数是功能码0x03(读保持寄存器),间隔大约500毫秒,值是各种浮点数的编码。这就是正常的轮询节奏。

我专门用I/O图表拉了一下流量速率曲线,发现整段时间里有一个明显的小尖峰:在某个时间点,请求数量忽然暴增,随后又慢慢恢复。这个尖峰所在的时间窗口,就是攻击者集中写入参数的时间段。如果不先建立这个基线,你很难从几万条报文里分辨出那几十条真正关键的数据。很多人一上来就筛关键字“modbus”,筛出来的东西反而让人眼花缭乱。

3.2 关键过滤命令与特征提取

确定异常窗口后,我把tshark的过滤条件集中到写操作上。Modbus写操作通常集中在两个功能码:0x06代表写单个保持寄存器,0x05代表写单个线圈。我用tshark把这两类请求全部提取出来,直接看源IP、目标IP、寄存器地址和写入值:

tshark -r cap.pcapng -Y "modbus.func_code == 0x06" -T fields -e ip.src -e ip.dst -e modbus.regaddr -e modbus.value16 -E separator=,
tshark -r cap.pcapng -Y "modbus.func_code == 0x05" -T fields -e ip.src -e ip.dst -e modbus.regaddr -e modbus.coilval -E separator=,

结果非常清晰:正常情况下几乎没有0x06写寄存器请求,但在时间戳20:15:20到20:15:35之间,上位机对PLC连续发起了十几次写请求。写入的寄存器地址从0x0029开始,连续覆盖了多个地址;写入值从正常范围内的30附近一路抬升到55,恰好对应加药泵频率从常规上限之上继续越级抬升。随后还有一次0x05写线圈操作,目标线圈地址是0x0034,写入值是0xFF00,即置位状态。

3.3 从寄存器地址读出攻击意图

单独看这些寄存器地址,一时还不能完全确定它们代表什么工艺参数。但顺着Modbus应用层往下追,我发现每次异常写请求后,从站都返回了正常的写确认响应,这说明PLC确实接受了这些写入值,而不是被拒绝。这里有一个容易踩的坑:你以为只要找到攻击者发起的恶意请求就够了,但如果PLC本身没有接受,那这条请求可能只是撞了一下门,真正的效果未必发生。我特意把请求与响应的事务标识符做了匹配,确认这些写操作都成功完成,加药泵频率确实被改到55Hz,而且那个线圈也被置位。

到这一步,流量分析给出的结论是:有人通过上位机所在网段,向PLC的某段寄存器发起了批量写操作,改写了加药泵频率设定值,同时置位了一个远程维护标志线圈。但这个“远程维护标志”是什么,为什么要置位它,就不是流量本身能回答的了。所以我把这两条关键信息记下来:异常寄存器0x0029周边区域、异常线圈0x0034,然后转战组态工程包。

3.4 这条链路里的三个常见坑

第一个坑是只看请求不看响应,容易把未生效的尝试当成攻击结果。第二个坑是忽略时间对齐,流量里的时间戳如果不和报警日志、工程包里的记录比对,很难定位真正的事件窗口。第三个坑是过度依赖Wireshark图形界面,其实用tshark以字段方式导出,配合电子表格透视,反而更容易发现连续写入的规律。我后来重新复盘时,用tshark把整个异常窗口的所有请求都导成了CSV,然后在表格里按时间排序,连续10次地址递进写入的模式一目了然。工控流量分析,拼的不是一两个高级过滤技巧,而是更系统的数据透视意识。

4. 第二关卡在组态包里:被藏起来的密码和启动触发器

4.1 先扒工程包的家谱:7z里都有什么

流量分析告一段落,我转向engineer_project.7z。很多人拿到加密压缩包的第一反应就是挂字典爆破,我在这里硬生生忍住了。先用file命令确认格式,再用7z查看压缩包内部结构,发现它并不是一个简单的打包文件,而是带着清晰的目录分层。压缩包加密,但包内的文件列表居然可见,里面有各类工程配置文件、数据库导出文件和梯形图逻辑片段。这意味着真正的难度不在于能不能解开包,而在于解开之后能不能从海量配置里找到关键对象。

我尝试直接解压,提示密码错误。这本来也在预料之中。既然压缩包元数据里没有密码,我就把所有附属材料翻了一遍,包括题目环境里的说明文档和报警日志。报警日志最后一条记录格外有意思,它显示的报警时间与流量异常窗口高度吻合,而且在报警文本的尾部还跟着一串看起来很像备注的字符,像是一串日期加单词的组合。我把它当作密码候选项试了一次,压缩包成功解开。

4.2 密码线索往往不在字典里

你有没有发现,很多实战场景里所谓的“加密文件密码”,并不会写在什么安全手册里,而是藏在工程师为了方便维护留下的备注、日志尾部、甚至工程注释里。这次也是一样,压缩包密码就是报警日志里那串日期加单词组合。出题人故意模拟了一种真实存在的坏习惯:操作员为了方便记忆,把工程备份密码写在报警备注里。这种信息,字典爆破永远撞不出来,但顺着现场材料读一遍就能发现。所以工控题里遇到加密文件,第一件事永远是把所有明文材料通读一遍,而不是急着上字典。

4.3 哈希破解只是起点:变量地址映射才是命门

解包之后,我先翻到了工程核心配置文件,里面有若干XML和数据库文件。我做的第一件事是搜索关键字,包括password、user、hash、account。结果果然有收获:工程师站的上位机组态账户直接以哈希形式写在配置里,看哈希前缀就能判断是Linux标准的shadow哈希。我把它单独摘出来,用john挂了一个常用弱口令字典:

john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt

很快跑出一个结果:admin账户的密码是一个极其常见的“年份+英文单词”组合,强度低得吓人。放到真实场景里,这种账号一旦泄露,整个工程师站就等同裸奔。不过哈希破解对我来说只是一个标记点,我真正关心的是工程内的变量地址映射表。我打开DB块导出文件,搜索之前在流量里看到的几个地址。0x0029这个寄存器地址,在工程变量表里对应的名称是“加药泵频率设定值”,0x0034这个线圈地址对应的名称是“远程维护标志位”。流量分析里的抽象地址,到这一步终于变成了有业务含义的工艺参数。

4.4 梯形图导出文件里的隐藏分支

变量名确认之后,我又去翻梯形图逻辑导出文件。在工程师站里,梯形图通常按程序段组织,我也习惯性地一段段看过去。表面逻辑看起来一切正常:加药泵频率设定值来自PID输出,经过上限限制模块,最终送到模拟量输出通道。安全联锁的逻辑也是常规写法,频率超过45Hz就强制停机。但我在一个不太起眼的程序段里,发现了一个特意用注释标注为“临时维护逻辑”的隐藏分支。

这段隐藏分支的逻辑并不复杂,含义是:当“远程维护标志位”为真时,频率设定值会绕过常规PID输出和上限限制,直接强设为55Hz。换句话说,流量分析里看到的那个0x0034线圈置位操作,就是这个隐藏分支的开关。只要这个标志位被置位,即使所有正常联锁都在工作,加药泵也会被强行拉到55Hz。这正好解释了为什么上位机日志里没有人工操作记录,却能发生频率异常上升的现象。隐藏分支的判断条件,被攻击者从网络侧直接触发,导致整个逻辑保护形同虚设。

4.5 把流量线索与组态线索合并

到这一步,前两层线索已经能拼出一条完整的逻辑链:攻击者先通过上位机网段向PLC写入频率设定值的相关寄存器,同时置位远程维护标志位;组态工程里的隐藏分支在标志位为真时启动,把频率强推至55Hz并绕过安全联锁。至于攻击者是如何进入上位机网段的、为什么能绕过工程师站的认证,这两点在流量和组态包里还没有答案。我隐隐觉得,这些问题很可能藏在第三个交付物——PLC固件镜像里。

5. 第三关卡在固件深处:从binwalk提取到后门定位

5.1 binwalk只是开始,文件系统识别更重要

PLC固件镜像文件plc_fw_v2.bin是最后一层谜题。我照例先跑binwalk,然后立刻发现一个典型陷阱:binwalk在同一份固件里报出了不止一个文件系统特征,既有CRC表,又有SquashFS特征,旁边还跟着一堆看起来像加密数据的随机区段。如果你不加思考直接binwalk -e,会把大量误报内容一起解出来,反而增加干扰。我重新看了binwalk输出的偏移地址,结合固件的一般布局,判断真正的用户态文件系统集中在后半段,以SquashFS特征为主。随后解包并挂载了根文件系统:

binwalk -e plc_fw_v2.bin unsquashfs _plc_fw_v2.bin.extracted/xxx.squashfs

5.2 解包后的重点目录排查

文件系统解开之后,我没有漫无目的地翻文件,而是先检查几个固定的关键位置:第一是/etc/passwd和/etc/shadow,看有没有非默认用户;第二是开机启动脚本目录,看有没有额外启动的服务;第三是/usr/sbin和/usr/bin,对照常见固件版本找陌生文件。结果在shadow里发现了一个名叫diag的额外用户,这个用户名在公开版固件里并不存在,而且它的密码哈希强度同样很低。在启动脚本目录里,我又看到一个单独的启动脚本,会在系统启动时把telnetd绑定到管理网口地址上。

如果只是到这里,你可能觉得“无非是固件被人加了调试账号”。但真正让我确定这就是第三层答案的,是那个叫debug_shell的二进制文件。它在/usr/sbin目录下,文件名明显不是标准系统组件,而且时间戳比同一目录下其他文件都要新。这个调试接口平时处于隐藏状态,只有通过diag用户登录系统后,才能执行它。从固件编译时间戳来看,这个镜像的构建时间早于公开版本说明里标注的发布时间,说明它很可能是维护中误用的非正式内部构建。

5.3 弱口令不是目的:隐藏调试接口才是答案

于是我在模拟靶机环境中,试着用diag用户和弱口令登录telnet,成功后立刻定位到debug_shell。该接口提供了一套底层的维护命令,里面有一项可以直接读取某个特定数据块的内容。我按提示输入命令,返回了一串Base64编码的文本,解码之后是一个固定格式的标识符。随后我把这个标识符与前面组态包里找到的隐藏分支信息拼接在一起,组成了完整的最终Flag。到这里,三层分析才算是真正闭环:从固件里找到隐藏入口,到通过入口读取内部数据,再到和组态逻辑里的关键变量名关联,最后完成题目要求的事件还原。

5.4 固件分析工具箱与合规边界

这次固件分析让我认识到,工具只是开胃菜,真正重要的是一套“解包总要先识别文件系统、排查总要先看启动脚本和账号体系”的流程。binwalk、unsquashfs是常用工具,关键目录的排查顺序也很重要。必须再次强调,这份固件是主办方提供的模拟镜像,整个过程都在隔离环境里进行。如果你手上有一份真实设备的固件,请先确认是否有分析和测试授权,不要轻易对在役设备做任何形式的逆向或调试操作,这一点在工控领域尤其重要。

6. 解完题之后再想一步:这些手法对防守有什么启发

6.1 异常写报文如何变成检测规则

题目里的攻击动作,翻译成防守语言其实特别清晰:一次对工业控制协议的非预期写操作。你完全可以把它写成检测规则,比如在ICS流量出口或上位机网段旁路部署检测设备,对Modbus/TCP协议做深度包解析,重点监控两类现象:一类是写寄存器功能码的请求频率和覆盖范围是否异于基线,另一类是关键寄存器地址是否出现在白名单之外的操作来源中。题目里那几十次异常写请求如果被这样的规则捕获,完全可以在攻击者修改频率的瞬间就触发事件告警,而不必等到联锁停机后才去做事故复盘。

6.2 组态工程文件的配置审计

组态工程文件是工控环境里最容易被忽略的数据资产。这次的工程备份包里藏着弱口令账户、隐藏逻辑分支和明文关键注释,放在真实环境里,任何一个都是严重隐患。防守视角的做法是:对工程师站、组态软件和工程备份文件建立资产管理台账,重要工程文件要做加密存储和权限隔离;对梯形图逻辑段做版本管理,定期与基线版本做diff;对工程师账户执行和IT系统同等级别的口令策略,复杂度和更换周期都不能放松。

6.3 固件基线与调试端口管理

固件层面的问题在真实场站更隐蔽。很多工控设备部署多年,固件版本停留在出厂状态,内部可能存在非公开调试账号或隐藏维护接口。对防守方来说,能做的事包括:整理所有设备的固件版本清单,计算每个设备当前固件的哈希值作为基准,并定期复核;关闭不使用的调试服务和物理端口;把维护网段和常规控制网段做严格隔离,避免攻击者从业务侧直接触达设备调试链路。这些东西听起来很基础,但真正执行过就会知道,光是把资产台账推进到90%的覆盖,就需要相当长时间。

6.4 新人怎么走工控CTF这条路

如果你刚接触工控CTF,我的建议是从协议分析入手,先找一份带注释的Modbus/TCP数据包,花一天时间把字段结构、功能码和常见错误码搞明白。第二步是组态,搭一套简单的PLC模拟环境,亲手把变量表、寄存器和梯形图功能块对应起来,重点理解“某个地址到底对应现场哪个设备”。第三步才是固件,找一个公开的固件库样本,用binwalk走通解包、挂载、关键目录排查的完整流程。每一步都要在本地隔离环境完成,不要拿真实系统练手。等你走完这些路,再回头看“玄机”这套题,思路往往就清楚了。

这次复盘我前后跑了三遍,最大的收获不是拿到了Flag,而是把“协议—组态—固件”这三个层面彻底串联成了一条可复用的分析链路。现在我再去看真实环境的工控数据,第一反应不再是找漏洞,而是先问三个问题:正常状态长什么样、哪里有不该出现的写操作、设备里是否藏着不该有的入口。最后再分享一个小技巧:这类多文件联动的题目,最好把流量时间戳、报警日志时间、组态文件修改时间全部统一成epoch秒格式,放到同一张电子表格里按时间轴排序,很多看似割裂的线索会自动站到你应该看到的位置上。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进