NRF51822 代码读出保护绕过方法
Nordic 蓝牙 SoC NRF51822 是一种Cortex-M0 处理器,实现了代码读出保护(CRP)机制,但仍可以使用SWD、OpenOCD(通过 SWD 与 NRF51822 芯片交互的软件)、gdb技术结合进行绕过。
一、准备
- 搭建OpenOCD和GDB调试环境。OpenOCD需要设置J-link的驱动为WinUSB模式。
- 连接设备:VCC、GND、SWDIO 和 SWCLK 连接到SWD 调试器J-link上的对应引脚(NRF51822使用SWD,它是 JTAG 的两线版本。可以用任何 SWD 调试器连接到它)。
- 开启监听
openocd -f interface/jlink.cfg -c "transport select swd" -f target/nrf51.cfg
- 连接固件
# telnet 到 OpenOCD 与其进行交互
telnet 127.0.0.1 4444
或者
# gdb 到 OpenOCD 与其进行交互
gdb-multiarch.exe
(gdb)set arch arm
(gdb)target remote :3333
二、调试
NRF51822的内存保护由UICR(用户信息配置寄存器)中的RBPCONF寄存器处理,这组寄存器配置了芯片的一些运行信息如bootloader地址,代码段长度等等。
默认值为 0xFFFFFFFF,此时可以读取。但设置为了 0xFFFF00FF,读保护启用。
查看NRF51 系列的 SDK,找到uicr_config.h文件,发现地址映射为0x10001004。
直接提取固件是不可取的,提取出来为全0。将0x10001004设回0xFFFFFFFF更行不通。
想要改UICR,只能写NVMC(Non-Volatile Memory Controller)的ERASEALL寄存器,但这样固件就全擦除了,出现矛盾。因为这是芯片的安全特性——不允许随便读写flash。
三、绕过
arm thumb指令长度是16bit,想要32bit寻址就只能通过寄存器寻址,不能直接在指令里面塞立即数。先复位暂停:
此时能看到pc寄存器和初始堆栈指针msp,实际上,此时0x0处的值就是图片中msp的值。
我们需要事先了解可读固件的代码执行逻辑,不然工作量会很大:
注意,CRP 是否启用并不重要——我们可以控制寄存器,即使启用 CRP 也可以单步执行。那么现在有一个思路,既然是通过寄存器寻址,那么我们可以调整pc值指向第二条指令,再设置寄存器为想要的地址,就能通过寄存器读出固件内容的一个32位单元。编写脚本,重复执行即可得到完整的固件。
四、代码
在我自己的固件中,这条关键的指令地址在0x6d4,被赋值的寄存器是r4。
from pwn import *
import rep = remote("127.0.0.1", 4444)p.recvuntil(">")
p.write("reset halt\n")
p.recvuntil(">")with open("firmware.bin", "wb") as f:for addr in range(0, 0x40000, 4):p.write("reg pc 0x6d4\n")p.recvuntil(">")p.write("reg r4 " + hex(addr) + "\n")p.recvuntil(">")p.write("step\n")p.recvuntil(">")p.write("reg r4\n")ret = p.recvuntil(">")d = re.search('0x[0-9a-fA-F]{8}', ret.decode('utf-8'))[0]f.write(p32(int(d, 16)))if addr % 0x100 == 0:print("reading:", addr)
好啦,作为小白的我又搞定一个固件,如有理解不到位的地方,欢迎各位大佬批评指正。