本文记录一次对自有光猫的硬件调试。核心思路不是破解 Web 页面,也不是刷写 NAND,而是通过 3.3 V UART 进入 TcBoot,将临时 OpenWrt initramfs 加载到内存,随后只读挂载原厂配置分区,读取本机保存的超级管理员凭据。
请只操作自己拥有或明确获授权的设备。文中的命令示例使用脱敏占位符;实拍结果图按记录现场保留,仅隐藏了设备序列号。
设备信息
这次操作的设备是甘肃电信定制版烽火 HG6543C1:
- 型号:FiberHome HG6543C1
- 硬件版本:V3.0
- 原厂软件版本:V3.00.M5002
- SoC:EcoNet EN751221(Web 页面显示 EN7526 系列)
- CPU:双核 MIPS 34Kc,900 MHz
- 内存:256 MiB
- Flash:Winbond W25N01G,128 MiB SPI NAND
- 原厂内核:Linux 3.18.21
- Bootloader:EN751221 TcBoot/Bootbase
不同地区、硬件版本或运营商批次的 PCB、分区表和配置路径可能不同,不要把本文地址直接套用到别的型号。

前置条件
硬件
- CH340G、CP2102 或 FT232 等 USB 转 TTL 模块
- TTL 电平为 3.3 V
- 杜邦线三根
- 万用表
- 小螺丝刀和绝缘工具
- 光猫原装电源

不要使用主板 RS-232 串口,也不要给 UART 接口输入 5 V。USB-TTL 模块的 VCC 不接光猫,只接 TXD、RXD、GND。
Linux 软件
本文使用 Arch Linux:
sudo pacman -S picocom dtc |
安装 uv 后,Python 依赖由脚本临时管理,不需要污染系统 Python。
临时 OpenWrt 镜像来自实验性的 EN751221 OpenWrt 构建;本文实际使用 v2026-06-03,不要用后续版本套用本文的固定偏移:
请理解:这是实验镜像,只用于 RAM 启动和只读检查,不应直接刷入运营商光猫。
第一步:确认 UART 引脚
HG6543C1 主板下缘附近有一组 JP1 排针。本机实测结果如下:
在本文照片的观察方向,JP1 编号是从右向左的;因此照片中从左到右依次为 JP1-5、JP1-4、JP1-3、JP1-2、JP1-1。
| 光猫引脚 | 功能 | 接 USB-TTL |
|---|---|---|
| JP1-2 | 光猫 TX | RXD |
| JP1-3 | 光猫 RX | TXD |
| JP1-4 | GND | GND |
其余引脚不要连接。

接线前断开光猫电源。用万用表确认 GND,并确认 USB-TTL 的 TXD 对 GND 空闲电压约为 3.3 V。接线规则是交叉连接:光猫 TX 到模块 RXD,光猫 RX 到模块 TXD。
最终接线:
HG6543C1 JP1-2 (TX) ─── CH340G RXD |
第二步:读取启动日志并进入 Bootloader
插入 USB-TTL,确认设备节点:
ls -l /dev/ttyUSB* |
打开串口:
sudo picocom -b 115200 --flow n /dev/ttyUSB0 |
参数为 115200 8N1,无硬件/软件流控。给光猫上电后,应看到类似内容:
EN751221 ... free bootbase |
在倒计时内按任意键:
bldr> |
查看命令:
bldr> help |
本机 Bootloader 提供 go、decomp、dump、jump、flash、xmdm 等命令。本文只使用 xmdm 和 jump,不要执行 flash、spi nand rw test、bdstore 或其他写入命令。

