记一次救砖:MBrush v4.2 (君正 X1021) 内存注入
踩坑背景
在折腾MBrush v4.2时,因为疏忽,把针对新版单片机架构(MBrush2)的固件刷了进去。
重启后,打印机彻底变成了“砖头”:USB 连不上、网页打不开。
我尝试UART调试,由于触点间距1mm,我的选择是拿三个别针接触它们,然后连接usb工具。显示日志:
U-Boot 2013.07-g0fa8b95 (Sep 29 2019 - 22:45:03)
Board: Sepal (Ingenic XBurst X1021 SoC)
DRAM: 64 MiB
...
NAND: 128 MiB (ATO25D1GA)
然而
绝望一:手速再快也按不住的 bootdelay=0
系统确实在启动,内核也确实报错了(Wrong Image Format for bootm command)。但我发现,U-Boot 的日志中赫然写着:
Hit any key to stop autoboot: 0
0 秒延迟!这意味着 U-Boot 在初始化串口后,连一毫秒都不等,直接去读坏掉的内核。无论我怎么疯狂按回车,都无法进入 U-Boot 命令行。
方案:Python 加特林物理外挂
既然人的手速不行,那就让机器来。我写了一个 Python 脚本,以 3Mbps 的满速在串口疯狂循环发送回车符。
import serial, time
# 提前疯狂发送回车,通电瞬间拦截 U-Boot
ser = serial.Serial('COM3', 3000000, timeout=0.01)
while True:
ser.write(b' \r\n')
time.sleep(0.005)
if b'sepal' in ser.read_all():
print("拦截成功!")
break
然而行不通。
我另辟蹊径,找到其NAND Flash,在 NAND read: device 0 offset 0x100000... 瞬间用镊子短接VCC和CS,成功进入U-Boot。
绝望二:被阉割到极致的 U-Boot
拿到 Shell 后,敲下 loady 0x80600000(准备用 Ymodem 协议把正确的 uImage 内核传进内存)。
结果系统冰冷地回复:
Unknown command 'loady' - try 'help'
我敲下 help,心凉了半截。这个 U-Boot 为了节省空间,把 loady、loadx、loadb 等所有的串口文件传输协议全部阉割了!
同时,日志显示 GMAC failed to reset!,意味着底层根本没有初始化网卡,tftpboot 也成了废设。
USB 线刷又卡在 Win10 的驱动签名上…… 难道真的没救了?
绝望三变绝杀:3Mbps 串口下的“内存硬写”大法!
看着手里的 uImage 文件(大约 2.9MB)和这个只有基础读写命令的 U-Boot,我突然意识到:
U-Boot 支持 mw.l (Memory Write Long) 命令,可以直接向指定内存地址写入 32 位的十六进制数据。
既然串口波特率高达 3Mbps,我为什么不直接写一个 Python 脚本,把这 2.9MB 的内核文件切碎,翻译成几十万条 mw.l 文本命令,一行一行硬“灌”进打印机的内存里呢?!
说干就干!我编写了以下这个“硬核注入器”脚本:
核心注入脚本 (inject_uImage.py)
import serial
import struct
import time
import sys
PORT = 'COM3' # 你的串口号
BAUDRATE = 3000000
FILE_PATH = 'uImage' # 正确的内核文件
def main():
ser = serial.Serial(PORT, BAUDRATE, timeout=1)
with open(FILE_PATH, 'rb') as f:
data = f.read()
# 补齐 4 字节对齐
while len(data) % 4 != 0:
data += b'\x00'
addr = 0x80600000
print(f"🚀 开始将 {len(data)} 字节硬核注入到内存 0x{addr:X} ...")
# 唤醒 U-Boot 提示符
ser.write(b'\r\n')
ser.read_until(b'#')
start_time = time.time()
# 将二进制文件转换为 32位 小端序整数
words =[struct.unpack('<I', data[i:i+4])[0] for i in range(0, len(data), 4)]
for i, word in enumerate(words):
# 拼接 U-Boot 的 mw.l 命令
cmd = f"mw.l {addr + i*4:x} {word:08x}\r\n".encode('ascii')
ser.write(cmd)
# 极速等待 U-Boot 响应,确保数据不丢失
if b'#' not in ser.read_until(b'#'):
# 超时重试机制
ser.write(b'\r\n')
ser.read_until(b'#')
ser.write(cmd)
ser.read_until(b'#')
if i % 10000 == 0 or i == len(words) - 1:
progress = (i / len(words)) * 100
print(f"📦 进度: {progress:.1f}% | 已耗时 {time.time() - start_time:.1f} 秒")
print("\n🎉 内存注入完成!")
ser.close()
if __name__ == '__main__':
main()
运行结果:
脚本启动后,电脑就像一个发疯的黑客,以每秒几百条命令的速度向 X1021 芯片狂轰滥炸。得益于 3Mbps 的超高波特率,这几百万字节的文本命令传输居然没有任何卡顿。
大约 10 多分钟后,进度条走到 100%,2.9MB 的 uImage 完美地在机器的 0x80600000 内存处拼装完成!
终局:擦除与烧录
关闭 Python 脚本,我重新打开了 XCOM 串口助手,敲下回车,熟悉的 sepal_x1021-sfcnand# 依然在等我。
既然内核已经躺在内存里了,剩下的就是把它永久烙印进 SPI NAND 闪存里:
# 1. 顺手把这该死的 0 秒延迟改掉,拯救未来的自己
sepal_x1021-sfcnand# setenv bootdelay 3
sepal_x1021-sfcnand# saveenv
# 2. 擦除原本损坏的内核分区 (从 0x100000 开始的 4MB 空间)
sepal_x1021-sfcnand# nand erase 0x100000 0x400000
# 3. 将刚刚辛苦灌入 0x80600000 内存的数据写入闪存
sepal_x1021-sfcnand# nand write 0x80600000 0x100000 0x400000
# 4. 重启,见证奇迹!
sepal_x1021-sfcnand# reset
随着 reset 命令的敲下,屏幕上终于出现了令人热泪盈眶的:
Starting kernel ...
紧接着,Linux 的开机日志倾泻而出,MBrush 满血复活!
总结
这次救砖之旅简直叠满了嵌入式开发的 debuff:按键锁死 (bootdelay=0) + 传输协议被阉割 + 网络模块失效。
但这也恰恰证明了底层的魅力:只要 UART 串口还在,只要 CPU 还能解析基础的读写指令,哪怕是用最原始的 mw.l 文本命令敲进去几百万行代码,也一样能把系统救回来。
本文由AI生成,仅做简单记录和适当参考