REA驱动的某川PLC私有协议-逆向分析实战

· 2026-10-14 08:00 · 89 阅读

原创 ic3blac4 2026-10-14 08:00 辽宁

诚招web、re、crypto、pwn、misc、合约方向的师傅,长期招新IOT+Car+工控+样本分析多个组招人有意向的师傅请联系邮箱 admin@chamd5.org(带上简历和想加入的小组)

1 前言

PLC是工业控制系统的"大脑",而上位机编程软件,是攻击者攻击控制器的第一要素。要评估风险,第一步就是把它与控制器之间的通信面摸清楚——尤其是那种没有文档、没有 SDK、没有编程语言绑定的私有协议。

本文的研究对象,是国产某川新发布的中大型PLC的上位机软件IFA与配套仿真控制器之间的私有通信协议。它承载着工程下载、状态查询、运行控制等敏感操作。

笔者此前在CODESYS生态做过较长研究:自2021年起分析CODESYS V2相关产品并发现数十个漏洞,提交厂商后经修复发布;此后又系统梳理 CODESYS V3通信协议的授权机制与漏洞挖掘方法。这些工作沉淀出一条经验:工业协议里的"私有",往往只是包装。

而这一次多了一个新变量——REA(Reverse Engineer Anything)。2026年10月,它登上GitHub Trending榜首,公开报道记录其单日新增约3万star,版本号一周内由v4.0连发至v6.0。但它的公开演示大多集中在可自由运行、可反复试错的软目标(Electron 应用、老游戏逻辑重建)。

于是问题被推到台前:把这类工具用到"不能随意试错、符号常被剥离、且带商业保护"的工业上位机上,它还能干活吗?

本文给出的答案是:能,而且干得比预想的多。 从"不知道这是什么协议"到"能把它停掉再拉起来",这套协议的逆向工作几乎全部由 REA 完成;人唯一不可替代的动作,是在组态软件里按了一下"停止"、再按了一下"启动"。

2 REA是什么

REA 是一层基于 MCP(Model Context Protocol) 的逆向工程编排层。它本身不做反汇编,而是把Ghidra、IDA、Hopper、JADX、Binwalk 等分析引擎统一封装为133个可编排工具,覆盖原生PE/ELF、.NET 托管程序集、Electron/JavaScript、Android APK、固件镜像与各类归档包。

对本文而言,它的两条纪律最关键:

  1. 定性优先——先回答"这是什么东西",而不是一头扎进字节;

  2. 证据分级——每条结论区分observation(观察)/ inference(推断)/ unknown(未知),保留证据与不确定性边界。

后者与笔者一贯坚持的"诚实分级"(CONFIRMED / INFERRED / OPEN)天然一致。文中每个结论也都按此标注。

3. REA干了哪些

与其罗列"工具能做什么",不如把研究拆成环节,逐条核对谁在做。如下表所示。

研究环节

REA 做了什么

人类做了什么

协议栈身份识别

检索符号,秒级命中 Cmp* 组件名与 .cpp 源码路径

—

报文格式还原

反编译 PDU 解析逻辑,读出四层嵌套与逐字段偏移

—

载荷编码识别

顺符号链定点反编译,确认 IBTag = LEB128 变长编码

—

会话流程还原

静态还原帧构造顺序,与真实抓包逐帧对照

提供抓包环境

命令字表提取

扫34个 sendRequest 调用点,全量导出命令字

—

校验字段推导

定位CRC32调用位置与输入范围,导出待验证假设

脚本暴力拟合验证

启停方向裁决

从命令字表抽出候选命令对 cmd=0x11 / 0x10

按一下"停止"、再按一下"启动"

客户端实现

依结论产出纯 Python 启停客户端,实测生效

—

一句话结论:读懂这套私有协议,REA承担了全部逆向工作;人唯一不可替代的贡献,是提供真实流量、并按几下启停按钮给出真值。

第一步:协议栈身份识别

