3306 字
17 分钟
逆向波兰列车的 logic bomb:Dragon Sector 怎么把固件挖出来的

起因是网络安全课的一个 extra-credit 作业,要求看一个安全相关的 talk 写 reflection。我捡了 37C3 的 Breaking “DRM” in Polish Trains,看完顺手把 38C3 的 follow-up 也补了。reflection 已经交上去了,这里把整件事里最值得留下的部分单独写一下 —— 厂商干了什么,在 talk 里讲过太多;真正有工程价值的是他们怎么把这套东西挖出来的

事件本身的背景一句话讲完 给波兰运营商的 Impuls 电力动车组里植了 logic bomb,送去第三方车间维修就变砖。研究者是 Dragon Sector(Michał “Redford” Kowalczyk / Serge “q3k” Bazański / Jakub “MrTick” Stępniewicz)。原始披露在 2023-12-05 华沙的 OhMyH@ck,37C3 是三周后第二次公开。Talk 链接放最后。

接下来几节按调查的实际顺序走:从他们拿到一辆”软件全绿但不动”的车开始,到怎么把固件拿出来、怎么读懂、怎么定位锁。

起点 全绿的车开不了#

SPS Mińsk Mazowiecki 是个第三方车间,接了一辆 Newag Impuls 上来。所有 hardware-level 排查都过了,司机推油门 —— brake 释放、HMI 报告一切正常、四个 inverter 拒绝出力、车纹丝不动。

车间排到没招了之后,经理 Google 了一下 “polish hackers”,翻到一篇 Dragon Sector 的访谈,直接发了封冷邮件过去。这是 2022 年的事。

整车是分布式系统,五条 CAN bus 跑 CANopen。CANopen 在 CAN 之上加了一层对象字典 —— 把 device 的状态变量 / 控制变量映射成 typed 的 object dictionary entry,所有节点共享一份命名空间。这层是后面调查的入口。

第一步:在工作正常的车和卡死的车之间 diff CAN 流量。差异很干净 —— PLC 发给 inverter 的那帧里,4 个 enable bit 全是 0,功率字段也是 0。上游所有传感器都报绿,但 PLC 主动发了”别跑”。问题被锁死在 PLC 固件里。

第一关:固件怎么 dump 出来#

PLC 是 Selectron CPU831,基于 Infineon TriCore TC1130。开发用 IEC 61131-3(Structured Text + Function Block Diagram),IDE 叫 CAP1131 —— Selectron 自家的。代码在 IDE 里写,编译成 C,再编译成 TriCore 二进制,通过 UDP 或 RS-232 烧到 PLC 上。

CAP1131 没有”从 PLC 读取程序”的按钮。Selectron 的设计意图是合理的:工程师从 IDE 把程序烧上去,以后只在 IDE 里改,PLC 上跑的就当成黑盒。

但 CAP 自带一个调试子系统叫 CISM。开发用,不是面向最终运维的。CISM 跑在 PLC 上,接受远程指令,提供的能力包括 —— 按地址范围读任意内存

认证机制:静态用户名 + 静态密码。在老固件版本里改不掉

只要你有这对凭证(它们能从 IDE 安装包里提到)、能连到 PLC 的调试端口,就能像 dd if=/dev/mem 一样把整个运行中的镜像端到端拽下来。Dragon Sector 自己写了个 CISM 客户端,把固件全拿了。

这一步是整个披露能成立的根本原因。如果 CISM 是动态凭证 / 一次一密 / 物理接入,故事到这里就停了 —— 没有固件就没有逆向,没有逆向就没有锁的证据,没有证据就只剩一辆莫名其妙不动的车和厂商一句”工艺老化”。

业界的同构问题非常多。任何工业设备上的 vendor debug channel,只要走静态凭证 + 任意读路径,本质都是一个未声明的后门 —— 区别只是面向谁。Newag 这次面向的是攻击者(他们自己)。下一次面向的可能就是别人。

第二关:逆向 TriCore + 一个叫 OCOPT 的全局数组#

固件拿到了,塞进 Ghidra,然后撞墙。

TriCore 不是 x86,也不是 ARM,Ghidra 的支持远没有那么成熟。两个具体问题:

一、调用约定。TriCore 有两套独立的寄存器堆,数据寄存器(D0-D15)和地址寄存器(A0-A15)。函数参数按类型分配到不同的堆 —— int 走 D,指针走 A。Ghidra 默认的 calling convention 不知道这件事,导致每个函数入口的参数列表都对错。每个函数你都得手动核对。

