新买的小米 BE10000 Pro,原本只是想给它配置主 WiFi 和 IoT WiFi。UCI 配置方案已经准备好了,接下来需要一个能长期使用的 root 登录入口。
用 XMiR-Patcher 开启临时 SSH 很顺利,安装永久 SSH 时却出现了两行互相打架的提示:前一行说安装完成,后一行说内核模块加载失败。继续往下查,涉及到了原厂 OpenWrt 衍生固件的启动逻辑、内核模块 ABI、MTD 分区写保护,以及恢复出厂时配置究竟如何重建。
这次把源码核查、远程操作和备份校验交给 Codex 协助执行,我提出需求,并要求实际做普通重启和恢复出厂验收。下面按设备上的观测、分区回读和测试结果记录整个过程。
最终在这台 RP04、原厂 1.0.70 上验证通过的是:恢复出厂后仍可直接通过 Telnet 登录 root;SSH 重新开启并安装软固化脚本后,可以经受普通重启。恢复出厂会清除 SSH 软固化配置,升级其他固件没有测试。
记录日期:2026 年 10 月 31 日。本文中的分区结构和命令均对应这一次实测,涉及分区写入与恢复出厂,复现前需要核对自己的型号、固件和分区表,并完成本机备份。
为什么需要原厂固件里的 root
我这次的需求是直接操作无线、网络和防火墙配置,为后续的主 WiFi、IoT WiFi 方案准备管理入口。需要用到的配置文件包括:
/etc/config/wireless
/etc/config/network
/etc/config/firewall
这些配置由 UCI 管理。root 是这套 Linux 系统的超级用户,SSH 和 Telnet 则是进入系统的两种方式。能打开路由器的 Web 管理页,与能拿到 root shell,是两件不同的事。
机器全程保持小米原厂固件,没有刷入社区 OpenWrt,也没有更换内核。在这台机器上实际读到的信息是:
| 项目 | 实测值 |
|---|---|
| 产品 | 小米 BE10000 Pro |
| 设备型号 | RP04 |
| 原厂固件 | 1.0.70 release,CN |
| CPU 架构 | arm64 / aarch64 |
| Linux 内核 | 6.6.47,SMP PREEMPT |
| OpenWrt 发行标识 | 18.06-SNAPSHOT |
| target | ipq54xx/generic |
| 无线配置中的驱动类型 | qcawificfg80211 |
这里的发行标识、内核版本和无线驱动都来自原厂系统。看到 UCI 或 OpenWrt 的名称,并不意味着可以直接套用社区某个发行版的全部教程。具体配置是否被厂商脚本重写,还需要另行验证。
这篇先把 root 入口建立并验收,主 WiFi 和 IoT WiFi 的具体拓扑、隔离规则及配置不在本次测试范围里。
先区分三种状态:临时开启、脚本软固化和 bdata 固化
“开启 SSH”和“永久 root”很容易被混在一起说。实际操作前,我先把目标拆成了三个层次:
| 层次 | 主要做什么 | 需要怎样验证 |
|---|---|---|
| 临时开启 | 在当前启动周期里修改开关、启动服务,取得 root | 新建连接并执行 id |
| 脚本软固化 | 把恢复 SSH 的脚本和启动入口放到持久配置中 | 普通重启后,不重跑漏洞即可登录 |
| bdata 固化 | 将解锁标志写进独立的 bdata 分区,供原厂启动流程读取 | 实际恢复出厂后,重新连接并取得 root |
社区常把第三种叫“硬固化”。在本文里,它具体指修改 bdata 中的环境参数,并不意味着任意固件升级后都保证可用。
还要把 root 入口是否保留 与 SSH 是否自动启动 分开验收。本次最后保留下来的基础入口是 root Telnet,SSH 依然需要相应的服务配置和启动脚本。
第一次操作:SSH 开了,永久安装却报错
使用的工具是 openwrt-xiaomi/xmir-patcher。它检测到 datacenter7,成功启动了 22 端口上的 SSH 服务。
进入后执行 id,得到:
uid=0(root) gid=0(root)
这一步确认了临时 root 可用。接着执行菜单 6,安装永久 SSH,工具先显示 bdata 中的几个开关都没有开启:
bdata: telnet_en = 0, ssh_en = 0, uart_en = 0
CPU arch: arm64
Kernel: Linux XiaoQiang 6.6.47 ... SMP PREEMPT ... aarch64 GNU/Linux
上传脚本和模块、执行安装之后,又出现了:
Ready! The Permanent SSH patch installed.
Patch for bdata result: ERROR: cannot load xmir_patcher.ko!
单看第一行,很容易以为已经结束了。但 bdata 的三个 *_en 标志仍然都是 0,这与“永久安装完成”的直觉明显对不上。
先做对照:普通重启成功,恢复出厂失败
在尝试修复之前,我先要求做一组实际对照。
普通重启后,没有再次运行漏洞,使用原来的 root 凭据可以直接登录 SSH。这说明至少有一部分持久化生效了。
随后执行原厂恢复出厂流程。路由器重新启动后,Web 管理页回到首次设置状态,接口中的 inited 也变成 0。页面恢复可访问后,继续观察了超过 100 秒,22 端口仍没有开启。
结果很明确:
| 操作 | 当时的结果 |
|---|---|
| 普通重启 | root SSH 可直接登录 |
| 恢复出厂 | SSH 未保留,22 端口关闭 |
这组测试先证明了工具并非完全没起作用。它已经安装的启动脚本能经受普通重启,而恢复出厂会清掉这部分用户配置。
对照本机挂载和启动脚本,也能看出几个相关位置的区别:
| 位置 | 在这台机器上的作用 |
|---|---|
/etc/init.d/dropbear |
位于运行时重建的 /etc 中,普通启动时需要重新修补相关条件 |
/etc/config |
持久配置目录,普通重启保留,恢复出厂会重建 |
/etc/crontabs |
持久保存 cron 和补丁脚本,恢复出厂同样会清理 |
/etc/shadow |
指向数据分区中的账户文件;恢复出厂后 root 密码会回到出厂生成值 |
| bdata 分区 | 独立保存设备环境参数,本次恢复出厂后修改值仍保留 |
“放在持久目录里”只能说明普通断电重启后有机会保留。恢复出厂是主动重建配置的流程,需要单独测试。
读源码和 dmesg:模块不兼容,成功提示也不完整
检查 install_ssh.py 后,可以把菜单 6 的执行过程拆成两段。
第一段安装 SSH 补丁:把脚本移到 /etc/crontabs/patches,写入 cron,添加防火墙启动钩子,设置 NVRAM,并执行一次补丁。这与普通重启成功的现象一致。
第二段尝试修改 bdata。它需要先加载 xmir_patcher.ko,再请求模块解除 bdata 写保护,最后执行 bdata set 和 bdata commit。
本机失败在第一步。重置之前读取的内核日志是:
module xmir_patcher: .gnu.linkonce.this_module section size must match the kernel's built struct module size at run time
这条错误比脚本日志中的“cannot load”具体得多。Linux 的模块加载代码会检查模块中 .gnu.linkonce.this_module 段的大小是否与运行时 struct module 一致,本次就在这里被拒绝了。
工具如何适配内核版本也值得看一眼。它根据 6.6 + arm64 + PREEMPT 选到预编译模块,然后修改其中的 vermagic:
原模块:6.6.0-XMiR-Patcher SMP preempt mod_unload aarch64
上传版:6.6.47 SMP preempt mod_unload aarch64
对比这两个本地文件,模块结构段仍都是 640 字节。替换版本字符串并没有改变结构布局,实际内核日志已经证明存在 ABI 不兼容。具体由哪个编译配置或厂商补丁造成,这次没有继续定位,也没有强制加载模块。
源码里还有两个影响诊断的细节:
- bdata 步骤在模块加载失败后直接退出,尚未执行后续的 bdata 写入。
- “Permanent SSH patch installed”无条件打印;
insmod的输出捕获又没有把标准错误一起保存。
因此,这次应当被报告为“SSH 脚本安装成功,bdata 修改失败”。工具的成功提示不能代替分区回读和重启验收。
社区里的另一条路线:临时进入工厂模式
继续查社区讨论,在 BE10000 Pro 的上游 Issue 154 中找到了两类反馈。
Xierfei提到过 Telnet 固化成功,再手动开启 SSH、安装脚本的经历;qdore 的后续评论给出了通过 crash 标志进入工厂模式、写入 bdata、再清除标志的三次重启步骤。
同一讨论中也有人报告直接 bdata commit 出现 I/O 错误,还有人质疑过硬固化步骤。它提供了一条值得验证的路线,完整的结果仍需要在自己的设备上确认。
这条路线的关键是改变 bdata 的可写状态:
正常模式
ft_mode=0,bdata 只读
↓
写入 crash 工厂模式标志并重启
↓
工厂模式
ft_mode=1,bdata 可写
↓
用原厂 bdata 命令提交解锁标志
↓
清除 crash 标志并重启
↓
回到正常模式,保留 bdata 中的修改
这样就可以通过原厂工具提交 bdata,不必先完成不兼容内核模块的适配。接下来的工作重点,是备份、检查前置条件,以及在每次重启后读取实际状态。
备份:需要保存到电脑,也需要验证能读对
开始写分区之前,先完成了原始镜像和配置备份。重要分区各自独立读取两遍,检查长度、比较内容,并计算 SHA256,确认文件已经保存到电脑。
本次保存的主要内容如下:
| 内容 | 本机大小 | 用途 |
|---|---|---|
| 原始 bdata | 512 KiB | 保留修改前的完整设备环境 |
原始 crash |
512 KiB | 退出工厂模式后核对恢复结果 |
引导环境 0:APPSBLENV |
512 KiB | 保存操作前的引导环境 |
| ART 校准数据 | 1536 KiB | 保存本机校准数据,本次不修改它 |
| 许可证分区 | 512 KiB | 保存本机许可证数据,本次不修改它 |
| 引导配置 | 1024 KiB | 记录最终恢复出厂测试前的状态 |
| 配置、账户、SSH 密钥和启动脚本 | 归档文件 | 留存操作前后配置,辅助恢复和核对 |
其中 bdata、crash 和引导环境在硬固化写入前备份;ART、许可证和引导配置在后续补齐。这些镜像是 MTD 分区的逻辑内容,不含 NAND OOB,也不是整机完整固件备份。
bdata 中包含本机环境参数,配置归档还可能包含密码和私钥。原始备份应当私密保存,不适合作为公开教程的附件,更不能直接拿其他设备的 bdata 覆盖本机。
bdata 的 CRC 范围也要核对
本机 bdata 分区有 512 KiB,但环境区是前 64 KiB:开头四字节存放小端 CRC32,校验范围是其后到 64 KiB 末尾的数据。将整个 512 KiB 都拿来计算 CRC,会得到错误的校验结论。
针对本机格式,离线检查可以写成:
from pathlib import Path
import zlib
image = Path("bdata-original.bin").read_bytes()
assert len(image) == 0x80000
stored_crc = int.from_bytes(image[:4], "little")
actual_crc = zlib.crc32(image[4:0x10000]) & 0xffffffff
assert stored_crc == actual_crc
这里的大小和范围来自本次镜像验证,不能据此推定其他小米路由器都采用相同格式。
写入工厂模式标志
先通过 /proc/mtd 确认本机的分区名称和大小。后面的写操作使用分区名,读取 /sys/class/mtd/mtdX/flags 时,mtdX 也必须按自己的分区表替换。
写入前,本机观测到:
ft_mode=0
bdata flags=0x0
crash 整分区均为 0xFF
工厂模式的启动脚本还可能运行硬件测试。检查本机脚本后,确认 auto_flashtest 不为 once,没有满足闪存压力测试的触发条件;取得工厂模式 root 后,也停止了 systestctl 测试服务。
以下各步是本机的执行顺序,每一步成功并回读核对后,才继续下一步。
生成明确的四字节文件
社区使用的标志是:
A5 5A 00 00
本次用八进制转义生成文件,避免依赖 shell 变量保存空字节:
printf '\245\132\000\000' > /tmp/rp04-factory-marker.bin &&
test "$(wc -c < /tmp/rp04-factory-marker.bin)" -eq 4 &&
mtd write /tmp/rp04-factory-marker.bin crash
然后回读整个 crash。本机原始分区全为 0xFF,因此可以明确核对:前四字节为 A5 5A 00 00,其余仍全部为 0xFF。确认后再重启。
如果原始 crash 含有其他数据,就不能假设这个短文件写入方式会保留它们。分区写入包含擦除行为,原始内容需要事先理解和备份。
重连后检查模式和写保护
工厂模式启动后,通过 Telnet 取得了 root,并读取到:
uid=0(root) gid=0(root)
ft_mode=1
bdata flags=0x400
在这台机器上,bdata 此时已可写。确认模式之后执行:
/etc/init.d/systestctl stop
到这里还没有修改 bdata,只确认了进入工厂模式后确实可以解除本次遇到的写保护限制。
用原厂命令修改 bdata,再比较完整镜像
接下来使用原厂 bdata 命令设置四个值:
bdata set telnet_en=1 &&
bdata set ssh_en=1 &&
bdata set uart_en=1 &&
bdata set boot_wait=on &&
bdata commit
没有手工编辑二进制环境区,也没有自行计算新 CRC 后刷回镜像。参数提交由原厂工具完成,提交后的结果则通过完整回读验证。
修改后的分区又读取了两遍,检查了四件事:
- 两次读取长度都为 512 KiB,内容完全相同。
- 前 64 KiB 环境区的 CRC32 正确。
- 解析修改前后的全部环境变量,仅有预期的四项变化。
- 64 KiB 环境区之外的数据完全未变。
变量差异如下:
| 参数 | 修改前 | 修改后 |
|---|---|---|
ssh_en |
0 |
1 |
telnet_en |
0 |
1 |
uart_en |
0 |
1 |
boot_wait |
不存在 | on |
镜像验证通过后,再把相同的设置写入 NVRAM:
nvram set ssh_en=1 &&
nvram set telnet_en=1 &&
nvram set uart_en=1 &&
nvram set boot_wait=on &&
nvram commit
随后按社区步骤再重启一次,在工厂模式下确认 bdata 中的值仍然保留。
清除临时标志,回到正常模式
完成 bdata 修改之后,需要退出工厂模式:
mtd erase crash
因为本机原始 crash 是全 0xFF,擦除后可以直接将完整回读与原始镜像逐字节比较。两份内容一致,才继续重启。
正常启动后的结果是:
ft_mode=0
bdata flags=0x0
四个 bdata 标志仍然保留,Telnet 可以正常登录 root。这个状态很重要:可写的工厂模式已经退出,原先只读的分区重新受到保护,保留的是之前成功提交的环境参数。
本机 /lib/preinit/31_restore_nvram 的恢复出厂分支会执行 bdata sync,把 bdata 中的参数同步到 NVRAM。这解释了独立保存解锁标志的意义;接下来用真实恢复出厂,验证完整的启动和登录链路。
关键验收:真正恢复出厂,然后直接登录 root
执行前先确认当前已经是 ft_mode=0,bdata 与修改后的镜像一致,crash 与原始备份一致。
然后调用本机原厂的恢复出厂入口:
/usr/sbin/restore_defaults.sh
这个脚本会设置原厂重置标志并重启。测试中没有恢复此前的用户配置,也没有重新利用 datacenter7 或其他漏洞。
启动后依次确认:
| 检查项 | 实测结果 |
|---|---|
| 管理页面 | 回到首次设置状态 |
init_info.inited |
0 |
| 启动标识 | 与重置前不同 |
ft_mode |
0 |
| root Telnet | 可直接登录,执行 id 得到 UID 0 |
| bdata 完整回读 | 与修改后镜像逐字节一致 |
crash 完整回读 |
与原始全 0xFF 镜像一致 |
关键输出摘录:
uid=0(root) gid=0(root) groups=0(root)
ft_mode=0
bdata_telnet_en=1
nvram_telnet_en=1
bdata_ssh_en=1
nvram_ssh_en=1
bdata_uart_en=1
nvram_uart_en=1
bdata_boot_wait=on
nvram_boot_wait=on
bdata_flags=0x0
恢复出厂后,root 密码也回到了本机派生的出厂密码。 临时开启 SSH 时设置的密码没有继续保留;使用本机出厂密码成功登录后,又独立复查了一次。设备密码保存在私密备份说明中,文章不公开具体值。
这组结果证明了本次的 root Telnet 入口可以经受真实的恢复出厂。此时 Web 仍是未初始化状态,root 登录验证已经完成。
恢复 SSH,并让主机密钥经受普通重启
有了恢复出厂后仍可使用的 root Telnet,就可以继续处理 SSH。
本机原厂 /etc/init.d/dropbear 还有 release 通道检查。即便 NVRAM 中 ssh_en=1,仍需修补这个服务启动条件。因此通过 Telnet 执行:
nvram set ssh_en=1 && nvram commit
sed -i 's/= "release"/= "XXXXXX"/g' /etc/init.d/dropbear
/etc/init.d/dropbear enable
/etc/init.d/dropbear restart
这段替换针对本机查看过的启动脚本。不同固件先读自己的文件,不能假设条件写法相同。root 密码可以通过 passwd 设置为自己保存的管理口令。
如果旧 Dropbear 与 OpenSSH 客户端协商时提示只提供 ssh-rsa,可以在单次连接中指定:
ssh -o HostKeyAlgorithms=+ssh-rsa root@192.168.31.1
随后恢复基本初始化和 SSH,再运行原工具菜单 6。此时它读到的是:
bdata: telnet_en = 1, ssh_en = 1, uart_en = 1
工具跳过了需要加载内核模块的 bdata 修改步骤,正常安装 SSH 启动补丁、cron 和防火墙钩子。普通重启时,这些脚本负责重新开启 SSH。
同时保存 SSH 主机密钥
这里还处理了一个容易影响长期使用的问题:SSH 服务端的主机密钥位于 /etc/dropbear,需要保存到持久配置目录,让客户端在普通重启后仍能识别同一台机器。
本次将当前密钥复制到补丁目录,再通过 Dropbear 的 rsakeyfile 配置引用:
cp -p /etc/dropbear/dropbear_rsa_host_key \
/etc/crontabs/patches/dropbear_rsa_host_key
chmod 600 /etc/crontabs/patches/dropbear_rsa_host_key
uci set dropbear.@dropbear[0].rsakeyfile='/etc/crontabs/patches/dropbear_rsa_host_key'
uci commit dropbear
chmod 755 /etc/crontabs/patches/ssh_patch.sh
最后再普通重启一次,直接通过 SSH 登录 root,确认启动标识变化、ft_mode=0、bdata 未变、SSH 主机密钥也未变。防火墙启动钩子、cron 和补丁执行日志均存在。
恢复出厂仍会清除这一层 SSH 配置和密钥备份。 重置后先使用保留的 root Telnet 入口,再恢复 SSH;主机密钥在普通重启和恢复出厂两种场景下的预期也应当分别看待。
这次排障中值得保留的几个细节
要证明真的重启了
本次辅助脚本有一次过早关闭 Telnet 连接,重启命令没有实际执行。旧会话断开、随后还能重新连上,并不能证明系统已经重新启动。
实际验收时比较了:
cat /proc/sys/kernel/random/boot_id
uptime
发现启动标识没变后,修正了命令发送方式,重新完成重启和正常模式检查。公开记录里只保留“前后已变化”的结论,原始标识留在本地验收文件中。
连接超时需要结合链路和设备状态
本次部分启动阶段耗时较长,有线网口也曾暂时显示未连接。检查时分别看了物理链路、管理页是否恢复、22/23 端口是否可达,以及实际登录结果。
开启 Dropbear 后,主机密钥生成也可能需要等待。最终以新连接认证成功、执行 id 得到 root 为准。
密码、启动脚本和 bdata 是不同的持久化对象
这次恢复出厂后,bdata 保留、root Telnet 可用,但 root 密码回到了本机出厂生成值,SSH 启动配置则被清除。
因此,“重置后保留 root 入口”没有包含“用户设置的所有凭据和服务配置都保留”。这也是恢复出厂验收必须真正登录一次的原因。
Telnet 不加密传输,保留该入口后要限制管理网络的访问范围,不向 WAN 暴露它。root 口令、SSH 私钥和设备分区镜像都应私密保存。
最终结果和适用范围
| 场景 | 本次结果 |
|---|---|
通过 datacenter7 临时开启 SSH |
成功,root 可登录 |
| 最初仅软固化后普通重启 | root SSH 可直接登录 |
| 最初仅软固化后恢复出厂 | SSH 未保留,22 端口关闭 |
| 工厂模式下修改 bdata | 成功,完整回读与 CRC 校验通过 |
| 清除工厂模式标志后启动 | ft_mode=0,root Telnet 可用 |
| 硬固化后实际恢复出厂 | 无需漏洞,root Telnet 可直接登录 |
| 恢复出厂后的分区验证 | bdata 与修改后镜像相同,crash 与原始镜像相同 |
| 恢复 SSH 软固化后再次普通重启 | root SSH 可登录,SSH 主机密钥不变 |
| 升级其他固件、其他型号、UART 通信 | 未做独立验证 |
原始镜像、修改后镜像、配置归档、SHA256 清单和机器可读验收记录,最终整理成了本地私密备份包。后续可以继续执行准备好的 UCI 配置;无线方案本身仍需要自己的连通性和重启验收。
这次最有用的排障习惯,是给每种“成功”配一个能直接观测的条件:模块要真的加载,分区要能完整回读,重启要有新的启动标识,恢复出厂要回到首次设置状态,root 入口要用新连接实际认证。
参考与社区反馈
- XMiR-Patcher 项目
- BE10000 Pro 临时 root 方法与讨论
- Xierfei 关于 Telnet 固化及 SSH 的反馈
- qdore 提供的工厂模式与 bdata 操作步骤
- 同型号同内核的模块加载失败记录
- Linux 6.6 的模块结构大小检查
- 我补充到上游的完整中文实测反馈
感谢提供方法和讨论记录的 Xierfei、qdore、项目维护者以及其他参与者。这次在本机补上了实际恢复出厂后的 root 登录验证,也把结果反馈回了原讨论。
版权属于:一名宅。
本文链接:https://zhaiyiming.com/archives/83-104.html
转载时须注明出处及本声明