dlorent/es4308cfg
GitHub: dlorent/es4308cfg
该项目逆向破解了已停产 Edgecore ES4308-PoE 交换机的私有 NVRAM 配置格式,提供 Rust/Python 双编解码器、Kaitai 规范及可视化与配置转换工具。
Stars: 0 | Forks: 0
# es4308cfg — 逆向工程一款已停产交换机的配置格式
[](https://github.com/dlorent/es4308cfg/actions/workflows/ci.yml)

*`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, 云资产清单, 可视化界面, 无后门, 编解码器, 网络流量审计, 网络设备, 逆向工具, 逆向工程, 配置解析