二、指令模型错的。某些指令 Ghidra 实现得不对。Talk 里点名的是 nor 加 bit operand 的变种,反编译出来语义跟 CPU 实际行为不一样。这种错误特别坑 —— 程序不会 crash,反编译看起来跑通了,但结果是错的。

更深层的问题在编译器一侧。CAP 把 IEC 61131-3 程序编译成 C 之后再编译成 TriCore 二进制。它的编译模式有个特征:所有跨 function block 的数据流都走一个全局数组,叫 OCOPT,动态下标索引

什么意思:在 IEC 61131-3 里你写 FB(function block)和它们之间的连线,正常的实现会编译成 struct + 函数参数,类型信息能保留。CAP 不这样 —— 它把所有 inter-block 的中间值都塞进 OCOPT[i],i 在运行时根据控制流计算。

后果 看到的代码长这样 —— OCOPT[34] = ...; OCOPT[57] = OCOPT[34] + ...,而看不到任何”FB A 的输出连到 FB B 的输入”的结构。所有类型信息被擦除,反编译器没法做 dataflow analysis,跨函数追变量基本失效。

修这玩意儿是大量 Ghidra 脚本工作。Talk 里没展开细节(后面我会提为什么),但能看出来他们建了一套 OCOPT-aware 的分析 pass,把动态下标在控制流里的实际值反推出来,重新建立 FB 之间的连接关系。这一步把不可读的反编译结果变回了大致能看的 FBD 图。

光做静态没用。他们另外买了一台同型号的 PLC 单独搁实验台上,用来:做差分实验、写 NVRAM 位、观察 PLC 行为变化、不影响任何在跑的实车。这是负责任的研究方法 —— 在你完全理解一个机制之前,绝不在生产车上动手。

第三关:跨版本 diff 把锁逼出来#

有了 dump、有了能读的反编译,下一个问题是 —— 锁的代码在哪。

PLC 镜像不大,但完全人肉读不现实。突破口是规模:

  • 30 辆车上拿到的固件
  • 24 辆里有锁
  • 26 个不同的 firmware 版本

锁逻辑会随时间演化(更多触发条件、更多条件组合、更新的 GPS 围栏坐标),它在不同版本之间的差异最大。所以做跨版本 binary diff,变化最频繁的 region 就是嫌疑区。这套思路很经典,但只在你有足够多样本时才管用 —— Newag 自己提供了样本,通过频繁更新固件这件事。

具体看到的特征:

  • 接近 1,000,000 的常数 → odometer 阈值
  • 阈值过线时 NVRAM 里有 bit 翻转
  • 更多触发条件出现在新版本里 → NVRAM 里出现更多 bit
  • 锁车和正常车上的 NVRAM 位状态不同

到这里反推机制 里的若干 bit 是锁的状态机,触发条件命中时被置位,持续条件成立时被读取并 gate output。

验证靠台架 PLC:把 NVRAM 对应位手动置位,看 PLC 输出 → inverter enable bit 变 0、compressor enable 线变低。锁机制确认。但没有人在生产车上动 NVRAM —— 先在台架上 100% 理解,再考虑下一步。

锁清单#

发现的锁触发器一共这些(技术细节,排除法律 / 商业讨论):

  • 空闲计时器。车必须连续 3 分钟 > 60 km/h,在一个滚动窗口内。窗口最初 10 天,被运营商抱怨后改 21 天。通勤车一天跑 20 小时,这个条件实际上只在车被拉去维修时才会触发。
  • 地理围栏 + 空闲计时器。21 天版本只在硬编码 GPS 多边形内部生效。多边形坐标对应 Bydgoszcz、Bezza 在 Miłosna 的车间、SPS 计划在 Szadów 的厂、SPS Mińsk Mazowiecki、以及 Newag 自己的厂(带 debug 开关可以关掉)。其中一个多边形画歪了,圈到了一段在用线路 —— 后来一辆 Koleje Mazowieckie 载客车在 Miłosna 站附近就这么死了一次,这事是 38C3 才公开的。
  • 组件序列号 attestation。CAN3 bus extender 和 WTB(wire train bus)适配器在启动时核对预期序列号。常规维修换一个就死。
  • Inverter 固件版本钉。预期版本写死,不匹配就死。
  • Odometer 一致性。多个设备上的里程读数差超过 100 km 就死。
  • 硬编码日期。某辆车上有一段 21 November 2022 的日期比较,触发副气泵 logic bomb。比较实现里有 off-by-one,实际效果是”在 2100 年和 2121 年这种年份才会触发”。Talk 里把整段 FBD 走了一遍 —— 经过两个 latch、写到 nvram_lock_enabled,最后 gate 到 CAN I/O extender 上的 compressor enable 线。
  • 解锁键码。司机面板上一组特定按键组合可以清掉所有 NVRAM 锁。Newag 在调查进行中静默删了这个键码,新版本换成”开驾驶舱门 + 按厕所 SOS 按钮”作为计数器复位。这是 38C3 才披露的新变种。

