Update 2017-08-19: Updated Links, linked to follow on parts
This will be the first of a three four part series on bypassing the security of the embedded NXP LPC1343 In System Programmer (ISP) bootloader (read Part 2 and Part 3).
Although we'll focus on the LPC1343, keep in mind that this bootloader is the embedded bootloader in many NXP LPC microcontrollers.
One more thing worth mentioning, is that although the details outlined in this series will be specific to NXP LPC's you'll be able to find similar vulnerabilities in almost all non-secure microcontrollers.
As Jasper van Woudenberg (@jzvw) recently said at a an event we were both speaking at "If no-one implemented countermeasures -- it means it's vulnerable".
Also a big shoutout goes to Chris Gerlinsky (@akacastor) for dumping, dissassembling and documenting the bootloader as part of his Recon Brussels 2017 Talk.
You should take a look at this talk before you begin and both the slides and the video of the talk are available.
The Goal
Just to give you a taste of what the we hope to have up and running in the end, here it is:
This is why you should come take the training in Berlin. Write up for everyone else is also on the way. https://t.co/SP7RhDXWOI pic.twitter.com/mTweLfXU4X
— Dmitry Nedospasov (@nedos) 3 August 2017
What's happening is we're attempting to glitch the device (several times a second). Normally, since the device security is enabled, we won't be able to arbitrarily read from the device's memory. However, since we succeed in introducing a fault by utilizing a voltage glitch during power up, the device powers up unlocked and we have access to the debug functionality.
What you'll need
I'm a huge proponent of utilizing FPGAs in conjunction with a modern high-level scripting language like python. In fact this is at the heart of my training. I like to say that I teach people how to build python to hardware interfaces using FPGAs, and this exactly what we'll do in this series. If you don't have any equipment yet, I recently started putting together a list of recommended lab equipment. You might want to grab a couple of pieces of equipment from that list.
What you'll want to buy, because you'll need them to complete Part 2 and Part 3 is:
- 1x Digilent Arty FPGA Development Board Xilinx Artix-based Board
- 2x-4x Olimex P1343 Development Board (NXP LPC1343 development board) [Digikey]
- Maxim Max4619 Analog Multiplexer, which we'll need eventually, but this is also the only item you'll need to order on Digikey.
- A cheap breadboard, or splurge and get a 3M breadboard.
- Jumper wires
- 10uF decoupling capacitors - buy a kit, this is optional, but recommended if you don't have a kit already.
I recommend you buy at least two Olimex development boards and potentially a couple extra in case something goes wrong. They're extremely cheap and you'll want to physically modify the boards to make glitching easier (we'll cover this in part 3).
The LPC ISP Bootloader
To be able to tell whether or not the glitching was successful, we'll need to communicate with the bootloader and verify that we can read from the device.
The LPC1343 ISP bootloader comes in two flavors: USB and serial.
Communicating with the serial bootloader is much easier, especially since we've already got a USB to serial convertor on the Digilent Arty board.
Whether the LPC1343 boots in USB or Serial mode is determined by pin PIO0_3
.
Tying PIO0_3
to ground will ensure that the system boots in serial mode.
This pin will be different for other models hence it's best to check the datasheet to be sure.
Code Read Protection Values
The most important thing to remember is that since the bootloader is in the Read-Only Memory of the device, it's always there and it always gets executed.
However, depending on the value stored at particular location in flash, the bootloader may be disabled.
This value is referred to as the Code Read Protection value or CRP and is located at 0x2FC
.
Since this value has to be loaded into memory and checked, corrupting this read operation will cause the system to boot in an unlocked state.
The NXP user manual UM10375 (page 328) describes the possible CRP configurations:
NO_ISP
(0x4E697370
) - The NO_ISP does not offer any read protection, but disables the ISP bootloader, i.e. one could connect to the board with JTAG/SWD.CRP1
(0x12345678
) - JTAG/SWD debugging is disabled. The read memory command is also disabled (the one we want). However you'll be able to erase and overwrite the firmware in case you mess something up. We'll mostly be working with this CRP mode.CRP2
(0x87654321
) - JTAG/SWD debugging is disabled. WhileCRP1
allows for partially erasing the device,CRP2
only allows users to completely erase the device.CRP3
(0x43218765
) - JTAG/SWD debugging is disabled. If the chip is flashed with a valid firmware image, it will execute, bypassing the ISP.
So to summarize, memory address 0x2FC
stores a 32-bit value for code read protection.
The are 4 values that are defined, so there are 232 - 4 (4,294,967,292) values that are undefined.
However, if we manage to get the system to read an undefined value at boot, the system will remain in an unlocked state.
Hence, all we need to do is to flip just one bit in one of these 32-bit values as they are being read from memory.
One of my favorite lines in the datasheet is the following:
Caution: If CRP3 is selected, no future factory testing can be performed on the device.
The good news is once we get the glitch working, we'll be able to recover from a CRP3
state as well.
Synchronization
The ISP commands are all documented in UM10375 (page 323). There's also multiple tools that implement the communication protocol including lpcflash, lpc21isp and mxli, just to name a few. As far as I can tell mxli, seems like the best maintained code, although I feel Thorsten Schröder's (@ths's) tool, lpcflash, is much easier to read and understand.
Here's a list of steps to communicate with the bootloader. Note, the board echos back what you send it, so when implementing this in python, use the repr
function, so that you can see all the non-printable characters as well.
- Reset the device - the bootloader is stateful, you'll need to reset to resynchronize.
- Send an ascii
?
to the board - the board will use this to detect the baud rate. - The board replies with
Synchronized\r\n
. - Send
Synchronized\r\n
to the board to confirm. - The board will respond with
OK\r\n
, but you'll probably seeSynchronized\rOK\r\n
on the wire due to the echo. - Send
12000\r\n
to the board to set the clock rate to 12MHz. Note, this command has no affect since the board is in fact running off of an internal RC oscillator, but the datasheet says this is a necessary step. - The board will respond with
OK\r\n
, but you'll probably see12000\rOK\r\n
- At this stage you'll be able to use the read command. The response will differ depending on whether or not read protection is enabled. To read 4 Bytes (32-bits) from address 0 the command is:
R 0 4\r\n
. - If the read was valid, the host must respond with
OK\r\n
.
At this point we're synchronized with the ISP bootloader and can issue additional bootloader commands, in particular reading additional addresses. Note that the read command is aligned to 4 bytes. Hence the addresses will have to be multiples of 4.
Configuring the FPGA
At this stage we know the protocol, and we could easily connect a USB to serial convertor to speak to the LPC1343 board. However sooner or later, we'll want to use some additional hardware to glitch the device. Hence, we might as well get used to connecting the FPGA.
The Digilent Arty comes with a stock firmware that basically flashes a bunch of lights as a glorified demo. We'll want to load a different FPGA configuration file, aka a different bitstream, onto the board over USB. This will allow us to change the behavior of the FPGA. Initially all we want to do is something that I like to call a "passthrough". All the passthrough does is route the I/O pins through the FPGA. What we will do is connect the pins of the FT2232H to the female pin headers on the board and then use male-to-female jumper wires to power and communicate with the Olimex P1343.
Compiling the bitstream
For compiling a design for the Xilinx Artix FPGA, you'll need the Xilinx Vivado toolchain. The FPGA on the Digilent Arty board is supported under the free webpack license. Vivado can be a difficult to install, so if you want to save yourself the hassle send me a twitter DM and I'll send you a link where you can download the VMware virtual machine I use for my trainings.
I uploaded a working passthrough for the Digilent Arty board to Github.
To compile it, simply create a new project, select XC7A35TICSG324-1L
as the FPGA, add top.v as a design source and Arty_Master.xdc as the constraint file and hit Generate Bitstream.
Note, all the logic that's necessary is already implemented!
Once you hit generate bitstream, it'll take a couple of minutes to compile.
Afterwards, you'll get a top.bit
file under your project folder under <project name>.runs
and impl_1
.
You can use Vivado's Hardware Manager to load this bitstream on the FPGA.
Note, this does not program the non-volatile memory, hence when you power cycle the FPGA board the bitstream will be lost.
If you're really lazy, you can just download the latest bitstream file here.
Note, you'll still need Vivado or some other tool to flash the binary to the FPGA.
Connecting to the Olimex board
Once you have the bitstream compiled and loaded onto the FPGA we need to connect the FPGA to the UART of the Olimex P1343 board.
If you take a look at the Arty_Master.xdc file you'll see that board_tx
(output of the FPGA) and board_rx
(input of the FPGA) are connected to IO[26]
and IO[27]
, respectively.
Connect board_tx
(pin 26) and board_rx
(pin 27) to rxd
and txd
of the UEXT header of the Olimex P1343 (see below).
You can also power the board from the FPGA by connecting the VCC and GND of the board to the VCC and GND of one of the board's PMOD headers.
At this point you should be able to communicate from the PC to the Olimex P1343 through Digilent Arty board.
Set the jumper on the board and hit reset button on the board.
Assuming the board is powered up, you should see a yellow LED come on indicating the board is in the USB ISP bootloader.
Now, connect PIO0_3
to a ground pin (see below).
The yellow LED should now turn off.
Hit reset one more time and the yellow LED should stay off.
Now the board is in the Serial bootloader and ready to receive commands.
Initially I recommend you use a serial terminal to manually type in the bootloader commands and talk to the board.
On Linux the tty-device you are looking for will be /dev/ttyUSB1
, since we're using a 2-channel FTDI and /dev/ttyUSB0
is used for configuring the FPGA.
If you're using picocom, you can use picocom -b 115200 /dev/ttyUSB1
. Type ?
and you should see a "Syncrhonized" appear.
Follow steps 1-9 listed in the previous section to see how the bootloader works.
Python
In my experience, using libusb and the libusb wrapper for FTDI devices, libftdi, is far more reliable than using the serial port emulators. For communicating with hardware from python, I like to use pylibftdi. The only notable downside of using libftdi/pylibftdi is that the serial device (/dev/tty) will disappear whenever you execute your code. For me this is yet another reason to use VMware as I can disconnect and reconnect the USB in VMware instead of physically disconnecting the cable and inadvertently powercycling the boards.
You can see an example script in the arty-passthrough Github repo.
What's next?
Now you have all the primitives you'll need for interfacing to the bootloader and reading out the firmware. The bootloader will return data that is UUencoded. The first thing you'll need to do is write a script capable of extracting firmware from an unlocked device.
When you send a read command, i.e. read 4 bytes from address 0, you'll send R 0 4\r\n
and the response will show up as follows in a serial terminal:
$________
1020
So what you'll actually get is something like R 0 4\r0\r\n$________\r\n1020\r\n
. Don't forget to send an OK\r\n
to the board, otherwise the read will fail. Also remember that addresses have to be multiples of 4.
In the next post, we'll pick up from here and begin writing the HDL code necessary for implementing the glitcher. I'll also reveal my solution in the next post.
Can't wait for the solution?
Sign up for our mailing list and we'll send you the full solution for all four parts right away, so you won't have to wait until we publish all the materials.
Consider taking a training
If you're new to all of this, you should consider taking one of our trainings. This specific assignment is part of our five day course, which we currently only offer in Berlin. We also offer onsite trainings for companies, starting at just 5 participants. If you're building out a lab or need to teach your engineers on the proper use of lab equipment, we can help.
Questions? Comments?
You can always DM me on twitter or email me at dmitry [at] toothless.co.