nightjoker7/meshtastic-spoof-research

GitHub: nightjoker7/meshtastic-spoof-research

针对 Meshtastic 网格网络 from 字段欺骗漏洞(CVE-2025-55292)的安全研究,提供四个基于形状检测和硬件身份绑定的固件防御补丁,附带完整攻击证据与复现流程。

Stars: 0 | Forks: 0

# 阻止 Meshtastic `from` 字段欺骗攻击 — 形状检测防御 + 硬件绑定身份 **作者:** mattdeering **研究周期:** 2026-04-23 至 2026-04-24 **范围:** 针对小型本地 mesh 网络群的授权固件安全研究 → 提出了四个向上游提交的固件补丁,并附带实时攻击证据 ## 摘要 (TL;DR) Meshtastic 广播文本包**没有签名**。任何持有信道 PSK 的人都可以重写 `from` 字段,并发送伪装数据包,mesh 网络中的大部分节点会将其作为合法数据包接受。这是以下攻击事件的根本原因: - **MKE mesh 机器人** Discord 投诉事件(一个恶意节点伪装成知名社区成员), - **CVE-2025-55292**(DEF CON 33 "🥷" 事件 — 约 2000 个节点的长名称被通过伪造 NodeInfo 覆盖), - **GHSA-45vg-3f35-7ch2**(Cezar Lungu 于 2026 年 2 月的披露 — NodeInfo 欺骗强制设置了 HAM 模式标志,将 PKC DM 降级为明文)。 本代码库是**四个小型上游补丁**的公开参考,它们共同阻止了当今的攻击,并为永久修复扫清了障碍: | # | 补丁 | 目标 | 阻止 | |---|---|---|---| | A | **self-RX 异常丢弃** | `meshtastic/firmware:develop` | 丢弃声称 `from = our own nodeNum` 的欺骗包 | | B | **hop_start 异常丢弃** | `meshtastic/firmware:develop` | 拦截每一个通过标准 `Router::send` 发送的欺骗包 — 在 mesh 网络范围内每个受防御的跳站节点上阻止其传播 | | C | **HWIdentity Tier A**(不烧写 eFuse) | `meshtastic/firmware:develop`,修复 #8211 | 恢复出厂设置 + 刷入固件后身份保持不变 → 为签名机制提供安全的推广路径 | | D | **针对 PR #9610 的两项修复** | `weebl2000/meshtastic-firmware:fix/xeddsa-review` | 为 XEdDSA 签名工作扫清障碍,这是长期的永久修复方案 | 补丁 A + B 捕获了目前现实世界中攻击者(MKE 机器人、DEF CON 🥷、Cezar Lungu PoC)所使用的 100% 的攻击特征。补丁 C 使得签名机制的推广(通过上游的 #7602 + #9610)变得安全,且不会破坏节点的首次可见优先 (first-seen-wins) 缓存。补丁 D 有助于将签名工作合并到上游。 每个补丁的代码量均 ≤250 行,随附编译时选择退出 (opt-out) 选项,并已在真实硬件上完成构建、刷写和实时攻击测试。 ## 攻击细节 `meshtastic_MeshPacket` 的 `from` 字段是无符号的。在广播数据包(`TEXT_MESSAGE_APP`、`NODEINFO_APP`、`POSITION_APP`、`TELEMETRY_APP`、`TRACEROUTE_APP`)上,信道 PSK 提供了机密性,但**不提供真实性**。任何持有 PSK 的节点——包括整个全球公共 mesh 网络中使用默认 `AQ==` PSK 的每一个节点——都可以构造一个声称来自任何其他节点的有效加密数据包。 **上游状态:** 标准修复方案是 XEdDSA 数据包签名。Jonathan Bennett 的 PR #7602 在 2025 年引入了框架(仍为草案状态,自 2026 年 2 月起停滞)。Weebl2000 的 PR #9610 包含了实质内容(签名涵盖 `fromNode + packetId + portnum`,验证失败时丢弃数据包,设置 `HAS_XEDDSA_SIGNED` 位),但由于两个小的 UX 问题卡在了 CHANGES_REQUESTED 状态,此处的补丁 D 正解决了这些问题。 **在此期间,形状检测能为您带来什么:** 两种防御机制都针对标准 `meshtastic-python` + `sendText(fromId=...)` 攻击路径无法避免的特定结构特征——攻击者的 `Router::send()` 包含: ``` if (isFromUs(p)) p->hop_start = p->hop_limit; ``` 对于合法的发送者,`isFromUs(p)` 为 true → `hop_start` 会被标记为与 `hop_limit` 相等。对于将 `p->from = VICTIM_NODENUM` 的欺骗者来说,`isFromUs()` 在攻击者自身的 Router::send 处会返回 false → 线路上传输的 `hop_start` 会保持为 0。合法的中继包会保留原始标记(只有 `hop_limit` 递减),因此 `hop_start > 0` 在每个跳站都成立。`hop_start == 0 AND hop_limit > 0` 在合法的原始数据包中是不可能存在的特征,而这正是我们见过的每一个现实世界攻击者所产生的情况。 这些防御机制存在于 `Router::perhapsHandleReceived()` 中,在任何 OTA 数据包上触发,与端口号无关(同时捕获 NodeInfo、Text、Position 和 Telemetry),并在每个受防御的节点上运行——而不仅仅在被冒充的受害者节点上。运行此固件的 ROUTER_LATE 会拒绝中继欺骗包 → 从而切断了使欺骗包在全网传播的 5W 传播途径。 ## 语料库验证(零误报声明) 在部署之前,我们在多日的真实本地 mesh 流量捕获数据上运行了 hop_start 检查: - **670 个合法的 OTA 数据包** → `hop_start` 均匀分布在 `{1, 2, 3, 4, 5, 6, 7}` - **30 个 `hop_start = 0` 的数据包** → 每一个都是我们在研究期间自己生成的攻击包,已通过数据包 ID + 负载长度交叉验证 在该语料库中,**零个合法数据包**会被形状检测防御机制拦截。这种选择性是经验性的,而非理论性的。 `tools/extract_baseline_metrics.py` 可以在任何 meshlogger JSONL 存档上重现此分析。 ## 实时攻击证据 完整的报告位于 `evidence/` 中,包含每个数据包的时间戳、数据包 ID 以及与未防御见证节点的 A/B 交叉比对。 - **`evidence/phase_0/BASELINE.md`** — 未缓解的攻击成功。展示了攻击通过 5W ROUTER_LATE 传播到位于不同物理位置的节点(交叉比对的数据包 ID 证明了中继)。 - **`evidence/phase_1/SUMMARY.md`** — 阶段 1a self-RX 测试:21 次 SPOOF 丢弃,10/10 个唯一数据包 ID 被丢弃,0 次误报。 - **`evidence/phase_1/MESH_PREVENTION.md`** — 阶段 1e hop_start 测试:20 次丢弃,10/10 个唯一数据包被丢弃(包括在 `hop_limit=2` 处的中继副本),0 次误报。还包括与同一 RF 邻域中未防御见证节点的 A/B 交叉比对。 - **`evidence/phase_3/SUMMARY.md`** — HWIdentity Tier A 持久性测试:公钥位在两次恢复出厂设置周期中完全一致。 - **`evidence/phase_3/COMBINED_5LAYER_TEST.md`** — 组合固件(全部 4 个补丁 + 上游 XEdDSA 基础)实时攻击:**19 次丢弃 / 捕获 9 个唯一数据包 ID / 0 次误报 / 阶段 1a 和阶段 1e 在同一突发中均被触发**。身份稳定。 ## 提议的 PR 所有四个 PR 的正文都已准备好,可直接粘贴到 `pr-bodies/` 中: - [`pr-bodies/PR_A_self_rx.md`](pr-bodies/PR_A_self_rx.md) — self-RX 异常丢弃 - [`pr-bodies/PR_B_hop_start.md`](pr-bodies/PR_B_hop_start.md) — hop_start 异常丢弃 - [`pr-bodies/PR_C_hw_identity.md`](pr-bodies/PR_C_hw_identity.md) — HWIdentity Tier A - [`pr-bodies/PR_D_xeddsa_fixup.md`](pr-bodies/PR_D_xeddsa_fixup.md) — 针对上游 #9610 的修复 这些 PR 所基于的固件分支已推送到配套的代码分叉:[`nightjoker7/firmware`](https://github.com/nightjoker7/firmware)(分支:`defense/self-rx-drop`、`defense/hw-identity-tier-a`、`xeddsa-fixup`)。 这些 PR 是相互独立的——审查者可以按任何顺序挑选任何子集进行审查。 ## 复现测试 参见 [`REPRODUCIBILITY.md`](REPRODUCIBILITY.md)。需要两块 Meshtastic 开发板(ESP32-S3 和/或 nRF52840)、匹配的区域/预设以及 30 分钟的时间。 `tools/` 包含了最小的辅助脚本: - `attack_test_capture.py` — 用于攻击测试的双节点发布/订阅监视器 - `test_hwidentity_v2.py` — 针对补丁 C 的两次循环恢复出厂设置持久性测试 - `extract_baseline_metrics.py` — 语料库验证脚本 ## 已知的局限性 - **双节点测试设置。** 未在大型网络群中进行过规模测试。形状检测逻辑在不同节点数量下表现一致,但在大型网络群上进行长时间的 soak 测试观察将是非常有价值的。 - **hop_start 丢弃带来的旧版固件风险。** 如果网络群中存在由于固件过于老旧而不在发起时标记 `hop_start` 的设备,其合法流量将被拦截。我们在语料库中观察到 0 台此类设备。可以通过 `-DMESHTASTIC_DISABLE_SPOOF_HOPSTART_DROP=1` 按设备选择退出。 - **手工制作原始 protobuf 并手动标记 `hop_start = hop_limit` 的高级攻击者**可以绕过形状检查。XEdDSA 签名(通过 #9610)可以捕获这种情况;补丁 D 有助于为推进实现该目标的上游工作扫清障碍。 - **Tier A HWIdentity 无法抵御固件转储攻击者**(威胁面与今天相同——如今私钥已存在于 `/prefs` 中)。Tier B(eFuse HMAC 烧写)是未来的跟进方向,不在本文讨论范围内。 - **仅在 ESP32-S3 + nRF52840 上进行了测试。** Portduino、STM32 和 RP2040 的路径编译无异常,但未进行硬件测试。在缺少相关原语的目标平台上,补丁会优雅地失效(回退跳过)。 ## 本代码库中不包含的内容 - **攻击固件源代码。** 它位于固件分叉上一个明确标记的研究分支中,由 `-DSPOOFTEST_ENABLED=1` 控制,并且从未合并到任何受跟踪的分支中。未在此处重复包含。 - **原始 meshlogger JSONLs。** 包含了我们在无意中捕获的真实社区成员的流量;不属于我们,因此无权分发。 - **固件二进制文件。** 审查者可从源代码构建;二进制文件的 MD5 值记录在证据文件中,但此处不提供二进制文件托管。 - **HackRF IQ 捕获。** 多 GB 的文件,且与特定主机环境相关。 ## 致谢 感谢正在致力于永久修复的 Meshtastic 维护者和贡献者——特别是 jp-bennett(XEdDSA 框架 #7602)和 weebl2000(#9610)。这项工作旨在补充而非替代他们的努力:在今天为实时攻击类别提供保护,为明天的签名机制铺平道路。
标签:CVE-2025-55292, DEF CON, GHSA-45vg-3f35-7ch2, LoRa, Meshtastic, Spoofing, UML, XEdDSA, 加密通信, 固件安全, 安全补丁, 无线网状网络, 渗透测试证据, 漏洞修复, 物联网安全, 签名验证, 网络协议分析, 网络安全培训, 节点伪造, 身份欺骗, 逆向工具