执行方式有个共同点:锁不报警。HMI 上一切正常。锁的实现方式是 gate output:

  • PLC → inverter CAN 帧里 4 个 per-inverter enable bit 强制清零
  • 同一帧的 “reserved / emergency stop” bit 强制清零
  • 副气泵 enable 线拉低 —— 表面上以”compressor failure”形式出现

车间看到的就是司机推油门没反应,而所有诊断都没问题。

远程锁通道#

至少一辆车上装了一个 UDP-to-CAN + Modbus-RTU 转换器。它把锁所在的 CAN3 桥接到车上一个本地 telematics 网络 —— 跑乘客信息系统、带 GSM modem。

至少有一次记录在案的锁触发,值是从这个转换器收到的

转换器本身 37C3 时还没逆向完,38C3 也没补上。但意思已经摆在那:存在一条从蜂窝网络到 lock-relevant CAN ID 的可达路径。中间可能有 VPN,可能没有。具体的协议、认证机制、攻击面 —— 不知道。

这是整个披露里 network security 角度最吓人的部分。前面讲的所有锁都是 vendor 主动行为,但这条路径意味着锁是可以被实时触发的 —— 谁能触发、能不能 spoof、能不能 replay,Talk 都没回答。

归因 自己把 Newag 卖了#

Newag 公开的说法是 logic bomb 是”黑客或者别的什么人”塞进去的。这套话不需要 Dragon Sector 反驳 —— CAP 工具链自己把它否定了

每次 build 出来的 PLC 二进制里嵌了两个东西:

  1. 工程师 Windows 机器上的完整文件路径(类似 D:\Newag\Projects\...)
  2. 编译时时间戳

每次 flash 操作 PLC 自己也会写一条 log entry,记到 PLC 内部存储。

这两份记录交叉一对,事情就清楚了:

  • 哪些 build 写在哪些 PLC 上
  • 写入时间是哪天
  • build 那台机器属于哪台,在 Newag 内网
  • 每一次 flash 都精确发生在车被送去第三方车间维修的前 1 到 3 天

这是免费的归因证据链。如果你想搞 vendor sabotage,起码不要让你的工具链把开发机的路径名嵌进二进制。


回到工程教训这一面。这次披露能跑通,链条是这样的:

CAN/CANopen 默认无认证(于是流量 diff 拿到第一个嫌疑) → CISM 静态凭证任意读(于是固件能 dump) → 26 个固件版本(于是 binary diff 能定位锁) → CAP 嵌入 build path(于是法庭归因成立) → Dragon Sector 自己买台架 PLC + 拒绝在生产车上做未理解的实验(于是能在不伤人的前提下推到底)。

任何一环换个设计,故事都讲不下去 —— 披露这件事天然脆弱。debug channel 用动态凭证、固件版本更新得不那么频繁、build 元数据不嵌路径,任何一项,这案子都会卡在某一步。Newag 没有隐藏得多好;是研究者在每一关都恰好有路可走。

工业控制系统普遍处于这个状态 —— 调试通道宽松、CAN 总线开放、build pipeline 把开发机信息嵌进 artifact —— 这套生态原本是为了让现场工程师能修问题。到 vendor 把它当 DRM 用的时候,同样的设计变成了揭穿 vendor 的工具。

研究者把完整技术报告故意压着没发,等诉讼。Talk 里你看不到 OCOPT 的具体重建脚本、CISM 协议的精确握手、UDP-to-CAN 转换器的逆向。这些短期内不会公开,因为发出去就是给 Newag 律师送弹药。这种基于策略性沉默的披露姿态本身也是这个案子的一部分。

至于诉讼那一面 —— 38C3 之后到 2026 年 5 月又有一些更新,跟技术无关,我作业 PDF 里写了,这里就不展开了。


Talks

逆向波兰列车的 logic bomb:Dragon Sector 怎么把固件挖出来的
https://blog.lishuyu.top/posts/2026-05-04-newag-drm-reverse-engineering-deep-dive/
作者
猫猫魔女
发布于
2026-05-04
许可协议
CC BY-NC-SA 4.0