传统做法是"对着字节猜格式"。笔者的做法是先解决一个更便宜的问题:这到底是什么协议栈?

用REA打开上位机的原生PE后,检索通信相关符号,直接命中如下特征串:

CmpGwClientImpl.cpp
CmpGateway / CmpChannelClient / CmpRouter / CmpBlkDrvTcp
<厂商>CommDriver::<协议>ChannelConnect

以及一个版本字符串:1.2.heads/V2R1C00B18

这些不是"推测出来的格式",而是反编译后直接读到的符号名与源码路径——连原始工程的 .cpp 文件名都没有抹除。

结论已经清晰:该PL 的通信栈并非自研私有协议,而是基于CODESYS通信栈构建,厂商在其上做了一层适配封装。证据等级:CONFIRMED(静态观察,符号名与源码路径直接命中)。

这一步带来两个直接收益:

其一,攻击面从"未知"变成"已知"。 CODESYS通信栈是被公开研究多年的对象,其服务组划分、认证机制与已知漏洞类型都有公开资料。确认同源后,分析可以站在既有研究之上。

其二,既有认知可以直接迁移。 笔者对CODESYS V3 通信流程的掌握,在后续章节的会话流程还原中几乎逐条对上。

从安全设计角度看,这里也暴露一个常见误区:许多厂商把"没公开文档"等同于"安全"。 若底层复用的是公开协议栈,那么"私有"只是包装——真正的安全边界并没有因为"不公开"而扩大一丝。

第二步:协议格式

REA 反编译PDU解析逻辑后,一个完整数据包(剥离 TCP 头)呈现出清晰的四层嵌套。如下图为以一条状态查询帧为例的完整报文格式与字节偏移。

报文格式四层嵌套
报文格式四层嵌套
  • 第一层 Frame:u32 LE magic = 0xE8170100(线路字节 00 01 17 E8)+ 整帧总长;

  • 第二层 Envelope:外层固定头 + 路由标签(请求方 / 响应方的端口 + IP)+ ftype / ctrl + 会话模式号 mode(由服务器分配)+ seq / ack + inner_len + nonce;

  • 第三层 InnerPDU:16字节定长头,svc位于inner+6、cmd位于 inner+8、handle位于 inner+10;

  • 第四层 IBTag:[tag_id][len][data]依次排列的键值流。

其中有两个关键点,都是靠REA定点反编译坐实的:

① svc / cmd 的偏移不能凭直觉。 svc在 inner+6、cmd 在inner+8,不是 +8 与 +10。偏移写错一个,服务号与命令号会整体错位,方向判断与命令语义全部混乱。笔者的做法是用已知语义的帧(如登录帧)反推偏移。

② 载荷是LEB128变长编码。顺符号链下钻到 IBTagReaderMoveNext的解码循环,and 0x7F / shl 7 / or 正是 LEB128(varint)的标准实现指纹。据此确认tag_id与len均为变长编码。若按定长解析,服务器会直接返回"坏参数"——这正是许多逆向"看起来对、真机不认"的典型原因。

信封尾部的 4 字节 nonce 字段,第一眼像是完整性校验。经暴力拟合确认nonce = CRC32(inner),并且用对照实验验证——修改内层字段、重算CRC32后回填,服务器照收。结论明确:它只防传输错误,不防篡改。 顺带一条方法论提醒:该字段名为 "nonce",容易被误认为承担抗重放职责;把字段名当作功能说明,是协议逆向中最常见的误判来源之一。

第三步:完整通信流程

工业协议中"先建立会话、再进行业务操作"是通用设计。在分析一套新协议时,第一步不是急着发业务命令,而是把真实客户端的会话起始帧逐帧列全。整理后得到下图所示的五个阶段。

