PALL 锁不住:nRF51 调试口复用指令抽固件
原创
黑卷
黑卷
黑卷的IoT攻防日记
2026年10月19日 11:13
上海
去阅读
Nordic 的 nRF51 系列,是早些年 BLE 设备里很常见的 Cortex-M0 SoC。信标、智能锁、手环、传感器,很多都塞过它。厂商靠 UICR 里的 RBPCONF 做读保护——开到最强档 PALL,调试器直接读 Flash 会回全 0,想解保护只能 ERASEALL 整片擦掉。
听起来像「生产锁」已经锁死了。lessonsec 的 0xless 复现了 Include Security 提出的绕过思路:PALL 挡的是「直接读」,挡不住「改寄存器 + 单步执行」。调试口还在,就能劫持 CR0 里已有的 load 指令,把整片固件一点一点吐出来。
不用毛刺,不拆硅,标准 SWD 就够。目标还能继续跑。对智能锁、可穿戴这类「外壳一拧就能碰到测试点」的产品来说,这个前提并不苛刻。实验室复现用的是开发板,量产机上的测试点往往更隐蔽,但思路一样。
先看关键数字
实验板是网上最便宜的一档 nRF51822 开发板,芯片按常见款 XXAA 算——256KB Flash。工具链也不花哨:J-Link、OpenOCD、SEGGER Embedded Studio、arm-none-eabi-gdb,再加一段自动化脚本。
整次抽固件跑完大约 54 分钟。慢是慢,但证明一件事:生产锁开着,固件照样能完整落盘。后面要做静态分析、挖硬编码密钥、跟协议状态机,都建立在这一步之上。
抽固件几乎永远是 IoT 逆向的第一刀。刀能下得去,后面的故事才有得讲。和电压毛刺、开盖腐蚀比,这条路径设备还能继续工作,风险更可控,对「只想拿镜像、不想毁板」的人尤其友好。
保护模型:UICR、CR0、RBPCONF
安全相关配置落在地址 0x10001000 的 UICR(User Information Configuration Registers)。这篇最关心两个字段:CLENR0 和 RBPCONF。
CLENR0 把代码 Flash 切成两块:
- CR0
:从 0x00000000 起,长度由该寄存器定
* CR1
:剩下的部分;若 CLENR0 保持 0xFFFFFFFF,整片都当 CR1
CR0 更「硬」:跑在 CR1 的代码改不了 CR0;CR0 页也不能单独擦,只能整片 ERASEALL。厂商常把引导、密钥材料、敏感组件往 CR0 塞,指望这块区域更难被动。
RBPCONF 常见三档:
- 无保护
:调试器直接读整片 Flash
* PR0
:锁 CR0——CR1 代码读不了它,调试口也读不了 CR0
* PALL
:锁全部代码区;调试口直接读回 0;解保护只能 ERASEALL
PALL 被定位成「生产锁」,是这颗 MCU 上最强的一档读保护。文档叙事通常停在这里:开了 PALL,外面读不了;想清保护,就得把应用和配置一起擦掉。
但 nRF51 有个硬伤:关不掉调试口。很多同代 SoC 可以选择彻底禁用调试访问;这颗不行。直接读被挡,寄存器读写和单步执行还在——缝就在这儿。
换句工程话:读保护把「存储器读通路」收紧了,却把「CPU 调试通路」留着。攻击者不必自己发 Flash 读命令,只要让 CPU 代劳。
攻击链:借壳读,不是硬拆
绕过不靠电压毛刺,也不靠开盖腐蚀。它靠的是「执行环境自己」。Flash 读不出,寄存器状态调试器还能看、还能改。于是可以:停住 CPU、单步、改通用寄存器,让已经躺在 CR0 里的合法指令,按攻击者指定的地址去 load。
思路可以压成四句:
- 接 SWD(VCC / GND / SWDIO / SWDCLK),用 OpenOCD 开 GDB——PALL 开着时,J-Link 套件有时不给连 GDB,OpenOCD 更稳;
- 把通用寄存器全塞成
0xE000ED00(CPUID,nRF51 上固定读出0x410CC200),单步找「从寄存器取址再 load」的指令; - 找到后,把 PC 指回那条指令,寄存器改成目标 Flash 地址,再单步——读出来的就是被保护的内容;
- 脚本从
0x0扫到0x3FFFF,拼成firmware.bin。
Nordic 的模型默认:CR0 隔离 + 解保护必须 ERASEALL,就够护住敏感固件。缺了「调试口可彻底关闭」这一环,这些保证会漏风。
实操:板子、接线、假固件
开发板 SWD 脚是露出来的。脚距对不上常见排针时,作者用 IC 测试钩夹上去——照样能稳连。硬件门槛就是:有板、有调试器、有四根线。
先刷一份演示固件:里面只有一句 printf(\"SECRET\")。目标很直白——PALL 开了之后,还能不能把这串字从 Flash 里抠出来。成功标准不靠猜,靠字符串对得上。
未开保护时,GDB 直接读 Flash,字节正常;顺手读 0xE000ED00,确认 CPUID 是 0x410CC200。后面定位 load 指令,就靠这个已知值当「探针」——它在 PALL 打开后依然可读,特别适合当对照。
然后执行 nrfjprog --rbp ALL,重启,再读同一地址:全是 0x00000000。PALL 生效了。如果评估只做到这一步,很容易得出「保护正常」的结论。
真正要问的下一句是:寄存器还能不能改?CPU 还能不能单步?答案如果是「能」,保护模型就还没测完。
定位 load,再扫整片
脚本逻辑不复杂:寄存器全写成 0xE000ED00,stepi,看有没有寄存器变成 0x410CC200。多试几轮,作者在 PC=0x82 那条指令上命中——执行完 r0 里就是探针值。
把寄存器改成 0x00000000,再单步一次,读到的就是受保护区开头的数据。保护挡的是调试器的读事务;它挡不住「CPU 自己执行一条已经存在的 load」。
自动化就是循环:PC 固定到那条 load,地址每次 +4,打印 r0,直到扫完 256KB。不同料号 Flash 容量可能不同,XXAA 这档按 0x3FFFF 扫到底。
跑完大约 54 分钟,落盘 firmware.bin。xxd 一看,植入的 SECRET 还在。实验闭环成立:PALL 开着,完整镜像照样能重建。
作者后来把流程收成更完整的脚本(自动找 load、dump、拼 bin),开源仓库名是 nRF51-RBPCONF-Bypass。PoC 还能更快:若碰到 LDMIA 一类一次能 load 更多字节的指令,可以改 PC 跳过去提速。GDB 脚本写复杂了就别扭,他打算后续换 pyswd 一类直接控 SWD 的库(当时手里没有兼容的 ST-Link)。
54 分钟对实验够用,对批量拆解还偏慢。提速方向很清楚:少单步次数、多字节/次、少经 GDB 中转。手法不变,只是工程效率。
这意味着什么
对还在用 nRF51 的门锁、手环、传感器来说:产线开了 PALL,不等于「固件抽不走」。物理接触 + 标准调试器,就够把二进制完整带走。后续静态分析、挖硬编码密钥、找协议洞,都建立在这一步之上。
对评估和选型的人,可以对照下面几条——不是厂商官方 checklist,是这篇复现推出来的实操提醒。
再强调一次边界:这是有物理接触、有调试口的攻击面,不是公网 0click。但对消费级 IoT,「壳能打开、测试点能夹到」往往就是默认现实。威胁模型里如果只写「开了读保护」,漏的就是这一层。
产线如果还把 SWD 焊盘留在板边、丝印写得清清楚楚,等于把钥匙孔留在门外。评估报告里不妨单独开一栏:读保护档位、调试口是否可关、测试点是否暴露、抽固件是否可在不开盖或轻开盖条件下完成。
黑卷点评
读保护这事儿,名字听着硬,经常只挡了一半。
① 读保护 ≠ 调试口关闭——PALL 让直接读回 0,寄存器还能改,CR0 里的 load 就能被借壳。测保护时别只测「读 Flash 是否变 0」,还要测寄存器与单步是否仍可用。
② 非侵入不等于低威胁——不用毛刺、不拆硅,标准 SWD + 便宜开发板就能复现。产线如果默认「开了 PALL 就够」,威胁模型偏乐观。
③ 抽固件是起点不是终点——256KB 抽完只是拿到二进制;密钥、协议、后门都在后面。车载 T-BOX、充电桩、门锁里同类「调试口关不干净」的坑,换个芯片家族还会再遇到。
换到新一代 nRF52 的 APPROTECT,故事不完全一样——别默认「同一家 = 同一模型」。选型时把「调试口能否彻底关掉」写进检查项,比事后补固件划算。更新通道、调试通道、读保护,这三件事要一起看,别只盯着 App 图标好不好看。
一句话收束:挡直接读,只是门槛;关不掉的调试口,才是门轴。门轴还在,锁再厚也只是挡着正门。
往期推荐
从调试口抽到改固件:Sonoff ZigBee 网关的芯片到云端两条 CVE
EOL 路由照样打穿:Netgear WGR614v9 UART + Bitdefender Box SPI 降级 RCE
拍桌子才出 root:FiberGateway GR241AG 从 UART 故障注入打到 MEO 公网 WiFi RCE
四根铜焊盘听出 root:TP-Link TL-WR845N UART 未认证调试口
从 UART 焊到未认证后门:ANJIA PTZ 摄像头(CVE-2026-31077)
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论