dlorent/es4308cfg

GitHub: dlorent/es4308cfg

该项目逆向破解了已停产 Edgecore ES4308-PoE 交换机的私有 NVRAM 配置格式,提供 Rust/Python 双编解码器、Kaitai 规范及可视化与配置转换工具。

Stars: 0 | Forks: 0

# es4308cfg — 逆向工程一款已停产交换机的配置格式 [![CI](https://static.pigsec.cn/wp-content/uploads/repos/cas/ad/ad5834178f7599af9fdda11629d49cae07f2997beec49821b2920eff5bfd50e7.svg)](https://github.com/dlorent/es4308cfg/actions/workflows/ci.yml) ![全景图:从单个配置文件渲染出的交互式仪表盘](https://raw.githubusercontent.com/dlorent/es4308cfg/main/docs/atlas.png) *`es4308ctl atlas` 将一个 `switch.cfg` 转化为交互式仪表盘 —— 前面板、 端口×VLAN 矩阵、拓扑结构、安全态势。(基于经过脱敏处理的示例渲染。)* ``` # Rust:单一 static binary — 解码为 JSON、人类可读摘要、验证 cd es4308cfg && cargo run -qr -- show ../samples/switch-port7.cfg cargo run -qr -- decode ../samples/switch-port7.cfg # -> JSON # Python:相同的解码功能,加上交互式 atlas 和 config-as-code python3 es4308ctl/cli.py atlas samples/switch-port7.cfg # -> edgecore.html just check # run everything CI runs: rust + python + kaitai + cross-check + leak ``` 该 Python 工具是**纯标准库**(无需 `pip install`),在纯净的 Python 3.9+ 环境下即可运行。Rust 编解码器 需要 `cargo`;编译 Kaitai 规范需要 Java + `kaitai-struct-compiler`(Kaitai *Web IDE* 则两者都不需要)。如果你安装了 [nix](https://nixos.org),`nix develop` 可为你提供一个 包含完整工具链的 shell,而 `just check` 会运行全套测试。 范围说明:仅针对**单一 ES4308-PoE 设备**进行了验证;其他设备/固件版本 可能有所不同。所有操作均在经过脱敏的 [`samples/`](samples/) 上运行 —— 无需硬件。 **两套编解码器,交叉验证。** Rust crate([`es4308cfg/`](es4308cfg/))是出货的 字节精确编解码器 + 单二进制 CLI(`decode`/`show`/`verify`/`dump`)。Python 工具 ([`es4308ctl/`](es4308ctl/))是 RE + 工具层(全景图、YAML、diff),并自带 其解码器。它们是**同一规范的独立实现**,CI 断言它们 在每个样本上产生完全相同的解码输出([`tools/crosscheck.py`](tools/crosscheck.py)) —— 因此这种重复是一种差异测试,而非冗余代码。 ## 它是如何被破解的 该格式是通过*差异分析*从一个 **1056 字节**的 blob 中恢复的:在 Web UI 中修改 一处,重新下载,进行 diff,重复此过程。完整方法见 [`es4308cfg/docs/RE-NOTES.md`](es4308cfg/docs/RE-NOTES.md);字节级规范见 [`es4308cfg/docs/FORMAT.md`](es4308cfg/docs/FORMAT.md)。 1. **成帧。** `CONF ` magic,随后是 TLV 记录:`BA BE | type | flag | len(u16 be) | data`。 2. `0x0c..0x0f` 处的字节读取为 `01 15 73 88`,看起来完全像是一个 CRC。它们并不是 一个字段:它是一个常量、一个**校验和字节**,以及一个设备常量。真正的完整性校验是 两个普通求和 —— 位于 `0x06:07` 的 16 位主体和(其本身读取为 `4D 57`,ASCII 码的 "MW": 第二个障眼法)以及位于 `0x0d` 的 8 位二进制补码求和。 3. **混淆字符串。** SNMP 社区字符串存储为 `char + index`;减去 位置后,`70 76 64 6f 6d 68` 即变为 `public`。 4. **一种排列。** VLAN 成员资格是一个 bitmask,其端口→bit 映射*不*是 同一映射(`p1→0, p2→1, p3→2, p4→7, …`)。同样的排列再次出现在每端口 PVID 表(`entry = PVID << 4`)、速率/双工/流控数组以及 LLDP 表中,这也正是 我们确信它是结构特征而非巧合的原因。(PoE 的启用 bitmask 使用了不同的 半字节分割顺序;某厂商乐于在单个 blob 内混合使用各种约定。) 5. 最后,通过重构进行证明。编码器重新生成了一个真实的交换机变更 (`switch.cfg` + 添加 VLAN 200),且与交换机自身的导出文件**逐字节完全一致**。 ## 已验证(及未验证)的内容 | 元素 | 状态 | 证据 | |---|---|---| | 记录成帧(18 条记录) | ✅ 已验证 | `serialize(parse(x)) == x` — `roundtrip_byte_exact` | | 双重校验和 | ✅ 已验证 | 在所有样本上重计算 == 存储值 — `checksums_valid_on_samples` | | VLAN 成员资格 + 端口排列 | ✅ 已验证 | 解码与交换机的 `VidMasks` 在 11 个 VLAN 上匹配 — `test_golden` | | 每端口 PVID(`pvid<<4`,已排列) | ✅ 已验证 | 解码出的 VLAN 集合 + PVID 数组在**所有 7 个样本**上与手动验证的预言机核对一致 — `test_pvid_and_vlans_on_every_sample` | | 添加 VLAN 编码 | ✅ 已验证 | 逐字节复现了一个真实的交换机变更 — `reproduce_switch_port7_byte_identical` | | 配置 ⇄ YAML | ✅ 已验证 | 在所有样本上均实现字节精确的往返转换 — `test_yamlio` | | Rust 解码器 == Python 解码器 | ✅ 已验证 | 在所有 7 个样本上产生一致的解码 JSON — `crosscheck.py` (CI) | | `verify` 拒绝格式错误的输入 | ✅ 已验证 | 拒绝全零 / 溢出 / 尾部垃圾数据 — `test_verify` | | Kaitai 规范 | ✅ 可编译 + 解析 | CI 编译了 `spec/es4308.ksy` 并解析了一个样本 | | 每端口速率 / 双工 / 流控 | ✅ 已验证 | 4 项变更实现字节精确的往返转换 — `test_portcfg_poe_lldp` | | PoE 启用 + 优先级(半字节分割顺序) | ✅ 已验证 | 禁用 p3/p5/p6 → 掩码 `0xbc`;p6→Critical 落在半字节分割槽位上(而非 `PORT_BIT` 的位置) — `test_poe_priority_nibble_split` | | 每端口 LLDP 启用 | ✅ 已验证 | 禁用 p4/p6 追踪到 `PORT_BIT` 尾部 — `test_portcfg_poe_lldp` | | PoE 每端口限制 | 🟡 部分 | `009a`=15.4 W 已解码;仅采样到了统一的 802.3af 值 | | STP 计时器 | 🟡 部分 | 计时器与 RSTP 页面匹配;全局启用开关在二进制文件中尚未定位 | | `0x065` 每端口数组 bit | 🟡 部分 | 已定位(在成员资格更改时翻转),具体含义尚未解码 | | 入口帧类型(仅允许带 Tag) | 🟡 部分 | 可通过 Web UI 读取;其二进制位置尚未隔离 | | 记录 `0x03/0x0d/0x10/0x11/0x13/0x14` | ❔ 未知 | 原样保留 | | 实时写入 / 恢复 | ⛔ 不安全 | 测试中导致 NVRAM 损坏 — 已被隔离([`docs/INCIDENT.md`](docs/INCIDENT.md)) | 仅有一个添加 VLAN 的编辑操作实现了端到端的逐字节复现(VLAN 200,端口 7/8);这是一个强有力的 锚点,但并非详尽无遗的覆盖。 ## 工具集 | | | |---|---| | **Decode** | `cfg → JSON`(Rust *或* Python):VLAN、每端口 tagged/untagged + PVID、速率/双工/流控、LLDP、PoE、STP 计时器、管理、SNMP、身份标识 | | **Single binary** | `es4308cfg decode\|show\|verify\|dump\|vlans` — 零依赖的 Rust CLI | | **Visualize** | `atlas` → 自包含的交互式 `edgecore.html` | | **Config-as-code** | `yamlio` — 字节精确的 `cfg ⇄ YAML` 往返转换 | | **Generate** | `generate --vid 205 --tagged 8 --untagged 5` → 一个字节能通过自检的配置。**注意:** 自检证明了*编码器的一致性*,**并不**意味着交换机会在上传时接受它 —— 刷入前请先验证。 | | **Formal spec** | [`spec/es4308.ksy`](spec/) — 在 [Kaitai Web IDE](https://ide.kaitai.io) 中打开样本,或生成 C++/Python/Go/… 解析器 | ## 仓库结构图 ``` samples/ sanitized sample configs (safe to publish; tests run on these) spec/ formal format spec — es4308.ksy (Kaitai) + usage es4308cfg/ Rust codec + single-binary CLI (parse/edit/decode/verify) + docs/{FORMAT,RE-NOTES}.md es4308ctl/ Python tool (decode/show/atlas/verify/diff/generate/yamlio + read-only web) bindiff/ a generic differential-RE harness distilled from this project (secondary) experimental/ the dangerous live write/restore path — quarantined; see docs/INCIDENT.md docs/ INCIDENT.md (post-mortem) + design notes tools/ sanitize.py (scrub captures), leakscan.py (repo-wide), crosscheck.py (rust↔python) flake.nix · justfile pinned toolchain (`nix develop`) + one-command checks (`just check`) ``` ## 安全性 配置**下载和解码是只读且安全的。** 在测试期间,向交换机进行**写入**损坏了 身份标识/MAC 记录(表单会写入固定的 NVRAM 块),因此整个 路径被隔离在 [`experimental/`](experimental/) 中,事后分析记录在 [`docs/INCIDENT.md`](docs/INCIDENT.md) 中。不要将其指向你无法恢复的交换机。 样本已**脱敏**(合成的 MAC / RFC-5737 管理用 IP;SNMP 的 `public`/`private` 是 厂商默认值,并非机密)。真实→合成的映射关系存在于被 gitignore 的清单中,而 非受追踪的源码中,并且 [`tools/leakscan.py`](tools/leakscan.py) 会在 CI 中运行,以断言**任何受追踪的文件中都不会出现真实的 标识符**(不仅仅是样本文件)。采用 MIT 许可证。
标签:JS文件枚举, Kaitai, Python, Rust, 云资产清单, 可视化界面, 无后门, 编解码器, 网络流量审计, 网络设备, 逆向工具, 逆向工程, 配置解析