私有协议完整通信流程
私有协议完整通信流程
  1. 链路协商:客户端发送 ftype=0xC3 协商帧,服务器回复 ftype=0x83,其中携带服务器分配的会话模式号mode;

  2. 会话注册与登录:svc=1 cmd=1 建立初始上下文 → cmd=9 交换运行参数 → cmd=2 登录,服务器在响应尾部返回会话句柄 handle;

  3. 参数协商循环:svc=1 cmd=1 + cmd=9 重复三轮;

  4. 应用附着:svc=2 cmd=0x59,附着到应用服务;

  5. 业务阶段:至此才可发送svc=2 的业务命令(状态查询、启停控制)。

把这条流程与笔者前作写过的CODESYS V3流程对照,两者在阶段划分与令牌传递方式上高度对应:

阶段

CODESYS V3(笔者前作)

本协议

① 通道建立

CHOPEN → channel_id

ftype=0xC3 / 0x83 协商 → mode

② 会话创建

创建会话 → session_id

svc=1 cmd=1 会话注册

③ 参数 / 挑战

获取 challenge

svc=1 cmd=9 参数协商

④ 身份登录

授权登录(用户名 + 密码)

svc=1 cmd=2 登录 → handle

⑤ 应用登录

应用登录 → op_key

svc=2 cmd=0x59 附着应用服务

⑥ 业务报文

业务服务组报文 svc=2

业务命令

这个"五阶段"并非本文的新发现,而是CODESYS通信栈的标准会话流程;它的价值在于印证——一套被包装成"私有"的协议,其会话模型与公开协议栈几乎逐条对应。前序章节底层是CODESYS V3协议栈的判断,在这里得到了流程级的印证。

顺带记一个坑:中途跳过第 3、4 步直接发 svc=2 业务命令,服务器会统一返回错误码28并踢掉会话。笔者曾排除多个方向——命令码写错、需要双会话、Tag编码不对、乃至需要加密升级(全量抓包中 0 条升级帧)。真因是最简单的那个:会话前置没有走完。 当出现"参数都对、却整族被拒"时,问题通常不在命令本身,而在前置流程。

第四步:启停攻击

拿到应用层访问权后,剩下一个必须靠真值裁决的问题:哪个命令是"启动"、哪个是"停止"?

REA已经把答案的一半摆到桌面上:扫过上位机里34个 sendRequest 调用点后,命令字表里有一对高度可疑的候选——载荷逐字节完全相同、仅命令码不同:

svc=2 cmd=0x11   payload = 81 01 88 00 11 84 80 00 f8 b4 ef af   → 停止
svc=2 cmd=0x10   payload = 81 01 88 00 11 84 80 00 f8 b4 ef af   → 启动

典型的 Start / Stop 命令对。证据等级:CONFIRMED(抓包观察,载荷一致仅命令码不同)。

此步骤需要人类介入告知REA操作,REA抓取报文即可做全面分析:

整理后见下表所示。

命令

svc

cmd(十六进制 / 十进制)

旁证

停止

2

0x11 / 17

点击后状态字节 01 → 02

启动

2

0x10 / 16

点击后状态字节 02 → 01

状态查询

2

0x14 / 20

应答 Tag 0x82 第 4 字节

这一步,就是人类在这项研究里不可替代贡献。 其余功夫——包括找出这一对候选命令——都由REA完成。

第五步:实测验证

按上述结论,笔者用纯Python标准库写了一个单文件客户端:构造帧 → 正确重算 nonce → 按序完成会话建立 → 发送业务命令。全程不引用厂商任何DLL / SDK。在授权仿真环境中实测,输出如下所示。

从协议身份识别到控制器启停控制的完整闭环,到此打通。 证据等级:CONFIRMED(动态验证,仿真环境实测)。

需要说明的是,本实现仅用于授权环境下的安全验证,旨在验证协议是否存在未授权控制风险,不作为攻击工具。

IFA_PLC_ATTACK

协议安全风险分析

综合静态分析与动态验证,该协议在设计上存在如下风险,整理后见下表所示。

#

风险

具体表现

1

会话建立无认证

无凭据、无证书,"登录"仅为固定字节握手动作,重放即可获得合法会话

