三菱FX3U固件源码级禁止上传:从原理到实操

三菱FX3U固件源码级禁止上传:从原理到实操 1. 为什么我会在固件源码上动刀先聊聊交付现场那点糟心事1.1 设备被“二次开发”的痛点做设备交付这行干久了你会发现真正让人头疼的不是调试期的逻辑bug而是设备签收之后被人把程序偷偷“借”走。我经历过一次比较惨的案例一套三菱FX3U的成型设备交机三个月后客户拿着改得面目全非的程序来找我验收新功能里面有几段是我自己写的配方加密逻辑被人拆出来重写了一遍连注释风格都没换。也就是从那时候起我开始认真对待“禁止上传”这件事——在三菱FX3U的V10.5源码基础上把程序读取这条路从固件层面彻底封死。三菱自带的密码机制不是没用但它在某些公开讨论过的路径面前确实“没啥用”所以最终方案必须从源码层解决。我先把你可能会关心的问题摆出来这个方案改了什么、能防住什么、会误伤什么、在V10.5源码上具体怎么操作。这篇文章适合设备厂家的电气工程师、做自动化设备二次开发的同行以及所有被“客户/第三方读程序”困扰过的项目负责人。我会把改动的思路、定位代码的过程、实测验证的细节和踩过的坑都写清楚方便你少走弯路。1.2 传统密码保护为什么“有限”先说一个挺常见的场景。设备厂家卖出去的是一套完整的成型设备里面核心的价值不是钣金机架也不是伺服电机而是那套经过无数次现场调试才稳定下来的三菱FX3U控制程序。梯形图里那些联锁逻辑、配方参数、异常处理分支才是设备真正的“know-how”。问题就出在这儿设备交到客户手上之后PLC的编程口是敞开的。今天客户的电气工程师拿根编程线连上去明天第三方售后公司背着电脑来“帮忙看看”后天设备厂家的同行借着做配套工程的空档顺手把你的程序读出来了一份——这在我的圈子里不是新鲜事。程序一旦被拿走轻则对方照着逻辑改个参数做成“自主优化版”重则整个工艺被复刻到竞品设备上设备厂家的后续订单全部泡汤。我从一开始就吃过这个亏。那时候也设置了PLC密码也把参数区的关键字保护等级开到了最高但客户那边请来的一个搞自动化兼职的年轻人两个小时就把程序整体读了出来。我接到电话的时候整个人都是懵的。密码设了保护开了怎么还能被读走后来我查了一圈才明白FX3U这代产品在关键字校验上存在一个本质短板密码检验是发生在“读取请求”的校验流程里而不是发生在“存储介质”的物理层。只要有一条绕过校验的请求路径密码就形同虚设。具体怎么绕的我不想在这里展开做这行的朋友都知道网上相关的公开讨论一搜一大把。这里稍微把三菱FX3U自带的关键字保护机制说得细一点方便新入行的朋友理解。你可以在PLC参数里设置一个关键字也就是俗称的密码然后选保护等级。等级低一点的只禁止别人改写程序等级高一点的会禁止别人读取程序。这个机制的本意是好的但它的实现方式决定了自己的天花板——三菱FX3U的关键字校验放在通信协议层。上位机发出读取程序文件的请求之后PLC固件会先判断这条请求是不是带上了正确关键字如果带上就放行接着去闪存里把程序读出来打包返回如果没带就返回一个“关键字错误”的响应。问题就在这儿这个校验逻辑本质上是“问一下就放行”的通行证模式校验的是“请求是否携带正确钥匙”而不是“物理上阻止对存储区的读取请求”。于是只要掌握了协议层面的请求组织方式或者通过某些特定工具直接构造一条目标请求这个校验就可能被绕过去剩下的流程和正常读取没有任何区别。我在行业群里聊过这个事不少搞设备保护的老工程师都摇头大家普遍承认一个现实对三菱FX3U这种老一代PLC来说光靠参数区密码想挡住一个有心人几乎做不到。这也是我当时决定在固件源码层面做“禁止上传”功能的直接动力——既然校验层的密码保护不够硬那就把“读取用户程序文件”这个动作本身从协议层抹掉。1.3 项目定调在V10.5源码上做增量开发既然方向确定了就该选载体。我这边拿到的是三菱FX3U基于V10.5版本的固件源码也就是系统程序开发包原本是用来做功能定制的。V10.5是一个相对稳定、兼容性好的版本很多设备厂家手里都有基于这个版本的二次开发经验。我给自己定的目标是做一次“增量修改”不重构、不重写只在原有通信命令分发的代码路径上加一个可配置的“禁止上传”开关。具体约束有三条禁止读取用户程序区梯形图、ST程序、注释等代码文件让任何上位机工具都拉不走程序保留参数区和软元件的正常读取否则GX Works2没法识别PLC型号也别想做在线监控修改点尽量集中做好宏开关能在一个模块内解决的问题绝不动第二个模块。这三条约束后来被证明非常关键。尤其是第二条我在实际开发中见过不少同行为了“防读取”把整个读取命令一刀切结果PLC型号都识别不了现场工程师连设备都连不上保护是保护住了但设备也“残废”了。真正合理的禁止上传功能必须精细到只禁“程序文件”不碰“运行数据”。2. 禁止上传功能的实现原理它到底禁的是什么2.1 一条读取请求是怎么一步步拿到程序的要理解禁止上传功能得先清楚三菱FX3U里一条“读取程序”的请求在固件内部是怎么被处理的。整个过程大致是这样的上位机GX Works2、GX Developer或者任何支持FX协议的上位软件把“读取程序文件”这个动作封装成一条数据帧里面包含命令字、文件类型、起始地址和长度等信息数据帧经过编程口、USB转串口或以太网模块进入FX3U的通信接收缓冲区固件里的通信任务解析数据帧提取命令字进入命令分发函数分发函数判断这是一个“读程序文件”请求走与参数读取、软元件读取完全不同的分支在该分支里固件先做关键字校验校验通过后调用文件系统服务从用户程序区闪存中把代码数据读出来数据被填进响应帧原路返回给上位机。从这个流程能看到真正“读出程序”这件事发生在第5步但一切是否能继续往深处走取决于第4步的命令分发以及第5步前的那道文件系统服务入口。禁止上传功能就是在第4步或者第5步之间加一道闸门让“读程序文件”请求直接返回错误不进入实际的闪存读取动作。2.2 为什么一定要改到源码层而不是靠外部措施这里放一张方案对比表是我当时认真分析过的几个路线方案能防住什么防不住什么上位机软件加密/加密狗防住普通操作员误读别人拿原版GX Works2直接连PLC照样读外部硬件锁如加密中转模块防住一部分通过该模块的通信绕开模块直接接编程口就失效固件源码层禁止上传从根上不响应程序读取请求无除非设备被物理拆机并重烧固件我不想在这里贬低前两种方案它们各有适用的场景。比如上位机软件加密能在项目交付层面做一些操作权限管理起码能让现场操作员看不到敏感画面。但它有一个硬伤上位机工具是跑在电脑上的而PLC的编程口是物理对外开放的只要有人拿一台装了正版GX Works2的电脑直接连到PLC编程口上位机这边的任何加密措施都管不到。外部硬件锁也有类似问题它要求所有通信都必须经过这个锁可三菱FX3U的编程口就在控制柜里物理上很难保证别人不绕过。固件源码层的屏蔽则不同它等于把“读取程序”这件事变成了一个不受理的业务。不管上位机是谁家的软件、怎么组织请求帧只要最终走到固件里的命令分发函数就会被拦下来。这种“物理层不可用”的效果才是真正能把程序保护住的思路。用大白话讲前两种方案是给大门加了一把好锁固件方案是直接把那条通道从图纸上抹掉了——连门都没有防撬锁还有什么意义2.3 禁“上传”不等于禁所有读取这个边界要拎清楚做禁止上传功能最容易犯的错误就是理解成“所有读取请求都不能响应”。实际上一个PLC要正常运转很多读取动作是必需的比如上位机识别PLC型号和版本信息需要读系统区在线监控梯形图运行状态、读当前软元件值需要读设备内存区或软元件缓存读取报警历史、读取PLC当前运行模式需要读状态区。这些读取虽然也是“读”但它们和“读用户程序文件”是两个完全不同的概念。用户程序区存的是编译后的代码和注释是设备厂家的核心资产软元件区存的只是运行时的中间数据是设备当前状态的一个快照这两者的保密级别完全不一样。所以在设计拦截规则时我把目标精确锁定在“读用户程序文件”这一个命令字上其他读取请求全部放行。这样既实现了禁止上传又不影响设备日常的调试和诊断。下面这张表是我实际的允许与禁止清单请求类型是否允许说明读PLC型号/版本/内存容量允许上位机识别设备的必要信息读用户程序文件梯形图/ST禁止本功能的核心目标读参数文件PLC参数设置允许诊断时需要读软元件当前值D/M寄存器允许在线监控依赖读报警历史/运行状态允许故障诊断依赖下载/写入程序文件允许可按需配置设备厂家仍可更新程序2.4 一个细节多条通信链路怎么一网打尽三菱FX3U的通信入口不止一个有内置编程口、扩展通信板、以太网模块等。如果只在编程口这一条链路上加拦截那别人从以太网模块走读请求照样可能把程序拉走。我在实现时注意到不同通信链路的物理层处理不同但最终解析出来的命令字都会汇聚到同一个内部命令处理函数里。也就是说外部入口再多命令分发就那一个“集散地”。所以在命令分发这个统一出口做拦截性价比最高一条改动就能覆盖所有通信链路。实测下来编程口、USB转串口、以太网模块这三条路径全部都在同一个拦截点上被挡住。这个“集散地”思路也帮我在后续排查问题时省了不少事——只要在命令分发函数里看到请求被拦下了就说明所有入口都已经覆盖不需要逐个端口去验证。3. V10.5源码基础上修改实操记录3.1 准备源码环境和工具链先说明一下我这里的V10.5源码是公司通过正规授权拿到的固件开发包不是网上随便下的那种散包。开发包解压之后目录结构大致分几个部分内核启动、硬件驱动、通信协议、文件系统、应用逻辑等。我们要动的主要是通信协议和文件系统这两个目录。工具链方面三菱FX3U用的是一套基于瑞萨平台的交叉编译环境和普通的GCC开发流程不太一样。我当时的工作电脑是Windows环境装了官方推荐的编译器套件和烧写工具。如果你打算自己动手建议先把你手里的源码版本和编译器版本核对一遍不同版本之间如果编译器不匹配编译出来的固件可能根本无法运行。动工之前我做了几件准备工作把原版V10.5源码整体打了一个tag备份后续所有改动都在新的分支上进行方便随时回到原始状态把三菱官方提供的编程口通信协议文档翻出来确认了读程序文件命令字的定义范围在测试平台上刷入原生V10.5固件确认设备正常作为后续对比的基线。这一步看起来啰嗦但非常有必要。固件开发不像上位机开发改错了顶多软件崩溃固件刷错设备很可能直接变砖。所以备份、基线、回滚方案一样都不能少。3.2 定位命令分发核心代码在源码里找命令分发函数的过程其实就是顺着“读取请求”这条路反过来追。我先在源码目录里搜了协议相关的关键字比如frame_parse、command_dispatch、etx、check_sum这些很快就定位到了通信模块的几个核心文件。比较有代表性的文件大致是这些实际文件名因版本而异我这里按功能起名comm_frm.c负责数据帧接收和校验comm_cmd.c负责命令字分发把所有合法命令转给对应的处理函数file_service.c负责文件级的读写操作包括读取用户程序文件、参数文件等。comm_cmd.c里的命令分发函数就是我们要动手的地方。这个函数的结构不复杂本质是一个大switch或者一张命令表每个命令字对应一个处理函数指针。读程序文件这个命令对应的处理函数会再调用文件服务层把用户程序区的数据打包返回。这里要提醒一句不同版本源码的命名可能完全不同不要死记我这里的文件名。正确做法是在你自己的源码里先找到“读取文件”相关的函数名再反向追踪到命令分发层。搜“read”“upload”“file”“program”这些英文关键字通常很快就能定位。3.3 核心改动给上传命令加一道闸门我的改动做了一个编译宏开关默认关闭让固件行为在没有启用时和原版完全一致。下面这段是示意代码我已经把实际的协议细节简化掉了但思路是完整的。/* file: comm_cmd.c */ /* 三菱FX3U V10.5 命令分发处理示意代码非完整实现 */ #define CMD_READ_PROGRAM_FILE 0x01 /* 读用户程序文件 */ #define CMD_READ_PARAM_FILE 0x02 /* 读PLC参数文件 */ #define CMD_READ_DEVICE_MEM 0x03 /* 读软元件数据 */ #define CMD_READ_SYS_INFO 0x04 /* 读系统信息 */ /* 禁止上传功能的编译开关默认关闭 */ #ifndef CFG_DISABLE_UPLOAD #define CFG_DISABLE_UPLOAD 0 #endif static U8 cmd_dispatch(U8 cmd, const U8 *req, U16 len, U8 *rsp) { /* 在真正处理之前先做拦截判断 */ #if CFG_DISABLE_UPLOAD if (cmd CMD_READ_PROGRAM_FILE) { /* 直接返回“无法读取”的错误码不进文件服务层 */ return ERR_UPLOAD_DISABLED; } #endif switch (cmd) { case CMD_READ_PROGRAM_FILE: return read_program_file(req, len, rsp); case CMD_READ_PARAM_FILE: return read_param_file(req, len, rsp); case CMD_READ_DEVICE_MEM: return read_device_mem(req, len, rsp); case CMD_READ_SYS_INFO: return read_sys_info(req, len, rsp); default: return ERR_UNKNOWN_CMD; } }这段代码的核心逻辑就是在进入switch之前先判断当前命令是不是“读用户程序文件”。如果是就直接返回一个错误码不调用read_program_file函数。这样不管上位机发来的请求帧里带没带正确关键字都到不了真正读闪存的那一步。关于错误码我选了一个在FX协议里有明确含义的状态码这样上位机收到后能弹出相对友好的提示而不是直接卡死。这一点也在后面的实测里得到了验证GX Works2弹出来的报错文本能让现场工程师一眼看懂是“读取被禁止”而不是“设备故障”。3.4 保留必要命令别把路全部堵死加完拦截之后我又仔细过了一遍命令分发函数把所有“读”类命令逐个梳理了一遍。我的原则是宁可多放行一些无关紧要的命令也不能误伤正常调试所需的命令。因为一旦误伤现场工程师拿着GX Works2根本连不上PLC会直接骂娘。最终保留的是读PLC型号、读系统内存容量、读软元件、读运行状态、读参数文件这些命令。这些都是设备管理和故障诊断的基础能力禁止它们没有任何收益反而会增加维护成本。前面的表格里已经列过这里不再重复。有个细节要单独说一下有些上位机在连接PLC时会先通过“读系统信息”来确认当前挂载的是不是三菱FX3U如果这一步失败它会直接判定“PLC无法识别”后续所有操作都做不了。所以“读系统信息”这个命令是绝对不能动的。我一开始有人想连“读参数文件”也禁掉后来实测发现某些版本的GX Works2在打开工程时会先读参数文件来确认PLC配置禁了之后连接都会异常只好放回来。3.5 编译与烧写一把钥匙配一把锁代码改完之后就是编译。我这里的步骤是打开交叉编译环境的工程文件把CFG_DISABLE_UPLOAD宏设为1执行完整编译生成新的固件文件hex/bin格式用烧写工具把固件刷进测试PLC的存储区重启PLC确认运行指示灯正常进入下一步功能验证。编译过程中遇到过一个小坑源文件里之前有人定义过宏CFG_DISABLE_UPLOAD导致我这里重复定义编译警告。处理方法是先全局搜索这个宏名确认没有历史残留再使用。如果你的源码里也出现过类似的重复定义问题别直接删原来的宏先看清楚它是干什么用的有可能它只在某个模块生效和你的需求并不冲突。烧写环节我建议保留一份原厂V10.5固件的备份并且在烧录器里设置好固件区域保护防止误操作把引导区也覆盖掉。引导区一旦刷坏PLC就真的救不回来了。这里再多说一句烧写接线和工具的选择一定要用正规渠道的硬件我自己在测试时用过一条劣质USB转串口线烧写到一半直接丢帧PLC差点变砖。后来换了原厂编程线和靠谱的转换器就再没出过这种问题。4. 实测验证从工程软件到通信口反反复复确认4.1 测试矩阵什么工具、什么路径、预期什么结果固件改完不等于完事真正见真章的是验证环节。我搭建了一个测试环境一台三菱FX3U测试机刷入开启禁止上传的固件一台PC装了GX Works2、GX Developer和第三方的串口调试工具连接方式准备了编程口直连、USB转串口线、以及挂在扩展板上的简易以太网通信测试。测试矩阵如下测试项连接方式预期结果读取程序文件上传编程口GX Works2失败提示无法读取读取程序文件上传以太网GX Works2失败提示无法读取读取程序文件上传编程口GX Developer失败提示无法读取识别PLC型号和版本编程口GX Works2成功能正常识别在线监控软元件值编程口GX Works2成功寄存器数值能实时刷新下载程序到PLC编程口GX Works2成功程序正常写入修改PLC运行模式编程口GX Works2成功RUN/STOP切换正常和预期不一致的测试项我会优先排查原因。4.2 最重要的验证读程序必须失败但连接必须正常第一轮测试我先用GX Works2连接编程口执行“从PLC读取程序”的操作。程序连接没报错PLC型号也识别出来了然后点击读取进度条走了一点点就停住了随后弹出一条错误提示大意是“无法读取程序文件可能被保护或不存在”。这个提示正是我预期的“禁止上传”错误码翻译出来的内容。接着换GX Developer试结果类似能连接上、能读取PLC类型但一执行上传就直接报错。再换以太网方式连也一样被拦在门外。当时我心里一块石头落地了说明命令分发层的拦截对所有通信链路都生效了。同时我也确认了最关键的一点虽然上传被禁止了但GX Works2在连接阶段并没有出现“PLC无响应”的故障。也就是说读型号、读系统信息这些命令都正常返回了只是读程序文件的请求被单独掐断。连接正常、监控正常、下载正常只有上传这一条被堵