
去年搬家的时候买了一块 muiBoard——一块日本 mui Lab 做的”木头智能家居面板”,理念是”不打扰的计算”(Calm Technology),不用的时候就是一块普普通通的木板,touch 一下表面才会亮起 LED 矩阵显示时间、天气、家里的智能设备状态。颜值确实高,放在玄关很有格调。但配套 APP 的功能实在过于残废,连个正经的自动化都配不明白,并且触摸交互体验也很糟糕…,于是买来没多久就被我扔进柜子吃灰了。
这段时间放假在家闲得无聊,想起柜子里还躺着这么个东西,干脆拆开看看里面是什么料。
拆机

拧开背板螺丝,里面赫然躺着一块 RPi CM4。好家伙,拆开之前我还担心是 ESP32 之类的低功耗 MCUs,没想到直接上了个树莓派,性能完全够用,甚至有点过剩了。
I/O 板
但是问题来了:CM4 是 SoM 模组,自己不带 UART 引脚,看了一下它接入的板子也没有明显的 UART Pin,只能配合 IO 扩展板才能引出串口。但是手头上没有现成的,Amazon 上搜了一圈,最后斥巨资 4300 円下单了一块微雪的 CM4 I/O Board。

插上成功进入 U-Boot 终端。接下来只要在 bootargs 后面加个 init=/bin/bash,就能拿到 root shell 了……吗?

第一次尝试:setenv bootargs,失败
1 | U-Boot> setenv bootargs "... init=/bin/bash" |
系统……正常启动了。systemd 欢快地拉起一堆服务,完全无视了我设的 bootargs。
printenv 看了下,bootcmd=bootflow scan。bootflow scan 会扫 FAT 分区里的 boot.scr,而 boot.scr 的第一行就是:
1 | fdt addr ${fdt_addr} && fdt get value bootargs /chosen bootargs |
它从 DTB 的 /chosen/bootargs 节点重新读值覆盖 $bootargs,我 setenv 设的东西直接被冲掉了。
这其实是 RPi 的设计习惯:cmdline 放在 FAT 分区的 cmdline.txt 里,GPU 固件(start4.elf)在启动时把它写进 DTB 的 /chosen 节点。U-Boot 在 RPi 上只是二级接力,不信任自己 env 里的 bootargs,而是信任固件写好的那份。boot.scr 于是固定从 DTB 读,保证跟原生行为一致。
第二次尝试:绕过 bootflow,失败
既然 boot.scr 会覆盖,那我不走 bootflow,自己手动 load + booti 总行了吧。
1 | U-Boot> load mmc 0:1 ${kernel_addr_r} Image |
然后……就没有然后了。串口再也没有任何输出。
想了想才反应过来,我从 FAT 分区 load 出来的是一份裸 DTB,没有经过固件层面的 overlay 合并,更关键的是,console 设备树节点的配置在 overlay 里,裸 DTB 里根本没正确映射 ttyAMA0。内核起来了,但 console 输出到了一个不存在的地方,活活憋死在黑屏里。
手搓 boot.scr 流程,成功
反复分析 boot.scr 的逻辑之后,决定干脆完全复刻它的行为,但自己控制顺序:
1 | U-Boot> fdt addr ${fdt_addr} && fdt get value bootargs /chosen bootargs |
关键在于最后一行用的是 ${fdt_addr} 固件传进来的那份 DTB 地址。
然后 setenv 在 boot.scr 逻辑之外追加 init=/bin/bash,确保它排在 cmdline 末尾且不会被 boot.scr 二次覆盖。
1 | [ 3.317547] Freeing unused kernel memory: 4544K |
这次成功了!
Dump
拿到 shell 之后的第一件事当然是全盘备份。但它系统自带的 nc 有点问题,管道一接就卡死。手动把网卡拉起来:
1 | bash-5.2# mount -t proc proc /proc && mount -t sysfs sys /sys |
Mac 这边用 USB 网卡配上 10.0.0.1/24
然后用系统自带的 Python 跑个小服务,把 eMMC 的内容走 TCP 发出来:
1 | import socket, subprocess |
Mac 端 nc 10.0.0.2 9000 > mui_dump.img,等几分钟完整镜像就到手了。
分区与意料之中的 LUKS
gpt -r show mui_dump.img 看了下分区表:
1 | 8192 150128 1 FAT16 boot |
典型的 A/B 双根分区 + 一块 LUKS 数据区。第四个 LUKS1 用了 aes-xts-plain64, sha256 加密,没有 key 就只能干瞪眼。
但是!这种开机自动挂载的 LUKS 分区,解密脚本肯定在 rootfs 的某个地方。
1 | grep -rl luks ./dump/p2_rootfs/usr/bin/ |
auto-mount.sh,bingo:
1 | ENCRYPTED_DATA=$(fw_printenv store_k | cut -d= -f2) |
整个”安全设计”是这样的:
- LUKS passphrase 用 AES-256-CBC 加密后存在 u-boot 环境变量
store_k里 - AES key 由 CPU serial 重复 4 次拼出来(serial 是 16 hex = 8 字节,重复 4 次正好 32 字节)
- CPU serial 从
/proc/cpuinfo直接读
换句话说:只要能拿到 CPU serial + u-boot env,就能解开 LUKS。而 CPU serial 根本不是什么秘密,任何能进 U-Boot 或跑起系统的人都能读到;u-boot env 就在 eMMC 上,dump 出来就有。整套”加密”只防住了那种”把 eMMC 吹下来读 LUKS 分区但完全不看 rootfs”的抽象攻击者。
顺手看一眼 DTB
看看 config.txt 加载的 overlay 列表里:
1 | dtoverlay=mpm1-wacom-hid2.dtbo |
后面几个是 stock 自带的,mpm1-wacom-hid2.dtbo 是 mui 自研的——把它解出来看:
mpm1-wacom-hid2.dtbo — 触摸面板
1 | i2c-hid-dev@a { |
看起来 muiBoard 表面那块木头底下贴的不是普通电容触摸,而是一片完整的 Wacom 数位板模组。难怪触摸和书写的体验跟普通智能家居面板完全不在一个级别(甚至魔改一下能当 OSU 手台?)。
整套硬件定制全部走 RPi 标准的 overlay 机制——一个 Wacom dtbo + 两条 UART overlay 就搞定了。基础 dtb 跟官方完全一致,将来升级 RPi firmware 不会冲突。
这其实是相当克制的工程做法,比某些厂商把整个 dts fork 出来自己魔改、然后永远停留在某个内核版本的做法漂亮多了。残念的还是只有上面那套残废的 APP……
收获
muiBoard 硬件是真的不错,木头的质感、点阵触摸屏效果,都做得相当用心。残废的只是官方那套软件栈。现在把软件层彻底掀翻,反而可以当一块”长得很好看的 CM4 开发板”来用,性价比瞬间回正(?)
理论上换一块自己的 CM4 板子上去也是完全可行的,毕竟它的 bootloader 就是标准的 RPi U-Boot