2

握手可重放

协商帧载荷逐字节恒定,"认证"退化为"知道模板"

3

完整性校验无效

nonce = CRC32(inner),无密钥,改帧后重算即通过(已实测)

4

全流量明文

未观察到任何加密升级帧;工程文件明文传输,可嗅探、可替换

5

控制命令无授权分级

只读查询与写操作(启停)同权限,一旦建立会话即"一权通吃"

6

无速率 / 并发限制

未观察到对连接数或请求频率的任何限制

7

二进制指纹泄露

符号名、源码路径、版本串未剥离,直接暴露底层协议栈身份

在攻击路径上,这些风险并非孤立点,而是一条链:端口可达 → 建立会话(无需认证)→ 完成应用附着 → 发送启停命令。任何能访问业务端口的主体,都可以在不持有任何凭据的情况下,完成对控制器运行状态的控制。

需要特别指出的是:上述问题并非该厂商独有。由于协议底层复用CODESYS通信栈,同类问题在CODESYS生态的其他产品中同样需要关注;更广义地看,Modbus、S7Comm等经典工业协议也普遍建立在"网络可达即可信"的假设之上。这更多是一个行业性的历史遗留问题。

防护建议

厂商层面:

  • 会话认证:在会话建立阶段引入随机挑战值(challenge)与凭据校验,避免"固定模板即认证"的可重放设计;

  • 完整性校验:将无密钥的CRC32替换为带密钥的HMAC-SHA256;

  • 通信加密:CODESYS通信栈本身具备加密通道能力,但默认未启用——建议出厂配置中强制开启;

  • 授权分级:只读查询与控制类写操作权限分离,避免一权通吃;

  • 二进制瘦身:发布前剥离符号名与源码路径,减少协议栈指纹泄露。

用户层面:

  • 网络隔离:工控网络与办公网、互联网严格隔离,做好纵深防御;

  • 最小可达:通过防火墙/访问控制仅放行必要主机访问业务端口;

  • 远程访问加固:确需远程时采用安全VPN,并做好访问审计;

  • 关注补丁:关注厂商安全通告,测试后升级受影响产品。

REA在工控安全研究中的价值

一句话:这次研究里,读懂私有协议的活儿几乎全是REA干的,人类只按了几个按钮。

它能用在哪些地方:

  • 上位机 PE(组态软件、通信客户端、授权模块)是REA的舒适区——协议栈识别、报文格式还原、命令字提取、调用契约发现,基本都在数条编排内完成;

  • 尤其值得注意的是黑盒抓包摸不到的"调用契约":隐藏参数、返回值为void、运行模式标志、结构体的精确偏移——这些只有静态反编译能给出,而REA把它们的获取成本压得很低。这也是"能调通/调不通"的分水岭;

  • 它同时是"定性加速器":本次第一步就命中Cmp* 符号,"这是什么协议栈"这个最贵的问题被一次检索解决,后续分析直接站在既有研究之上。

边界也要说清楚:

  1. REA不是反汇编器,能力取决于所接入的引擎(本次为 Windows Ghidra,仅支持 PE 目标);对非PE的OT固件无能为力,那属于ICS专用工具链的范畴;

  2. 它不降低认知门槛——本次识别之所以快,前提是笔者熟悉CODESYS组件命名;它压缩的是机械检索成本,而非判断力成本;

  3. 它替代不了"观察真实流量 + 真值裁决"——这正是本次按下那两次启停按钮的意义所在。

所以正确用法是:以REA建立假设,以真机抓包与人工裁决坐实结论。 而人类的价值,恰恰集中在后者那几次"按键"上,在于对业务的理解上。

结束

招新小广告

ChaMd5 Venom 招收大佬入圈

新成立组IOT+工控+样本分析 长期招新

欢迎联系admin@chamd5.org

图片

跳转微信打开

本文转载自微信公众号「ChaMd5安全团队」,查看原文。版权归原作者所有,如有侵权请联系删除。