第三步:先把通用镜像识别的内存从 64 MiB 改成 256 MiB
3.1 这一修改解决什么问题
这里使用的是临时运行在内存中的 OpenWrt initramfs:
openwrt-econet-en751221-en751221_generic-initramfs-kernel.bin |
可以把这个文件简单理解成三部分:Linux 内核、临时根文件系统,以及放在文件末尾的“硬件说明书” DTB。内核会根据 DTB 判断这块板子有多少内存、闪存怎样分区。
这份通用镜像的 DTB 把内存写成了 0x04000000 字节,也就是 64 MiB;HG6543C1 实际装有 256 MiB。直接启动原镜像虽然能够进入 OpenWrt,但系统只会使用 64 MiB,几十秒后便可能耗尽内存、触发 OOM,然后自动重启。
这一节只改电脑里的镜像文件,不会改动光猫的 NAND 闪存。目标是把 DTB 中的内存大小改成:
0x10000000 字节 = 256 MiB |
3.2 准备 DTB 工具
本文是在 Arch Linux 上操作的。先确认三个命令是否存在:
command -v dtc |
如果没有输出,安装 dtc:
sudo pacman -S dtc |
dtc 用于在二进制 DTB 与可读的 DTS 文本之间转换;fdtget 用于读取属性,fdtput 用于修改属性。
3.3 先核对原始镜像,不能跳过
下面的 DTB 偏移量只适用于本文使用的这个确切文件。先执行:
stat -c '%s bytes' \ |
应得到:
6436792 bytes |
大小或 SHA-256 只要有一个不同,就不要继续照抄本节的 skip=6430768。不同构建的 DTB 位置可能不同,用错误偏移覆盖文件会得到无法启动的镜像。
3.4 从镜像末尾取出 DTB
本文镜像中的 DTB 从十六进制偏移 0x622030 开始:
0x622030 = 6430768(十进制) |
原 DTB 长度为 0x1788,即 6024 字节。用 dd 把它单独复制出来:
mkdir -p /tmp/hg6543-dtb |
检查提取结果:
stat -c '%s bytes' /tmp/hg6543-dtb/router.dtb |
应看到 6024 字节,并且开头四字节为 DTB 的固定文件头:
6024 bytes |
若大小或文件头不对,说明原镜像不一致或偏移量输错了,不要继续。
3.5 读取原值,再改成 256 MiB
先读 /memory@0 的 reg 属性:
fdtget -t x /tmp/hg6543-dtb/router.dtb /memory@0 reg |
本文原文件输出:
0 4000000 |
前一个 0 是内存起始地址,后一个 4000000 是容量,即十六进制 0x04000000,等于 64 MiB。执行修改:
fdtput -t x \ |
再次读取,确认命令真的生效:
fdtget -t x /tmp/hg6543-dtb/router.dtb /memory@0 reg |
现在必须输出:
0 10000000 |
它对应的 DTS 含义是:
memory@0 { |
3.6 把修改后的 DTB 放回镜像副本
不要直接覆盖下载的原文件。先复制一份,再把修改后的 DTB 写回原来的偏移位置:
cp \ |
这里的 conv=notrunc 表示只替换指定位置的数据,不截掉镜像的其他内容。
最后核对结果:
stat -c '%s bytes' \ |
本文得到:
6436792 bytes |
到这里仅解决了“系统错误地只识别 64 MiB 内存”的问题。还不能方便地访问 ConfigurationB,因为通用 DTB 不知道 HG6543C1 的原厂分区布局,这就是下一步要解决的事。
第四步:把 HG6543C1 的原厂分区表加入临时镜像
4.1 为什么还要加入分区表
光猫闪存里的数据本来就已经按原厂布局存放好了,我们并不是重新分区。所谓“加入分区表”,只是告诉临时 OpenWrt:每个区域从哪里开始、有多大、应该叫什么名字。
通用镜像默认只会列出 all_flash、bootloader 和 rest_of_flash 三个粗略区域。这样虽然能看到整颗 NAND,却没有单独的 ConfigurationB 设备,后面就无法直接执行:
mount -t jffs2 -o ro /dev/mtdblock7 /mnt/factory-config |
原厂启动日志给出的布局如下。reg 使用的是“起始地址 + 长度”,不是“起始地址 + 结束地址”。例如 ConfigurationB 从 0x2d80000 开始,长度为 0x300000,所以结束位置是 0x3080000。
| 启动地址 | 长度 | 结束地址 | 名称 |
|---|---|---|---|
0x0000000 |
0x080000 |
0x0080000 |
boot |
0x0080000 |
0x300000 |
0x0380000 |
KernelA |
0x0380000 |
0x1280000 |
0x1600000 |
RootfsA |
0x1600000 |
0x300000 |
0x1900000 |
KernelB |
0x1900000 |
0x1280000 |
0x2b80000 |
RootfsB |
0x2b80000 |
0x200000 |
0x2d80000 |
ConfigurationA |
0x2d80000 |
0x300000 |
0x3080000 |
ConfigurationB |
0x3080000 |
0x800000 |
0x3880000 |
Framework1 |
0x3880000 |
0x800000 |
0x4080000 |
Framework2 |
0x4080000 |
0x3b80000 |
0x7c00000 |
Apps |
所有分区都会在设备树中加上 read-only;。即使后续输错写入命令,内核也会多提供一层只读保护。
4.2 把二进制 DTB 转成可编辑文本
第三步已经修改了 /tmp/hg6543-dtb/router.dtb 的内存容量。现在把这同一个 DTB 反编译成 DTS 文本:
dtc -I dtb -O dts \ |
dtc 可能报告与网口节点有关的 warning;只要命令成功生成 router.dts,与本次分区修改无关的 warning 可以保留观察。
先找到原分区块:
grep -n -A25 -B5 'fixed-partitions' /tmp/hg6543-dtb/router.dts |
在本文镜像中,它位于以下层级:
spi_controller@1fa10000 |
原来只有 all_flash、bootloader、rest_of_flash 三项。
4.3 在文本编辑器中替换分区块
用 nano 打开文件:
nano /tmp/hg6543-dtb/router.dts |
在 nano 中按 Ctrl+W,输入 fixed-partitions 后回车。找到包含它的整个 partitions { ... };,从 partitions { 开始到与它配对的 }; 为止,整块替换为下面内容。不要删掉外层的 nand@0 { ... };。
partitions { |
按 Ctrl+O 保存,按回车确认文件名,再按 Ctrl+X 退出 nano。最常见的编辑错误是漏掉分号或花括号;下一条编译命令会指出出错行号。
4.4 重新编译,并在做镜像前检查关键分区
把 DTS 编译回 DTB:
dtc -I dts -O dtb \ |
如果这里出现 FATAL ERROR,不要继续制作镜像。根据提示的行号返回 nano,重点检查 {、} 和每行末尾的 ;。
先确认第三步的 256 MiB 设置没有丢失:
fdtget -t x \ |
应输出:
0 10000000 |
再检查我们最终要读取的 ConfigurationB:
fdtget \ |
必须分别输出:
ConfigurationB |
这一步通过,才说明分区名称、起始地址和长度都已经进入新 DTB。
4.5 生成最终的 RAM 启动镜像
修改分区表后,新的 DTB 比原来的 6024 字节稍大,不能简单地在旧 DTB 上覆盖。正确做法是:复制第三步的 256 MiB 镜像,把文件截到 DTB 起始位置,再从同一位置接上新 DTB。
cp \ |
这里的 truncate 只处理电脑里的镜像副本,不会接触光猫。它先移除文件末尾的旧 DTB;随后的 dd 再把新 DTB 接到偏移 6430768。
检查最终文件:
stat -c '%s bytes' \ |
本文使用 dtc 1.8.1 得到:
6437464 bytes |
不同 dtc 版本重新编译出的二进制排列可能不同,因此 SHA-256 可以作为本文环境的参考,不应取代前面的 fdtget 检查。只要内存输出为 0 10000000,且 ConfigurationB 输出为 2d80000 300000,关键修改就是正确的。
最终生成的 -256m-factoryparts.bin 仍然是 RAM 临时启动镜像。到这里没有执行 Bootloader 的 flash 命令,也没有向 NAND 写入任何内容。
第五步:用 XMODEM 上传到 RAM
TcBoot 的 xmdm 与 picocom/lrzsz 的组合在本机反复出现 NAK on sector,因此使用 pyserial 和 xmodem 直接发送。
保存以下脚本为 ~/Downloads/tcboot_xmodem_send.py:
# /// script |
先从 picocom 退出:Ctrl+A,再按 Ctrl+X。随后执行:
sudo -E ~/.local/bin/uv run \ |
成功时会看到:
Handshake received; starting transfer. |
received len 可能因为 XMODEM 的 128 字节补齐而略大于原文件,这是正常的。此过程只写 RAM。
重新打开 picocom:
sudo picocom -b 115200 --flow n /dev/ttyUSB0 |
启动内存中的 OpenWrt:
bldr> jump 80020000 |
第六步:确认内存与分区
进入 shell 后检查内存:
free -m |
正确结果应接近 256 MiB,而不是 64 MiB:
Mem: 249344 ... |
检查分区:
cat /proc/mtd |
应看到:
mtd0: 08000000 "all_flash" |
原厂启动日志显示设备从 B 槽启动,因此当前配置位于 ConfigurationB,即 mtd7。
第七步:只读挂载配置分区
mkdir -p /mnt/factory-config |
JFFS2 可能输出 CLEANMARKER、Empty flash,甚至少量旧节点 CRC 警告。本机原厂内核启动时也会出现相同 CRC 地址。这里必须保持只读,不要使用通用内核修复或写入该分区。
列出文件:
find /mnt/factory-config -maxdepth 4 -type f | sort |
目标文件为:
/mnt/factory-config/cfg/agentconf/factory.conf |
只筛选管理账号字段:
grep -niE 'telecom|admin|pass|passwd|pwd|account|user' \ |
或者只查看文件开头:
sed -n '1,12p' \ |
本机得到的结构如下,实际密码已脱敏:
TelecomAccount=telecomadmin |
同一文件中还可看到原厂 Telnet root 账号配置:
TelnetSuperAccount=root |
下面是本次从只读配置分区取得账号信息时的实拍,设备序列号已经隐藏:

不要在文章、截图或命令记录中公开真实密码。
第八步:退出临时系统并登录原厂后台
先卸载:
umount /mnt/factory-config |
然后重启:
reboot -f |
因为整个 OpenWrt 都在 RAM 中,断电或重启后会自动回到原厂系统,没有刷机,也没有改变 NAND 内容。
原厂系统启动完成后,通过光猫 Wi-Fi 访问:
http://192.168.1.1/ |
使用刚刚从 factory.conf 中读出的 TelecomAccount 和 TelecomPasswd 登录即可。
失败过程与排错
串口完全无输出
- 确认是
115200 8N1; - 光猫 TX 必须接模块 RXD;
- GND 必须共地;
- 不要连接 VCC;
- 确认 USB-TTL 逻辑电平是 3.3 V;
- 57600 下看到乱码并不表示正确波特率,本机最终确认是 115200。
picocom 调用 lrzsz 一直 NAK
本机的 lrzsz-sx --xmodem 与 TcBoot 不兼容,改用本文的 Python XMODEM 脚本。脚本会先消费 Bootloader 回显,再把握手字符交给 XMODEM 库。
OpenWrt 启动后 OOM 自动重启
通用设备树只声明了 64 MiB。必须将 /memory@0/reg 改为 0x10000000,即 256 MiB。
RootfsB 无法挂载
Filesystem uses "lzma" compression. This is not supported |
临时 OpenWrt 内核未启用旧式 SquashFS-LZMA。获取超级管理员密码并不需要 Rootfs,只挂载 JFFS2 的 ConfigurationB 即可。
JFFS2 出现 CRC 警告
保持只读。不要在实验内核下执行:
mount -o remount,rw |
这套方法最大的价值在于可回退:从进入 Bootloader 到读取配置,所有关键操作都发生在 RAM 或只读挂载中。只要不执行 Flash 写入,断电后仍会回到原厂系统。