首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
逆向工程
发新帖
0
0
[原创]基于 .NET 4 实现从欧姆龙 Sysmac Studio工程文件解析全局符号变量表
发表于: 15小时前
74
[原创]基于 .NET 4 实现从欧姆龙 Sysmac Studio工程文件解析全局符号变量表
Coder_shen
15小时前
74
# 基于 .NET 4 实现从欧姆龙 Sysmac Studio 工程文件解析全局符号变量表 > 摘要:欧姆龙 NJ/NX 系列 PLC 的 EtherNet/IP 通讯(CIP 符号寻址)需要提前知道每个变量的**符号名、完整路径与 PLC 数据类型**。本文介绍如何在不依赖任何第三方库、目标框架仅为 .NET Framework 4.0 的前提下,直接解析 Sysmac Studio 的 `.smc2` 工程文件,从中还原出完整的全局符号变量表——包括 ZIP 压缩包的手工解析、SLWD 变量表格式、DUT 结构体展开、变量树构建与"完整路径名 → 数据类型"映射的生成。 --- ## 一、背景与需求 一个实际工程(焊机生产线,NX502-1600)的全局变量表包含 **3,042 个顶层变量、44 万多个叶子变量**。上位机驱动与模拟器都需要这样一张表: - **驱动侧**:浏览变量树、按符号名读写变量; - **模拟器侧**:按 `VAR://名称` 解析内存地址,模拟批量读写。 变量表有三个来源:运行时从 PLC 上传、解析 txt 导出文件、**直接解析 .smc2 工程文件**。前两者依赖 PLC 在线或人工导出步骤,而 .smc2 是 Sysmac Studio 的工程存档本身——离线、完整、无需额外操作,是实现"打开工程即能通讯"的最佳数据源。 但有两个现实约束: 1. **格式未公开**——.smc2 的内部结构只能靠逆向分析; 2. **目标框架是 .NET 4.0**——没有 `System.IO.Compression.ZipArchive`(.NET 4.5 才有),必须手工解析 ZIP。 ## 二、.smc2 工程文件的内部结构 .smc2 本质上是一个 **ZIP 压缩包**,内部按工程 GUID 组织: ``` <项目GUID>/ <GUID>.xml 变量表 (SLWD 格式) / 数据类型 (DUT) / PLC 配置 <GUID>.oem 工程实体清单 (变量表/POUs/任务等) <GUID>.manifest CPU 型号信息 <GUID>.NexBuildResults 编译版本 ``` - 变量表以 **SLWD** 文本格式嵌在 xml 条目中(文件头 30 字符内出现 `[SLWD` 即判定); - 结构体(DUT)与枚举定义也是 xml(`<DataType BaseType="STRUCT">` / `BaseType="ENUM"`); - 哪个 GUID 是全局变量表、属于哪个 POU,由 `.oem` 实体清单回答。 ## 三、第一步:手写极简 ZIP 读取器 .NET 4.0 没有 `ZipArchive`,好在 .smc2 只用 Deflate 压缩(方法 8),按 ZIP 规范解析三种结构即可: ``` [本地文件头] [数据] ... [中央目录] ... [EOCD] ``` ```csharp internal class Smc2ZipArchive : IDisposable { // 1. EOCD 签名 0x06054b50: 从文件尾部回扫定位 (末尾注释可能长达 64KB) // 读出入口数量 / 中央目录偏移与长度 (不支持 ZIP64) // 2. 中央目录签名 0x02014b50: 逐条目读 Method / 压缩前后大小 / // 本地头偏移 / 条目名 (UTF-8) // 3. 读取: 按本地头偏移定位, 跳过 30 字节固定字段 + 文件名 + 扩展区, // 方法 8 用 DeflateStream 解压 (RFC1951 裸 deflate 流, 与 ZIP 方法 8 一致) } ``` 要点: - **回扫 EOCD**:ZIP 末尾注释最长 64KB,从尾部向前扫描签名最稳妥; - **循环读满**:`FileStream.Read` 不保证一次读满,封装 `ReadExactly` 处理短读; - 全部条目名用 `StringComparer.OrdinalIgnoreCase` 存字典,读取时按名字索引。 整个读取器不到 150 行,零依赖。 ## 四、SLWD 变量表格式 SLWD 文本每行一条变量记录,列以 TAB 分隔、每列为 `K=V`: ``` ++D=N=HMI_Button<TAB>D=ARRAY[0..9,0..4] OF BOOL<TAB>AT=%W0.0<TAB> Com=&comment<TAB>G=VAR_GLOBAL<TAB>NTP=PublicationOnly<TAB>R=1... ``` 解析规则: | 键 | 含义 | 键 | 含义 | |---|---|---|---| | `N` | 变量名 | `R` | 保持属性 (=1) | | `D` | 数据类型 (IEC 写法原样保留) | `RO` | 只读 (=1) | | `AT` | 硬件地址原文 | `Ord` | 排序号 | | `Com` | 注释 (XML 实体编码) | `G` | 作用域 (VAR_GLOBAL/VAR/...) | | `IV` | 初始值 | `NTP` | 网络公开属性 | 实现上的三个细节: 1. **键前缀剥离**:`K` 前面的 `+` 是行/列标记,参与匹配前剥掉; 2. **后键覆盖**:同一行重复出现的键,后出现的生效(`data[key] = value` 即可); 3. **`+GN=` 行跳过**:只是作用域标记,记录自身的 `G=` 值更精确。 `Com=` 里的 `&`、`<` 等需要做 XML 实体解码——注意 **`&` 必须放在 `>`/`<` 之后替换**,避免双重解码。 ## 五、OEM 实体清单:回答"变量表属于谁" 一个工程含全局变量表 + 各 POU(Program/FB/Function)的局部变量表,它们的归属记录在 `.oem` 中: ```xml <Entity type="Program" name="ST10" id="{guid-A}" /> <Entity type="Variables" subtype="Local" id="{guid-B}" /> <!-- 属于 ST10 --> <Entity type="Variables" subtype="Global" /> <!-- 全局表 --> <Entity type="DataType" name="Recipe_Data" id="{guid-C}" /> <Entity type="NexTask" name="PrimaryTask" id="{guid-D}" /> ``` 解析器按文档顺序扫描 `<Entity .../>`,维护"当前 POU"上下文:遇到 `Program`/`Function`/`FunctionBlock` 进入上下文,`Variables`/`PouBody` 等实体挂在当前 POU 名下。最后得到 `变量表 GUID → POU 名` 的映射,全局表命名 `Global`,局部表用 POU 名。 ## 六、DUT 结构体与枚举 结构体成员的类型信息不在 SLWD 里,而在 DUT 定义中: ```xml <DataType Name="Recipe_Data" BaseType="STRUCT" OffsetType="NJ"> <DataType Name="WeldTime" BaseType="UINT" OffsetChannel="0" OffsetBit="0"/> <DataType Name="WeldPressure" BaseType="ARRAY[0..3] OF REAL" .../> </DataType> <DataType Name="State_ENUM" BaseType="ENUM"> ... </DataType> ``` - 成员是自闭合的 `<DataType ... />` 引用,正则平铺收集即可; - 跳过无名/无类型成员和内联 `STRUCT` 声明; - **IEC 类型名大小写不敏感**(工程里 `ST_Robot_01IN` 被引用为 `ST_Robot_01In` 很常见),DUT 字典必须用 `OrdinalIgnoreCase`; - 枚举定义可能在另一个 xml 里,与结构体分开扫描(`BaseType="ENUM"` 收集类型名,供模拟器把枚举按 4 字节整数读写)。 ## 七、变量树构建:递归展开 + 数组延迟展开 每张变量表构建为顶层节点列表,`BuildTypedNode` 按类型名分派: | 类型名 | 结果 | |---|---| | `STRING[n]` | STRING 叶子 | | `ARRAY[a..b,c..d] OF X` | ARRAY 节点(存维度/上下界/元素类型元数据) | | 已知 DUT 名 | STRUCT 节点 + 成员子树(递归) | | Sysmac 库类型(如 `_sAXIS_REF`) | 内置成员表构建的 STRUCT 子树 | | 其它 | 标量叶子(类型名原样保留) | 两个关键设计: **1. 数组延迟展开**。一个 `ARRAY[0..99,0..99] OF REAL` 有 1 万个元素,全部预展开会让 44 万叶子的工程再膨胀一个数量级。所以数组节点只存边界元数据,GUI 浏览展开树节点时再按需生成 `Name[x,y]` 元素;例外是**结构体数组**——元素类型是结构体时预展开(浏览体验与性能的平衡点)。 **2. 结构体子树模板缓存**。同一 DUT 类型会被大量重复展开(结构体数组每个元素都重建一遍完整成员子树),首次构建后把成员节点字典缓存起来复用节点引用——树节点仅供只读浏览,共享子树是安全的。配合"类型名不含 `[` 先短路、不跑正则"的检查,44 万叶子的工程导入从几十秒降到 **3.7 秒**。 递归保护:`activeDuts` 集合防止结构体递归自引用(含自身数组的 DUT)导致死循环;模板缓存"先登记空模板、再递归填充"处理自引用。 ## 八、完整路径 → 类型映射:为通讯服务 驱动树浏览与模拟器通讯共用同一棵树。模拟器按地址读写需要的是平铺映射: ``` "H.PLC_Mode" -> "INT" "CCD[0].Out_STRING" -> "STRING" "H.PLCData.XXX" -> "REAL" (结构体成员) "_EC_CommErrTbl[5]" -> "BOOL" (数组元素) "RecipeType" -> "ARRAY[0..9] OF Recipe_Data" (裸数组名, 支持整数组读写) ``` `loadVariableTypeMap` 递归枚举节点子树:结构体节点递归成员(路径加 `.成员名`),数组节点先登记裸数组名、再按边界枚举元素路径(结构体元素在树上直接递归,标量元素直接登记)。 **网络公开过滤**是通讯正确性的关键:实体 PLC 上只有设置过"网络公开"的变量才能被 EtherNet/IP 访问(SLWD 的 `NTP` 字段非空)。同一份解析结果,传 `onlyPublishedGlobals` 即过滤未公开变量——实测 44 万叶子的工程,过滤后只剩 **306,345** 个网络公开变量(顶层 3,042 → 355)。 ## 九、控制器信息提取 CPU 型号/固件/IP/任务周期等散落在不同文件中(`.oem`/`.manifest`/`.NexBuildResults`/配置 xml),用一组窄正则逐个文件累积提取: | 信息 | 来源片段 | |---|---| | 型号 + 固件 | `SetUnit "NX502-1600 Ver.1.66"` | | IP / 掩码 / 网关 | `NexCpuEipTcpIp` 配置块(NX5xx 双网口有 Port1/Port2 两段) | | 任务周期/优先级 | `TaskExecutionCondition` / `ExecutionPriority` | | 作者/语言/时间 | `<SolutionModel>` 元数据 | 一个值得记录的坑:`CompileVersion` 在 NX 工程里第一个匹配往往是占位的 `0.0,0.0`——取**第一个非占位的值**才是真实编译器版本。 ## 十、小结 整套解析器 **约 1,500 行、零第三方依赖、纯 .NET 4.0**,拆解下来的技术点: 1. ZIP 三种结构的手工解析(EOCD 回扫 / 中央目录 / 本地头)+ Deflate 解压; 2. SLWD 文本格式的键值解析与 XML 实体解码顺序; 3. OEM 实体流式扫描维护 POU 上下文; 4. DUT 递归展开 + 模板缓存 + 数组延迟展开的组合拳; 5. 完整路径 → 类型映射与网络公开过滤。 同一份实现同时服务两个程序集:驱动侧(变量树浏览)与模拟器侧(批量读写通讯),实际工程 44 万叶子导入 3.7 秒,验证与 Sysmac Studio 变量监视器导出的清单完全一致。 > 本文对应的实现代码见 `OmronNjNxEipDriver` 解决方案中的 `OmronSmc2File.cs`(驱动与模拟器两个工程保持同步的双份拷贝)。
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
收藏
・
0
点赞
・
0
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
0
)
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
Coder_shen
6
发帖
4
回帖
10
RANK
关注
私信
他的文章
[原创]零依赖解析西门子 STEP 7 工程:一个 .NET 离线变量表解析器的设计全记录
83
[原创]基于 .NET 4 实现从欧姆龙 Sysmac Studio工程文件解析全局符号变量表
65
基于C#实现CodesysV3符号变量访问
288
[原创]C#实现西门子S71200与S71500控制器优化DB块符号访问
6030
基于 C# 零依赖西门子 API 实现从博途工程文件解析符号变量树
161
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部