Part 2, Topic 2: Introduction to Voltage Glitching (MAIN)¶
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: While it's not as sophisticated as the ChipWhisperer Lite or ChipWhisperer Pro's glitch hardware, the ChipWhisperer Nano is also capable of glitching. In this lab, we'll do some simple glitch tests on the Nano's target board, showing how to scan through glitch settings and seeing what effect it has on the hardware.
LEARNING OUTCOMES:
- Understanding how voltage glitching can be used to disrupt a target's operation
- Scanning glitch settings to determine successful ones
Digital hardware devices have certain voltage and clock requirements to function properly. If these requirements are not met, the device can fail to function, or even be damage. By shorting the voltage pins of a microcontroller for controlled, short periods of time, we can cause it to behave erratically, clearning registers and skipping instructions. Such attacks can be immensely powerful in practice. Consider for example the following code from linux-util-2.24
:
/*
* auth.c -- PAM authorization code, common between chsh and chfn
* (c) 2012 by Cody Maloney <cmaloney@theoreticalchaos.com>
*
* this program is free software. you can redistribute it and
* modify it under the terms of the gnu general public license.
* there is no warranty.
*
*/
#include "auth.h"
#include "pamfail.h"
int auth_pam(const char *service_name, uid_t uid, const char *username)
{
if (uid != 0) {
pam_handle_t *pamh = NULL;
struct pam_conv conv = { misc_conv, NULL };
int retcode;
retcode = pam_start(service_name, username, &conv, &pamh);
if (pam_fail_check(pamh, retcode))
return FALSE;
retcode = pam_authenticate(pamh, 0);
if (pam_fail_check(pamh, retcode))
return FALSE;
retcode = pam_acct_mgmt(pamh, 0);
if (retcode == PAM_NEW_AUTHTOK_REQD)
retcode =
pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
if (pam_fail_check(pamh, retcode))
return FALSE;
retcode = pam_setcred(pamh, 0);
if (pam_fail_check(pamh, retcode))
return FALSE;
pam_end(pamh, 0);
/* no need to establish a session; this isn't a
* session-oriented activity... */
}
return TRUE;
}
This is the login code for the Linux OS. Note that if we could skip the check of if (uid != 0)
and simply branch to the end, we could avoid having to enter a password. This is the power of glitch attacks - not that we are breaking encryption, but simply bypassing the entire authentication module!
Glitch Hardware¶
The ChipWhisperer Nano's glitch setup is pretty simple. Like its bigger brothers, the Lite and the Pro, it uses a MOSFET to short the microcontroller's voltage supply to ground:
For the Nano, Glitch In
is controlled by 2 parameters:
scope.glitch.ext_offset
- The glitch will be inserted roughly8.3ns * scope.glitch.ext_offset
scope.glitch.repeat
- The glitch will be inserted for roughly8.3ns * scope.glitch.repeat
During this lab, we'll be varying these parameters to see if we can get the target to mess up a calculation that it's doing.
SCOPETYPE = 'CWNANO'
PLATFORM = 'CWNANO'
SS_VER = 'SS_VER_2_1'
VERSION = 'HARDWARE'
allowable_exceptions = None
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, 6))
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, 6))
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😍
%%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
.
arm-none-eabi-gcc (15:9-2019-q4-0ubuntu1) 9.2.1 20191025 (release) [ARM/arm-9-branch revision 277599]
Copyright (C) 2019 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.
Welcome to another exciting ChipWhisperer target build!!
.
.
Compiling:
Compiling:
-en simpleserial-glitch.c ...
-en .././simpleserial/simpleserial.c ...
+--------------------------------------------------------
+ Built for platform CWNANO Built-in Target (STM32F030) with:
+ CRYPTO\_TARGET = NONE
+ CRYPTO\_OPTIONS =
+--------------------------------------------------------
-e Done!
-e Done!
.
LINKING:
-en simpleserial-glitch-CWNANO.elf ...
-e Done!
.
.
Creating load file for Flash: simpleserial-glitch-CWNANO.hex
arm-none-eabi-objcopy -O ihex -R .eeprom -R .fuse -R .lock -R .signature simpleserial-glitch-CWNANO.elf simpleserial-glitch-CWNANO.hex
Creating load file for Flash: simpleserial-glitch-CWNANO.bin
arm-none-eabi-objcopy -O binary -R .eeprom -R .fuse -R .lock -R .signature simpleserial-glitch-CWNANO.elf simpleserial-glitch-CWNANO.bin
.
.
.
Creating load file for EEPROM: simpleserial-glitch-CWNANO.eep
Creating Extended Listing: simpleserial-glitch-CWNANO.lss
arm-none-eabi-objcopy -j .eeprom --set-section-flags=.eeprom="alloc,load" \
--change-section-lma .eeprom=0 --no-change-warnings -O ihex simpleserial-glitch-CWNANO.elf simpleserial-glitch-CWNANO.eep || exit 0
Creating Symbol Table: simpleserial-glitch-CWNANO.sym
arm-none-eabi-objdump -h -S -z simpleserial-glitch-CWNANO.elf > simpleserial-glitch-CWNANO.lss
arm-none-eabi-nm -n simpleserial-glitch-CWNANO.elf > simpleserial-glitch-CWNANO.sym
Size after:
text data bss dec hex filename
5380 12 1364 6756 1a64 simpleserial-glitch-CWNANO.elf
fw_path = "../../../firmware/mcu/simpleserial-glitch/simpleserial-glitch-{}.hex".format(PLATFORM)
cw.program_target(scope, prog, fw_path)
Detected known STMF32: STM32F04xxx Extended erase (0x44), this can take ten seconds or more Attempting to program 5391 bytes at 0x8000000 STM32F Programming flash...
STM32F Reading flash...
Verified flash OK, 5391 bytes
scope.io.clkout = 7.5E6
def reboot_flush():
scope.io.nrst = False
time.sleep(0.05)
scope.io.nrst = "high_z"
time.sleep(0.05)
#Flush garbage too
target.flush()
scope
ChipWhisperer Nano Device fw_version = major = 0 minor = 66 debug = 0 io = tio1 = None tio2 = None tio3 = None tio4 = high_z pdid = True pdic = False nrst = True clkout = 7500000.0 cdc_settings = bytearray(b'\x01\x00\x00\x00') adc = clk_src = int clk_freq = 7500000.0 samples = 5000 glitch = repeat = 0 ext_offset = 500 errors = sam_errors = False sam_led_setting = Default
reboot_flush()
scope.arm()
target.simpleserial_write("g", bytearray([]))
scope.capture()
val = target.simpleserial_read_witherrors('r', 4, 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'c4 09 00 00'), 'full\_response': CWbytearray(b'00 72 04 c4 09 00 00 15 00'), 'rv': bytearray(b'\x00')}
gc = cw.GlitchController(groups=["success", "reset", "normal"], parameters=["repeat", "ext_offset"])
gc.display_stats()
Some tips for finding good glitches:
- This is a VCC line that we're shorting, so there's going to be stuff fighting against us. If your glitch is too short, it might not have any effect
- Likewise, if your glitch is too long, the target will always crash. There's typically a small band where you're able to affect the target, but it won't always crash it.
- Be patient. Glitching can be somewhat inconsistant, so don't be discouraged if it takes a while to see some success!
gc.glitch_plot(plotdots={"success":"+g", "reset":"xr", "normal":None})
from importlib import reload
import chipwhisperer.common.results.glitch as glitch
from tqdm.notebook import trange
import struct
g_step = 1
gc.set_global_step(g_step)
gc.set_range("repeat", 1, 10)
gc.set_range("ext_offset", 1, 500)
scope.glitch.repeat = 0
reboot_flush()
sample_size = 1
for glitch_setting in gc.glitch_values():
scope.glitch.repeat = glitch_setting[0]
scope.glitch.ext_offset = glitch_setting[1]
successes = 0
resets = 0
for i in range(3):
target.flush()
scope.arm()
#Do glitch loop
target.simpleserial_write("g", bytearray([]))
ret = scope.capture()
val = target.simpleserial_read_witherrors('r', 4, glitch_timeout=10)#For loop check
if ret:
print('Timeout - no trigger')
gc.add("reset")
resets += 1
#Device is slow to boot?
reboot_flush()
else:
if val['valid'] is False:
reboot_flush()
gc.add("reset")
resets += 1
else:
gcnt = struct.unpack("<I", val['payload'])[0]
if gcnt != 2500: #for loop check
gc.add("success")
print(gcnt)
successes += 1
else:
gc.add("normal")
if successes > 0:
print("successes = {}, resets = {}, repeat = {}, ext_offset = {}".format(successes, resets, scope.glitch.repeat, scope.glitch.ext_offset))
print("Done glitching")
Done glitching
gc.plot_2d(alpha=False)
[0, 1]
['repeat', 'ext\_offset'] (1, 1)
scope.dis()
target.dis()
Unlike the other ChipWhisperers, the Nano doesn't have sychronous glitching. This means that ext_offset
is a mixture of both the offset within the clock cycle, which affects glitch success, and ext_offset, which affects which instruction is being glitched. As such, ext_offset settings you find in this lab won't be directly applicable to other labs. That being said, good ranges for repeat and the success rate of glitches still gives valuable information that you can apply to other labs.