救砖:MBrush v4.2 (君正 X1021) 内存注入
踩坑背景
在折腾MBrush v4.2时,因为疏忽,把针对新版单片机架构(MBrush2)的固件刷了进去。于是打印机变砖。 我尝试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)
然而
问题一:无法进入U-Boot
系统确实在启动,内核也确实报错了(Wrong Image Format for bootm command)。但我发现,U-Boot 的日志中写着:
Hit any key to stop autoboot: 0
这意味着 U-Boot 在初始化串口后,等待0秒直接去读坏掉的内核。无论我怎么快速按回车,都无法进入 U-Boot 命令行。
方案:脚本向串口灌数据
import serial, time
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'
这个 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 文本命令,灌进打印机的内存。
注入脚本
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()
终局:擦除与烧录
关闭 Python 脚本,打开了串口助手。
既然内核已经在内存里了,剩下的就是把它写进 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
之后显示:
Starting kernel ...
紧接着,Linux 显示开机日志,MBrush 满血复活!
总结
这次救砖之旅简直叠满了嵌入式开发的 debuff:按键锁死 (bootdelay=0) + 传输协议被阉割 + 网络模块失效。
但这也恰恰证明了底层的魅力:只要 UART 串口还在,只要 CPU 还能解析基础的读写指令,哪怕是用最原始的 mw.l 文本命令敲进去几百万行代码,也一样能把系统救回来。
本文主要由AI生成,仅做简单记录和适当参考