Update 2017-08-19: Updated Links, linked to follow on parts
So first of all, let's pick up where we left off in Part 1 (also remember to checkout Part 3).
If you haven't yet read and worked through, Part 1 you should start there.
Last week I shared a working passthrough configuration for the Digilent Arty board on Github.
This logic implemented in this design essentially bypasses the FPGA, exposing the TX/RX of the second channel of the FTDI 2232H on pins IO[26]
and IO[27]
.
Using this configuration, you should be able to connect using the python script included in the project.
Communicating with python
First things first, we'll need to be able to read data from the device. The problem we'll eventually run into (when glitching) is that either the data be corrupted because we'll genuinely get garbage data back or because the voltage will drop so low, that the FPGA will fail to decode the bytes properly. No matter the reason, what we'll need is a non-blocking read function. We'll log the errors to terminal, but make sure not to match resulting strings in such a way that they block the predicates within your python logic.
Non-blocking Read
This code will allow you to check if the response matches the expected
string.
If it does, it will return None
, otherwise it will return the result that was read and output the error to stdout
.
Remember, you can use the repr
function to print non-printable characters as a python unicode string which you can use in your python code.
def expect_read(expected):
result = ""
# Don't attempt to read more than 10 times
for i in range(0,10):
result += dev.read(len(expected))
if expected in result:
return None;
print "\tExpected = " + repr(expected) + " got " + repr(result)
return result
Synchronizing with the Bootloader
Now, we'll need a function to synchronize the device.
The bootloader is stateful and requires a couple of things, as described in Part 1.
We'll need to send a ?
, send Sychronized\r\n
, set the clock rate and check the responses.
One thing that can be tricky is that the NXP LPC bootloader does local echo by default, so you will see some of your data getting echoed back.
Most notably however, the LPC will not echo back the \n
character that you send it, but you'll be able to verify this with some trial and error.
You can simply write a function like my expect_read
function, to do "best effort" reads and return whether or not the data was as expected:
def synchronize():
# Detect baud rate
dev.write('?')
# Wait for 'Synchronized\r\n'
expect_read('Synchronized\r\n')
# Reply 'Synchronized\r\n'
dev.write('Synchronized\r\n')
# Verify 'Synchronized\rOK\r\n'
expect_read('Synchronized\rOK\r\n')
# Set a clock rate (value doesn't matter)
dev.write("12000\r\n")
# Verify OK
expect_read('12000\rOK\r\n')
Reading an address
Eventually, we'll simply read arbitrary addresses from the LPC1343 target board, but first we need to check the CRP value. CRP, or Code Read Protection, disables some of the debug functionality of the device. Most notably, setting a CRP value of CRP1 or higher will disable the read command. Hence, we can simply try to read the first address of memory and if this fails CRP is set.
The NXP UM10375 (page 332) describes the read command and states that the first character of the response determines whether or not the command succeeded or not.
If the command succeeded the response will be 0
, so a response like R 0 4\r0\r\n$________\r\n1020\r\n
succeeded, while a response like R 0 4\r19\r\n
failed.
If the command succeeded, the host most confirm it with OK\r\n
.
The NXP UM10375 (page 332) also states, "The length of any UU-encoded line should not exceed 61 characters (bytes) ".
Hence, we should read up to 61 characters of data from the device.
Remember the pylibftdi
read is non-blocking.
Hence, even though we'll be reading less in most cases, this won't be a problem.
This is also why we append the result
string.
So what we need is something like this:
def read_address(address, length):
cmd = 'R {:d} {:d}\r\n'.format(address, length)
dev.write(cmd)
result = ""
# Don't attempt to read more than 10 times
for i in range(0,10):
result += dev.read(61)
if '\r\n' in result:
break
# Check if command succeeded.
if '\r0' in result:
dev.write('OK\r\n')
expect_read('OK\r\n')
return result
else:
return None
Checking the CRP Value
Finally even though read_address
will do all the heavy lifting, let's create a test_crp
function for good measure.
To test the CRP value, we can simply read 4 bytes from address 0, i.e. call read_address(0,4)
.
Most firmwares of course won't set the CRP value, so even the default firmware on the board does not set CRP.
I will provide binaries that do set the CRP value as part of Part 3.
def test_crp():
result = read_address(0,4)
if result:
print "DEVICE UNLOCKED"
print repr(result)
return result
print "device is locked."
return None
Building the Glitcher
The ultimate goal for Part 2 is to create the logic necessary for configuring and executing the glitch after reseting the LPC1343 target board. Since the LPC1343 does not randomize it's clock at boot in any way, simply counting FPGA clock cycles after reset will be a reliable enough way to place the glitch. In the next part, we'll use one of the output pins of the FPGA to control a select line of a Maxim Max4619 to rapidly switch between two voltages that we will configure. So we'll use the FPGA to reset the target board, count clock cycles and eventually glitch the target by changing the signal on the select line of the multiplexer.
Ultimately, we'll be able to control the glitch pulse width and delay from python and implement all the control logic required for moving the pulse position and configuring the duration of the pulse from python.
On an oscilloscope (I recommend and use the Rigol DS2072A myself) the result will look something like this:
A preview of this week's blog post: building the logic for the glitcher. Go hack some hardware. https://t.co/Gtz4PWXCbH pic.twitter.com/RygeyvEkBz
— Dmitry Nedospasov (@nedos) 8 August 2017
Glitcher Design
Let's go over the high level design first.
cmd
module - Since we're sending UART data to the target board as well as the FPGA, it's easiest to design a simple protocol to be able to differentiate between the two. This module is responsible for decoding, which data goes to board and which data is destined for the FPGA. this module is also responsible for driving the inputs of all the other modules.resetter
module - This module simply holds the line low for enough clock cycles for the LPC1343 target board to fully reset. It is triggered by theboard_rst
from thecmd
module.delay
module - This module is responsible for delaying by a certain amount of clock cycles before the glitch takes place. It recieveswidth
anddelay
from thecmd
module and has ardy
signal, which is high whenever the module is not running.trigger
moudle - This module consists of a simple 2-state state machine that checks therdy
of thedelay
module after aboard_rst
has occurred. Once the delay module has finished, i.e.rdy
is high, it set it's outputvalid
.pulse
module - This is the actual module that outputs a pulse. We will eventually connect this signal to the select line of the Maxim Max4619. It receiveswidth
andcnt
from thecmd
module. Thewidth
signal determines the width of the pulse in clock cycles,cnt
determines how many times this should be repeated.
Block Diagram
All in all, you get a block diagram like this:
Simple Communications Protocol
One of the simplest and most reliable protocols one can implement is to send variable length data to the target and fixed length data to the FPGA.
The easiest realization of this is simply to send a binary length byte, followed by the serial data to the target board.
Hence, anything that begins with a null byte, i.e. \x00
, is a 'special' byte for the FPGA.
So when we want to communicate with the board, we can do something along the lines of:
def board_write(msg):
length = struct.pack('B',len(msg))
dev.write(length + msg)
And the 'special' messages to the FPGA, i.e. the messages starting with \x00
, can look like this:
def reset_fpga():
dev.write('\x00\xff')
def reset_board():
dev.write('\x00\xfe')
def glitch():
dev.write('\x00\x00')
In this example \x00\xff
resets the FPGA, \x00\xfe
resets the board and \x00\x00
enables the glitch.
One thing worth mentioning is that if you're decoding data in the FPGA, you'll need a FIFO because UART receivers usually require fewer clock cycles to decode a byte of data than UART transmitters need to send a byte of data. The simple solution to this is to buffer the data using a FIFO, which then feeds the data to the transmitter, see the block diagram. Fortunately, Vivado has the IP catalog that will let you synthesize a FIFO with the flags that you'll want.
What's next?
Now we've covered all the modules we'll need to build a glitcher, as well as what python code will be necessary.
In total you'll need to write 4 modules, as well as instantiate a UART transmitter, a UART receiver and FIFO.
The cmd
module will likely consist of a large state machine to differential between a special byte and data to the target board.
For the delay
and pulse
modules, it's sufficient to use two state state machines (with state idle and running) with counters that count up every clock cycle (default assignments).
In the next part we'll load up firmware with CRP1 set and begin to put it all together.
Until then you should build and test all the pieces, verifying that you can set all of the paramters you'll need to glitch the device.
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.