As of August 2020 the site you are on (wiki.newae.com) is deprecated, and content is now at rtfm.newae.com.

Difference between revisions of "Making Scripts"

From ChipWhisperer Wiki
Jump to: navigation, search
(Updated scripts and added stm32 scripts)
 
(7 intermediate revisions by 2 users not shown)
Line 1: Line 1:
<H1>THIS PAGE IS OLD - ONLY FOR 3.X SCRIPTS. WILL BE UPDATED SHORTLY. </H1>
+
{{Warningbox|For the older V3.x tools, see [[V3:Making_Scripts]]}}
  
=The Basic=
+
== Scripting with ChipWhisperer as a python module ==  
User scripts allows partial (i.e.: setting up the environment) or total automation of the execution flow.  
+
When used without the GUI, the 4.0 API removes much of the high level abstractions so you can have more control over the capture process. It also answers questions like--when I capture a trace in what order are things happening? The following example scripts will give you a starting point when scripting with the ChipWhisperer tool. All these examples on this page can be found in their home <code>chipwhisperer/software/scripting-examples</code>.
  
A basic script would look like this:
+
=== Perform Some Traces during AES encryption and get the results as Numpy array ===
 +
This script is an example of using the <code>chipwhisperer</code> module for capturing traces during AES encryption. <b>Make sure to have the correct firmware loaded on the target.</b> These traces are then saved and loaded later for analysis.
  
<pre>
+
==== XMEGA Target ====
from chipwhisperer.common.scripts.base import UserScriptBase
+
<syntaxhighlight lang=python>
 +
from __future__ import division, print_function
  
 +
import time
 +
import os
  
class UserScript(UserScriptBase):
+
import numpy as np
    _name = "ChipWhisperer-Lite: AES SimpleSerial on XMEGA"
+
import matplotlib.pyplot as plt
    _description = "SimpleSerial with Standard Target for AES (XMEGA)"
+
from datetime import datetime
 +
from tqdm import tqdm
  
    def __init__(self, api):
+
import chipwhisperer as cw
        super(UserScript, self).__init__(api)
+
from chipwhisperer.capture.acq_patterns.basic import AcqKeyTextPattern_Basic
 +
from chipwhisperer.tests.tools_for_tests import FIRMWARE_DIR
 +
from chipwhisperer.capture.api.programmers import XMEGAProgrammer
  
    def run(self):
+
scope = cw.scope()
        #User commands here
+
target = cw.target(scope)
        self.api.setParameter(['Generic Settings', 'Scope Module', 'ChipWhisperer/OpenADC'])
+
        self.api.setParameter(['Generic Settings', 'Target Module', 'Simple Serial'])
+
        self.api.setParameter(['Generic Settings', 'Trace Format', 'ChipWhisperer/Native'])
+
        self.api.setParameter(['Simple Serial', 'Connection', 'ChipWhisperer-Lite'])
+
        self.api.setParameter(['ChipWhisperer/OpenADC', 'Connection', 'ChipWhisperer-Lite'])
+
               
+
        self.api.connect()
+
       
+
        #Example of using a list to set parameters. Slightly easier to copy/paste in this format
+
        lstexample = [['CW Extra Settings', 'Trigger Pins', 'Target IO4 (Trigger Line)', True],
+
                      ['CW Extra Settings', 'Target IOn Pins', 'Target IO1', 'Serial RXD'],
+
                      ['CW Extra Settings', 'Target IOn Pins', 'Target IO2', 'Serial TXD'],
+
                      ['OpenADC', 'Clock Setup', 'CLKGEN Settings', 'Desired Frequency', 7370000.0],
+
                      ['CW Extra Settings', 'Target HS IO-Out', 'CLKGEN'],
+
                      ['OpenADC', 'Clock Setup', 'ADC Clock', 'Source', 'CLKGEN x4 via DCM'],
+
                      ['OpenADC', 'Trigger Setup', 'Total Samples', 3000],
+
                      ['OpenADC', 'Trigger Setup', 'Offset', 1250],
+
                      ['OpenADC', 'Gain Setting', 'Setting', 45],
+
                      ['OpenADC', 'Trigger Setup', 'Mode', 'rising edge'],
+
                      #Final step: make DCMs relock in case they are lost
+
                      ['OpenADC', 'Clock Setup', 'ADC Clock', 'Reset ADC DCM', None],
+
                      ]
+
        for cmd in lstexample: self.api.setParameter(cmd)
+
       
+
        #Let's only do a few traces
+
        self.api.setParameter(['Generic Settings', 'Acquisition Settings', 'Number of Traces', 50])
+
                     
+
        #The environment is already set, lets do our first capture
+
        self.api.capture1()
+
</pre>
+
  
User scripts should inherit from UserScriptBase that specifies the run() method that is called when clicking it in the menu or pressing the attack button (in the analyzer tool).
+
# setup scope parameters
 +
scope.gain.gain = 45
 +
scope.adc.samples = 3000
 +
scope.adc.offset = 1250
 +
scope.adc.basic_mode = "rising_edge"
 +
scope.clock.clkgen_freq = 7370000
 +
scope.clock.adc_src = "clkgen_x4"
 +
scope.trigger.triggers = "tio4"
 +
scope.io.tio1 = "serial_rx"
 +
scope.io.tio2 = "serial_tx"
 +
scope.io.hs2 = "clkgen"
  
The API is passed as an argument by the GUI through the constructor in order to allow the script to "remote control" the existing section. A name and a description should also be specified.
+
# program the target
 +
programmer = XMEGAProgrammer()
 +
programmer.scope = scope
 +
programmer._logging = None
 +
programmer.find()
 +
programmer.erase()
 +
aes_firmware_dir = os.path.join(FIRMWARE_DIR, 'simpleserial-aes')
 +
aes_hex = os.path.join(aes_firmware_dir, r"simpleserial-aes-CW303.hex")
 +
programmer.program(aes_hex, memtype="flash", verify=True)
 +
programmer.close()
  
==Running from the Terminal==
+
ktp = AcqKeyTextPattern_Basic(target=target)
This step is only needed if you want to run the script from the terminal. In this case, you don't need to use the GUI, the capture can be performed using only the API. In order to do it, you should add the following lines to the end of your script file:
+
  
<pre>
+
traces = []
if __name__ == '__main__':
+
textin = []
    from chipwhisperer.common.api.CWCoreAPI import CWCoreAPI
+
keys = []
    api = CWCoreAPI()              # Instantiate the API
+
N = 50  # Number of traces
    api.runScriptClass(UserScript) # Run UserScript through the API
+
target.init()
</pre>
+
for i in tqdm(range(N), desc='Capturing traces'):
or if you want the GUI:
+
    # run aux stuff that should come before trace here
  
<pre>
+
     key, text = ktp.newPair()  # manual creation of a key, text pair can be substituted here
if __name__ == '__main__':
+
     textin.append(text)
    from chipwhisperer.common.api.CWCoreAPI import CWCoreAPI
+
     keys.append(key)
    import chipwhisperer.capture.ui.CWCaptureGUI as cwc      # Import the ChipWhispererCapture GUI
+
    from chipwhisperer.common.utils.parameter import Parameter
+
     app = cwc.makeApplication()   
+
    Parameter.usePyQtGraph = True
+
    api = CWCoreAPI()              # Instantiate the API
+
     gui = cwc.CWAnalyzerGUI(api)   # Instantiate the GUI
+
     gui.show()  
+
    api.runScriptClass(UserScript)  # Run UserScript through the API
+
  
     app.exec_()
+
     target.reinit()
</pre>
+
  
==Adding user scripts to the GUI menu==
+
    target.setModeEncrypt()  # only does something for targets that support it
New scripts can be added to the tool menu automatically by saving it in its respective script folder inside the chipwhisperer installation folder or user projects folder:
+
    target.loadEncryptionKey(key)
 +
    target.loadInput(text)
  
* chipwhisperer/software/chipwhisperer/capture/scripts
+
    # run aux stuff that should run before the scope arms here
* chipwhisperer/software/chipwhisperer/analyzer/scripts
+
* ~/chipwhisperer_projects/chipwhisperer/capture/scripts
+
* ~/chipwhisperer_projects/chipwhisperer/analyzer/scripts
+
  
Files put in these directories are scanned during the GUI initialization and all UserScriptBase classes are added to the menu. You can copy and past the content of the ''Analysis Script'' window to a text editor or use the ''Attack Script Generator''->''Attack Script''->''Copy'' option.
+
    scope.arm()
  
== Other examples==
+
    # run aux stuff that should run after the scope arms here
The directories listed above already have some examples which can be used as a reference to create new scripts. More advanced scripts can be located in the chipwhisperer/software/chipwhisperer/tests folder.
+
  
Scripts auto-generated by the analyzer tool can also be executed standalone or saved into the scripts directory so that it will show up in the next GUI execution.
+
    target.go()
 +
    timeout = 50
 +
    # wait for target to finish
 +
    while target.isDone() is False and timeout:
 +
        timeout -= 1
 +
        time.sleep(0.01)
  
=Advanced=
+
    try:
 +
        ret = scope.capture()
 +
        if ret:
 +
            print('Timeout happened during acquisition')
 +
    except IOError as e:
 +
        print('IOError: %s' % str(e))
  
If you decide to run both tools in sequence, do as follows:
+
    # run aux stuff that should happen after trace here
 +
    _ = target.readOutput()  # clears the response from the serial port
 +
    traces.append(scope.getLastTrace())
 +
trace_array = np.asarray(traces)  # if you prefer to work with numpy array for number crunching
 +
textin_array = np.asarray(textin)
 +
known_keys = np.asarray(keys)  # for fixed key, these keys are all the same
  
<pre>
+
now = datetime.now()
if __name__ == '__main__':
+
fmt_string = '{:02}{:02}_{}.npy'
    from chipwhisperer.common.api.CWCoreAPI import CWCoreAPI
+
trace_file_path = fmt_string.format(now.hour, now.minute, "traces")
    import chipwhisperer.capture.ui.CWCaptureGUI as cwc
+
textin_file_path = fmt_string.format(now.hour, now.minute, "textins")
    import chipwhisperer.analyzer.ui.CWAnalyzerGUI as cwa
+
keys_file_path = fmt_string.format(now.hour, now.minute, "keys")
    from chipwhisperer.common.utils.parameter import Parameter
+
    app = cwc.makeApplication()
+
    Parameter.usePyQtGraph = True
+
    api = CWCoreAPI()             # Instantiate the API
+
    gui = cwc.CWCaptureGUI(api)  # Instantiate the Capture GUI
+
    gui.show()
+
    api.runScriptClass(Capture)
+
    gui.close()
+
    gui.reset()                   # Delete saved geometry settings in the Capture tool so it will not be used by the Analyzer
+
  
    gui = cwa.CWAnalyzerGUI(api) # Instantiate the Analyzer GUI
+
print('Saving results to {},{} and {}...'.format(trace_file_path, textin_file_path, keys_file_path), end='')
    gui.show()
+
# save to a files for later processing
    api.runScriptClass(Attack)   # Run the script (default is the "run" method)
+
np.save(trace_file_path, trace_array)
 +
np.save(textin_file_path, textin_array)
 +
np.save(keys_file_path, known_keys)
 +
print('Done')
  
     app.exec_()
+
# uncomment plt.show() to show an example trace
</pre>
+
plt.plot(traces[0])
 +
#plt.show()
 +
 
 +
# cleanup the connection to the target and scope
 +
scope.dis()
 +
target.dis()
 +
</syntaxhighlight>
 +
 
 +
==== STM32F3 Target ====
 +
<syntaxhighlight lang=python>
 +
from __future__ import division, print_function
 +
 
 +
import time
 +
import os
 +
 
 +
import numpy as np
 +
import matplotlib.pyplot as plt
 +
from datetime import datetime
 +
from tqdm import tqdm
 +
 
 +
import chipwhisperer as cw
 +
from chipwhisperer.capture.acq_patterns.basic import AcqKeyTextPattern_Basic
 +
from chipwhisperer.tests.tools_for_tests import FIRMWARE_DIR
 +
from chipwhisperer.capture.api.programmers import STM32FProgrammer
 +
 
 +
scope = cw.scope()
 +
target = cw.target(scope)
 +
 
 +
# setup scope parameters
 +
scope.gain.gain = 45
 +
scope.adc.samples = 5000
 +
scope.adc.offset = 0
 +
scope.adc.basic_mode = "rising_edge"
 +
scope.clock.clkgen_freq = 7370000
 +
scope.clock.adc_src = "clkgen_x4"
 +
scope.trigger.triggers = "tio4"
 +
scope.io.tio1 = "serial_rx"
 +
scope.io.tio2 = "serial_tx"
 +
scope.io.hs2 = "clkgen"
 +
 
 +
# program the target
 +
programmer = STM32FProgrammer()
 +
programmer.scope = scope
 +
programmer._logging = None
 +
programmer.open()
 +
programmer.find()
 +
programmer.erase()
 +
aes_firmware_dir = os.path.join(FIRMWARE_DIR, 'simpleserial-aes')
 +
aes_hex = os.path.join(aes_firmware_dir, r"simpleserial-aes-CW308_STM32F3.hex")
 +
programmer.program(aes_hex, memtype="flash", verify=True)
 +
programmer.close()
 +
 
 +
ktp = AcqKeyTextPattern_Basic(target=target)
 +
 
 +
traces = []
 +
textin = []
 +
keys = []
 +
N = 50  # Number of traces
 +
target.init()
 +
for i in tqdm(range(N), desc='Capturing traces'):
 +
     # run aux stuff that should come before trace here
 +
 
 +
    key, text = ktp.newPair()  # manual creation of a key, text pair can be substituted here
 +
    textin.append(text)
 +
    keys.append(key)
 +
 
 +
    target.reinit()
 +
 
 +
    target.setModeEncrypt()  # only does something for targets that support it
 +
    target.loadEncryptionKey(key)
 +
    target.loadInput(text)
 +
 
 +
    # run aux stuff that should run before the scope arms here
 +
 
 +
    scope.arm()
 +
 
 +
    # run aux stuff that should run after the scope arms here
 +
 
 +
    target.go()
 +
    timeout = 50
 +
    # wait for target to finish
 +
    while target.isDone() is False and timeout:
 +
        timeout -= 1
 +
        time.sleep(0.01)
 +
 
 +
    try:
 +
        ret = scope.capture()
 +
        if ret:
 +
            print('Timeout happened during acquisition')
 +
    except IOError as e:
 +
        print('IOError: %s' % str(e))
 +
 
 +
    # run aux stuff that should happen after trace here
 +
 
 +
    _ = target.readOutput() # throw out the target response
 +
    traces.append(scope.getLastTrace())
 +
trace_array = np.asarray(traces)  # if you prefer to work with numpy array for number crunching
 +
textin_array = np.asarray(textin)
 +
known_keys = np.asarray(keys)  # for fixed key, these keys are all the same
 +
 
 +
now = datetime.now()
 +
fmt_string = '{:02}{:02}_{}.npy'
 +
trace_file_path = fmt_string.format(now.hour, now.minute, "traces")
 +
textin_file_path = fmt_string.format(now.hour, now.minute, "textins")
 +
keys_file_path = fmt_string.format(now.hour, now.minute, "keys")
 +
 
 +
print('Saving results to {},{} and {}...'.format(trace_file_path, textin_file_path, keys_file_path), end='')
 +
# save to a files for later processing
 +
np.save(trace_file_path, trace_array)
 +
np.save(textin_file_path, textin_array)
 +
np.save(keys_file_path, known_keys)
 +
print('Done')
 +
 
 +
# show an example trace
 +
plt.plot(traces[0])
 +
plt.show()
 +
 
 +
# cleanup the connection to the target and scope
 +
scope.dis()
 +
target.dis()
 +
</syntaxhighlight>
 +
 
 +
=== Manually breaking AES encryption with your recorded traces (As much as scripting is manual) ===
 +
Using the saved traces of the AES encryption you can now break the sub-keys of the encryption key. This script is covered in more detail in [[Tutorial_B6_Breaking_AES_(Manual_CPA_Attack) | Tutorial B6]].
 +
 
 +
<syntaxhighlight lang=python>
 +
from __future__ import division, print_function
 +
 
 +
import numpy as np
 +
from tqdm import tqdm
 +
 
 +
HW = [bin(n).count("1") for n in range(0, 256)]
 +
 
 +
sbox = (
 +
    0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
 +
    0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
 +
    0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
 +
    0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
 +
    0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
 +
    0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
 +
    0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
 +
    0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
 +
    0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
 +
    0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
 +
    0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
 +
    0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
 +
    0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
 +
    0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
 +
    0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
 +
    0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16)
 +
 
 +
 
 +
def intermediate(pt, keyguess):
 +
    return sbox[pt ^ keyguess]
 +
 
 +
# put the actual file names in here
 +
keys = np.load('1147_keys.npy')
 +
textins = np.load('1147_textins.npy')
 +
traces = np.load('1147_traces.npy')
 +
 
 +
knownkey = keys[0] # for fixed key they are all the same
 +
pt = textins
 +
numtraces = np.shape(traces)[0]
 +
numpoint = np.shape(traces)[1]
 +
 
 +
bestguess = [0] * 16
 +
pge = [256] * 16
 +
for bnum in tqdm(range(0, 16), desc='Attacking subkeys'):
 +
    cpaoutput = [0] * 256
 +
    maxcpa = [0] * 256
 +
    for kguess in range(0, 256):
 +
 
 +
        # Initialize arrays &amp; variables to zero
 +
        sumnum = np.zeros(numpoint)
 +
        sumden1 = np.zeros(numpoint)
 +
        sumden2 = np.zeros(numpoint)
 +
 
 +
        hyp = np.zeros(numtraces)
 +
        for tnum in range(0, numtraces):
 +
            hyp[tnum] = HW[intermediate(pt[tnum][bnum], kguess)]
 +
 
 +
        # Mean of hypothesis
 +
        meanh = np.mean(hyp, dtype=np.float64)
 +
 
 +
        # Mean of all points in trace
 +
        meant = np.mean(traces, axis=0, dtype=np.float64)
 +
 
 +
        # For each trace, do the following
 +
        for tnum in range(0, numtraces):
 +
            hdiff = (hyp[tnum] - meanh)
 +
            tdiff = traces[tnum, :] - meant
 +
 
 +
            sumnum = sumnum + (hdiff * tdiff)
 +
            sumden1 = sumden1 + hdiff * hdiff
 +
            sumden2 = sumden2 + tdiff * tdiff
 +
 
 +
        cpaoutput[kguess] = sumnum / np.sqrt(sumden1 * sumden2)
 +
        maxcpa[kguess] = max(abs(cpaoutput[kguess]))
 +
 
 +
    bestguess[bnum] = np.argmax(maxcpa)
 +
 
 +
    cparefs = np.argsort(maxcpa)[::-1]
 +
 
 +
    # Find PGE
 +
    pge[bnum] = list(cparefs).index(knownkey[bnum])
 +
 
 +
print("Best Key Guess: ", end="")
 +
for b in bestguess: print("%02x " % b, end="")
 +
 
 +
print("")
 +
print("PGE: ", end="")
 +
for b in pge: print("%02d " % b, end="")
 +
</syntaxhighlight>
 +
 
 +
=== Exploring glitches ===
 +
This script shows an example of using the ChipWhisperer tool for performing clock glitch attacks on a target executing code. This script has similar functionality of the glitch explorer in the GUI but exposes more the insides of the ChipWhisperer tool. This script varies the offset and the width percentage of the clock glitch applied to the target during code execution. More details about clock glitching can be found in [[Tutorial_A2_Introduction_to_Glitch_Attacks_(including_Glitch_Explorer) | Tutorial A2]]
 +
 
 +
==== XMEGA Target ====
 +
<syntaxhighlight lang=python>
 +
from __future__ import print_function, division
 +
 
 +
import time
 +
import logging
 +
import os
 +
from collections import namedtuple
 +
import csv
 +
 
 +
import numpy as np
 +
 
 +
import chipwhisperer as cw
 +
from chipwhisperer.tests.tools_for_tests import FIRMWARE_DIR
 +
from chipwhisperer.capture.api.programmers import XMEGAProgrammer
 +
#from scripting_utils import GlitchResultsDisplay
 +
 
 +
logging.basicConfig(level=logging.WARN)
 +
scope = cw.scope()
 +
target = cw.target(scope)
 +
 
 +
# setup parameters needed for glitch the XMEGA
 +
scope.glitch.clk_src = 'clkgen'
 +
 
 +
scope.gain.gain = 45
 +
scope.adc.samples = 3000
 +
scope.adc.offset = 0
 +
scope.adc.basic_mode = "rising_edge"
 +
scope.clock.clkgen_freq = 7370000
 +
scope.clock.adc_src = "clkgen_x4"
 +
scope.trigger.triggers = "tio4"
 +
scope.io.tio1 = "serial_rx"
 +
scope.io.tio2 = "serial_tx"
 +
scope.io.hs2 = "glitch"
 +
 
 +
target.go_cmd = ""
 +
target.key_cmd = ""
 +
 
 +
# program the XMEGA with the built hex file
 +
programmer = XMEGAProgrammer()
 +
programmer.scope = scope
 +
programmer._logging = None
 +
programmer.find()
 +
programmer.erase()
 +
glitch_simple_firmware_dir = os.path.join(FIRMWARE_DIR, 'glitch-simple')
 +
glitch_simple_hex = os.path.join(glitch_simple_firmware_dir, r"glitchsimple-CW303.hex")
 +
programmer.program(glitch_simple_hex, memtype="flash", verify=True)
 +
programmer.close()
 +
 
 +
# format output table
 +
headers = ['target output', 'width', 'offset', 'success']
 +
#glitch_display = GlitchResultsDisplay(headers)
 +
 
 +
# set glitch parameters
 +
# trigger glitches with external trigger
 +
scope.glitch.trigger_src = 'ext_single'
 +
scope.glitch.repeat = 105
 +
 
 +
traces = []
 +
outputs = []
 +
widths = []
 +
offsets = []
 +
 
 +
# named tuples to make it easier to change the scope of the test
 +
Range = namedtuple('Range', ['min', 'max', 'step'])
 +
width_range = Range(-10, 10, 4)
 +
offset_range = Range(-10, 10, 4)
 +
 
 +
# glitch cycle
 +
scope.glitch.width = width_range.min
 +
open('glitch_out.csv', 'w').close()
 +
f = open('glitch_out.csv', 'ab')
 +
writer = csv.writer(f)
 +
target.init()
 +
while scope.glitch.width < width_range.max:
 +
    scope.glitch.offset = offset_range.min
 +
    while scope.glitch.offset < offset_range.max:
 +
        # call before trace things here
 +
 
 +
        # flush the garbage from the computer's target read buffer
 +
        target.ser.flush()
 +
 
 +
        # target enters reset mode
 +
        scope.io.pdic = 'low'
 +
 
 +
        # run aux stuff that should run before the scope arms here
 +
 
 +
        scope.arm()
 +
 
 +
        # run aux stuff that should run after the scope arms here
 +
 
 +
        # target exits reset mode
 +
        scope.io.pdic = 'high'
 +
 
 +
        timeout = 50
 +
        # wait for target to finish
 +
        while target.isDone() is False and timeout:
 +
            timeout -= 1
 +
            time.sleep(0.01)
 +
 
 +
        try:
 +
            ret = scope.capture()
 +
            if ret:
 +
                logging.warning('Timeout happened during acquisition')
 +
        except IOError as e:
 +
            logging.error('IOError: %s' % str(e))
 +
 
 +
        # get the results from the scope
 +
        trace = scope.getLastTrace()
 +
        # read from the targets buffer
 +
        output = target.ser.read(32, timeout=10)
 +
        traces.append(trace)
 +
        outputs.append(output)
 +
        widths.append(scope.glitch.width)
 +
        offsets.append(scope.glitch.width)
 +
 
 +
        # for table display purposes
 +
        success = '1234' in repr(output) # check for glitch success (depends on targets active firmware)
 +
        data = [repr(output), scope.glitch.width, scope.glitch.offset, success]
 +
        #glitch_display.add_data(data)
 +
        writer.writerow(data)
 +
 
 +
        # run aux stuff that should happen after trace here
 +
        scope.glitch.offset += offset_range.step
 +
    scope.glitch.width += width_range.step
 +
f.close()
 +
traces = np.asarray(traces)
 +
# the rest of the data is available with the outputs, widths, and offsets lists
 +
#glitch_display.display_table()
 +
print('Done')
 +
 
 +
# clean up the connection to the scope and target
 +
scope.dis()
 +
target.dis()
 +
</syntaxhighlight>
 +
 
 +
==== STM32F3 Target ====
 +
<syntaxhighlight lang=python>
 +
from __future__ import print_function, division
 +
 
 +
import time
 +
import logging
 +
import os
 +
from collections import namedtuple
 +
import csv
 +
 
 +
import numpy as np
 +
 
 +
import chipwhisperer as cw
 +
from chipwhisperer.tests.tools_for_tests import FIRMWARE_DIR
 +
from chipwhisperer.capture.api.programmers import STM32FProgrammer
 +
from scripting_utils import GlitchResultsDisplay
 +
 
 +
logging.basicConfig(level=logging.WARN)
 +
scope = cw.scope()
 +
target = cw.target(scope)
 +
 
 +
# setup parameters needed for glitch the stm32f
 +
scope.glitch.clk_src = 'clkgen'
 +
 
 +
scope.gain.gain = 45
 +
scope.adc.samples = 5000
 +
scope.adc.offset = 0
 +
scope.adc.basic_mode = "rising_edge"
 +
scope.clock.clkgen_freq = 7370000
 +
scope.clock.adc_src = "clkgen_x4"
 +
scope.trigger.triggers = "tio4"
 +
scope.io.tio1 = "serial_rx"
 +
scope.io.tio2 = "serial_tx"
 +
scope.io.hs2 = "glitch"
 +
 
 +
target.go_cmd = ""
 +
target.key_cmd = ""
 +
 
 +
# program the stm32f with the built hex file
 +
programmer = STM32FProgrammer()
 +
programmer.scope = scope
 +
programmer._logging = None
 +
programmer.open()
 +
programmer.find()
 +
programmer.erase()
 +
glitch_simple_firmware_dir = os.path.join(FIRMWARE_DIR, 'glitch-simple')
 +
glitch_simple_hex = os.path.join(glitch_simple_firmware_dir, r"glitchsimple-CW308_STM32F3.hex")
 +
programmer.program(glitch_simple_hex, memtype="flash", verify=True)
 +
programmer.close()
 +
 
 +
# format output table
 +
headers = ['target output', 'width', 'offset', 'success']
 +
glitch_display = GlitchResultsDisplay(headers)
 +
 
 +
# set glitch parameters
 +
# trigger glitches with external trigger
 +
scope.glitch.trigger_src = 'ext_single'
 +
scope.glitch.repeat = 105
 +
 
 +
traces = []
 +
outputs = []
 +
widths = []
 +
offsets = []
 +
 
 +
# named tuples to make it easier to change the scope of the test
 +
Range = namedtuple('Range', ['min', 'max', 'step'])
 +
width_range = Range(-40, 40, 0.39*5)
 +
offset_range = Range(-40, 40, 0.39*5)
 +
 
 +
# glitch cycle
 +
scope.glitch.width = width_range.min
 +
open('glitch_out.csv', 'w').close()
 +
f = open('glitch_out.csv', 'ab')
 +
writer = csv.writer(f)
 +
target.init()
 +
while scope.glitch.width < width_range.max:
 +
    scope.glitch.offset = offset_range.min
 +
    while scope.glitch.offset < offset_range.max:
 +
        # call before trace things here
 +
 
 +
        # flush the garbage from the computer's target read buffer
 +
        target.ser.flush()
 +
 
 +
        # run aux stuff that should run before the scope arms here
 +
 
 +
        # target enters reset state
 +
        scope.io.nrst = 'low'
 +
 
 +
        scope.arm()
 +
 
 +
        # run aux stuff that should run after the scope arms here
 +
 
 +
        # target exits reset state and starts execution
 +
        scope.io.nrst = 'high'
 +
 
 +
        timeout = 50
 +
        # wait for target to finish
 +
        while target.isDone() is False and timeout:
 +
            timeout -= 1
 +
            time.sleep(0.01)
 +
 
 +
        try:
 +
            ret = scope.capture()
 +
            if ret:
 +
                logging.warning('Timeout happened during acquisition')
 +
        except IOError as e:
 +
            logging.error('IOError: %s' % str(e))
 +
 
 +
        # get the results from the scope
 +
        trace = scope.getLastTrace()
 +
        # read from the targets buffer
 +
        output = target.ser.read(32, timeout=100)
 +
        traces.append(trace)
 +
        outputs.append(output)
 +
        widths.append(scope.glitch.width)
 +
        offsets.append(scope.glitch.width)
 +
 
 +
        # for table display purposes
 +
        success = '1234' in repr(output) # check for glitch success (depends on targets active firmware)
 +
        data = [repr(output), scope.glitch.width, scope.glitch.offset, success]
 +
        glitch_display.add_data(data)
 +
        writer.writerow(data)
 +
 
 +
        # run aux stuff that should happen after trace here
 +
        scope.glitch.offset += offset_range.step
 +
    scope.glitch.width += width_range.step
 +
f.close()
 +
traces = np.asarray(traces)
 +
# the rest of the data is available with the outputs, widths, and offsets lists
 +
glitch_display.display_table()
 +
print('Done')
 +
</syntaxhighlight>

Latest revision as of 11:13, 10 April 2018

For the older V3.x tools, see V3:Making_Scripts

Scripting with ChipWhisperer as a python module

When used without the GUI, the 4.0 API removes much of the high level abstractions so you can have more control over the capture process. It also answers questions like--when I capture a trace in what order are things happening? The following example scripts will give you a starting point when scripting with the ChipWhisperer tool. All these examples on this page can be found in their home chipwhisperer/software/scripting-examples.

Perform Some Traces during AES encryption and get the results as Numpy array

This script is an example of using the chipwhisperer module for capturing traces during AES encryption. Make sure to have the correct firmware loaded on the target. These traces are then saved and loaded later for analysis.

XMEGA Target

from __future__ import division, print_function

import time
import os

import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime
from tqdm import tqdm

import chipwhisperer as cw
from chipwhisperer.capture.acq_patterns.basic import AcqKeyTextPattern_Basic
from chipwhisperer.tests.tools_for_tests import FIRMWARE_DIR
from chipwhisperer.capture.api.programmers import XMEGAProgrammer

scope = cw.scope()
target = cw.target(scope)

# setup scope parameters
scope.gain.gain = 45
scope.adc.samples = 3000
scope.adc.offset = 1250
scope.adc.basic_mode = "rising_edge"
scope.clock.clkgen_freq = 7370000
scope.clock.adc_src = "clkgen_x4"
scope.trigger.triggers = "tio4"
scope.io.tio1 = "serial_rx"
scope.io.tio2 = "serial_tx"
scope.io.hs2 = "clkgen"

# program the target
programmer = XMEGAProgrammer()
programmer.scope = scope
programmer._logging = None
programmer.find()
programmer.erase()
aes_firmware_dir = os.path.join(FIRMWARE_DIR, 'simpleserial-aes')
aes_hex = os.path.join(aes_firmware_dir, r"simpleserial-aes-CW303.hex")
programmer.program(aes_hex, memtype="flash", verify=True)
programmer.close()

ktp = AcqKeyTextPattern_Basic(target=target)

traces = []
textin = []
keys = []
N = 50  # Number of traces
target.init()
for i in tqdm(range(N), desc='Capturing traces'):
    # run aux stuff that should come before trace here

    key, text = ktp.newPair()  # manual creation of a key, text pair can be substituted here
    textin.append(text)
    keys.append(key)

    target.reinit()

    target.setModeEncrypt()  # only does something for targets that support it
    target.loadEncryptionKey(key)
    target.loadInput(text)

    # run aux stuff that should run before the scope arms here

    scope.arm()

    # run aux stuff that should run after the scope arms here

    target.go()
    timeout = 50
    # wait for target to finish
    while target.isDone() is False and timeout:
        timeout -= 1
        time.sleep(0.01)

    try:
        ret = scope.capture()
        if ret:
            print('Timeout happened during acquisition')
    except IOError as e:
        print('IOError: %s' % str(e))

    # run aux stuff that should happen after trace here
    _ = target.readOutput()  # clears the response from the serial port
    traces.append(scope.getLastTrace())
trace_array = np.asarray(traces)  # if you prefer to work with numpy array for number crunching
textin_array = np.asarray(textin)
known_keys = np.asarray(keys)  # for fixed key, these keys are all the same

now = datetime.now()
fmt_string = '{:02}{:02}_{}.npy'
trace_file_path = fmt_string.format(now.hour, now.minute, "traces")
textin_file_path = fmt_string.format(now.hour, now.minute, "textins")
keys_file_path = fmt_string.format(now.hour, now.minute, "keys")

print('Saving results to {},{} and {}...'.format(trace_file_path, textin_file_path, keys_file_path), end='')
# save to a files for later processing
np.save(trace_file_path, trace_array)
np.save(textin_file_path, textin_array)
np.save(keys_file_path, known_keys)
print('Done')

# uncomment plt.show() to show an example trace
plt.plot(traces[0])
#plt.show()

# cleanup the connection to the target and scope
scope.dis()
target.dis()

STM32F3 Target

from __future__ import division, print_function

import time
import os

import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime
from tqdm import tqdm

import chipwhisperer as cw
from chipwhisperer.capture.acq_patterns.basic import AcqKeyTextPattern_Basic
from chipwhisperer.tests.tools_for_tests import FIRMWARE_DIR
from chipwhisperer.capture.api.programmers import STM32FProgrammer

scope = cw.scope()
target = cw.target(scope)

# setup scope parameters
scope.gain.gain = 45
scope.adc.samples = 5000
scope.adc.offset = 0
scope.adc.basic_mode = "rising_edge"
scope.clock.clkgen_freq = 7370000
scope.clock.adc_src = "clkgen_x4"
scope.trigger.triggers = "tio4"
scope.io.tio1 = "serial_rx"
scope.io.tio2 = "serial_tx"
scope.io.hs2 = "clkgen"

# program the target
programmer = STM32FProgrammer()
programmer.scope = scope
programmer._logging = None
programmer.open()
programmer.find()
programmer.erase()
aes_firmware_dir = os.path.join(FIRMWARE_DIR, 'simpleserial-aes')
aes_hex = os.path.join(aes_firmware_dir, r"simpleserial-aes-CW308_STM32F3.hex")
programmer.program(aes_hex, memtype="flash", verify=True)
programmer.close()

ktp = AcqKeyTextPattern_Basic(target=target)

traces = []
textin = []
keys = []
N = 50  # Number of traces
target.init()
for i in tqdm(range(N), desc='Capturing traces'):
    # run aux stuff that should come before trace here

    key, text = ktp.newPair()  # manual creation of a key, text pair can be substituted here
    textin.append(text)
    keys.append(key)

    target.reinit()

    target.setModeEncrypt()  # only does something for targets that support it
    target.loadEncryptionKey(key)
    target.loadInput(text)

    # run aux stuff that should run before the scope arms here

    scope.arm()

    # run aux stuff that should run after the scope arms here

    target.go()
    timeout = 50
    # wait for target to finish
    while target.isDone() is False and timeout:
        timeout -= 1
        time.sleep(0.01)

    try:
        ret = scope.capture()
        if ret:
            print('Timeout happened during acquisition')
    except IOError as e:
        print('IOError: %s' % str(e))

    # run aux stuff that should happen after trace here

    _ = target.readOutput() # throw out the target response
    traces.append(scope.getLastTrace())
trace_array = np.asarray(traces)  # if you prefer to work with numpy array for number crunching
textin_array = np.asarray(textin)
known_keys = np.asarray(keys)  # for fixed key, these keys are all the same

now = datetime.now()
fmt_string = '{:02}{:02}_{}.npy'
trace_file_path = fmt_string.format(now.hour, now.minute, "traces")
textin_file_path = fmt_string.format(now.hour, now.minute, "textins")
keys_file_path = fmt_string.format(now.hour, now.minute, "keys")

print('Saving results to {},{} and {}...'.format(trace_file_path, textin_file_path, keys_file_path), end='')
# save to a files for later processing
np.save(trace_file_path, trace_array)
np.save(textin_file_path, textin_array)
np.save(keys_file_path, known_keys)
print('Done')

# show an example trace
plt.plot(traces[0])
plt.show()

# cleanup the connection to the target and scope
scope.dis()
target.dis()

Manually breaking AES encryption with your recorded traces (As much as scripting is manual)

Using the saved traces of the AES encryption you can now break the sub-keys of the encryption key. This script is covered in more detail in Tutorial B6.

from __future__ import division, print_function

import numpy as np
from tqdm import tqdm

HW = [bin(n).count("1") for n in range(0, 256)]

sbox = (
    0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
    0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
    0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
    0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
    0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
    0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
    0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
    0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
    0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
    0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
    0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
    0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
    0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
    0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
    0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
    0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16)


def intermediate(pt, keyguess):
    return sbox[pt ^ keyguess]

# put the actual file names in here
keys = np.load('1147_keys.npy')
textins = np.load('1147_textins.npy')
traces = np.load('1147_traces.npy')

knownkey = keys[0] # for fixed key they are all the same
pt = textins
numtraces = np.shape(traces)[0]
numpoint = np.shape(traces)[1]

bestguess = [0] * 16
pge = [256] * 16
for bnum in tqdm(range(0, 16), desc='Attacking subkeys'):
    cpaoutput = [0] * 256
    maxcpa = [0] * 256
    for kguess in range(0, 256):

        # Initialize arrays &amp; variables to zero
        sumnum = np.zeros(numpoint)
        sumden1 = np.zeros(numpoint)
        sumden2 = np.zeros(numpoint)

        hyp = np.zeros(numtraces)
        for tnum in range(0, numtraces):
            hyp[tnum] = HW[intermediate(pt[tnum][bnum], kguess)]

        # Mean of hypothesis
        meanh = np.mean(hyp, dtype=np.float64)

        # Mean of all points in trace
        meant = np.mean(traces, axis=0, dtype=np.float64)

        # For each trace, do the following
        for tnum in range(0, numtraces):
            hdiff = (hyp[tnum] - meanh)
            tdiff = traces[tnum, :] - meant

            sumnum = sumnum + (hdiff * tdiff)
            sumden1 = sumden1 + hdiff * hdiff
            sumden2 = sumden2 + tdiff * tdiff

        cpaoutput[kguess] = sumnum / np.sqrt(sumden1 * sumden2)
        maxcpa[kguess] = max(abs(cpaoutput[kguess]))

    bestguess[bnum] = np.argmax(maxcpa)

    cparefs = np.argsort(maxcpa)[::-1]

    # Find PGE
    pge[bnum] = list(cparefs).index(knownkey[bnum])

print("Best Key Guess: ", end="")
for b in bestguess: print("%02x " % b, end="")

print("")
print("PGE: ", end="")
for b in pge: print("%02d " % b, end="")

Exploring glitches

This script shows an example of using the ChipWhisperer tool for performing clock glitch attacks on a target executing code. This script has similar functionality of the glitch explorer in the GUI but exposes more the insides of the ChipWhisperer tool. This script varies the offset and the width percentage of the clock glitch applied to the target during code execution. More details about clock glitching can be found in Tutorial A2

XMEGA Target

from __future__ import print_function, division

import time
import logging
import os
from collections import namedtuple
import csv

import numpy as np

import chipwhisperer as cw
from chipwhisperer.tests.tools_for_tests import FIRMWARE_DIR
from chipwhisperer.capture.api.programmers import XMEGAProgrammer
#from scripting_utils import GlitchResultsDisplay

logging.basicConfig(level=logging.WARN)
scope = cw.scope()
target = cw.target(scope)

# setup parameters needed for glitch the XMEGA
scope.glitch.clk_src = 'clkgen'

scope.gain.gain = 45
scope.adc.samples = 3000
scope.adc.offset = 0
scope.adc.basic_mode = "rising_edge"
scope.clock.clkgen_freq = 7370000
scope.clock.adc_src = "clkgen_x4"
scope.trigger.triggers = "tio4"
scope.io.tio1 = "serial_rx"
scope.io.tio2 = "serial_tx"
scope.io.hs2 = "glitch"

target.go_cmd = ""
target.key_cmd = ""

# program the XMEGA with the built hex file
programmer = XMEGAProgrammer()
programmer.scope = scope
programmer._logging = None
programmer.find()
programmer.erase()
glitch_simple_firmware_dir = os.path.join(FIRMWARE_DIR, 'glitch-simple')
glitch_simple_hex = os.path.join(glitch_simple_firmware_dir, r"glitchsimple-CW303.hex")
programmer.program(glitch_simple_hex, memtype="flash", verify=True)
programmer.close()

# format output table
headers = ['target output', 'width', 'offset', 'success']
#glitch_display = GlitchResultsDisplay(headers)

# set glitch parameters
# trigger glitches with external trigger
scope.glitch.trigger_src = 'ext_single'
scope.glitch.repeat = 105

traces = []
outputs = []
widths = []
offsets = []

# named tuples to make it easier to change the scope of the test
Range = namedtuple('Range', ['min', 'max', 'step'])
width_range = Range(-10, 10, 4)
offset_range = Range(-10, 10, 4)

# glitch cycle
scope.glitch.width = width_range.min
open('glitch_out.csv', 'w').close()
f = open('glitch_out.csv', 'ab')
writer = csv.writer(f)
target.init()
while scope.glitch.width < width_range.max:
    scope.glitch.offset = offset_range.min
    while scope.glitch.offset < offset_range.max:
        # call before trace things here

        # flush the garbage from the computer's target read buffer
        target.ser.flush()

        # target enters reset mode
        scope.io.pdic = 'low'

        # run aux stuff that should run before the scope arms here

        scope.arm()

        # run aux stuff that should run after the scope arms here

        # target exits reset mode
        scope.io.pdic = 'high'

        timeout = 50
        # wait for target to finish
        while target.isDone() is False and timeout:
            timeout -= 1
            time.sleep(0.01)

        try:
            ret = scope.capture()
            if ret:
                logging.warning('Timeout happened during acquisition')
        except IOError as e:
            logging.error('IOError: %s' % str(e))

        # get the results from the scope
        trace = scope.getLastTrace()
        # read from the targets buffer
        output = target.ser.read(32, timeout=10)
        traces.append(trace)
        outputs.append(output)
        widths.append(scope.glitch.width)
        offsets.append(scope.glitch.width)

        # for table display purposes
        success = '1234' in repr(output) # check for glitch success (depends on targets active firmware)
        data = [repr(output), scope.glitch.width, scope.glitch.offset, success]
        #glitch_display.add_data(data)
        writer.writerow(data)

        # run aux stuff that should happen after trace here
        scope.glitch.offset += offset_range.step
    scope.glitch.width += width_range.step
f.close()
traces = np.asarray(traces)
# the rest of the data is available with the outputs, widths, and offsets lists
#glitch_display.display_table()
print('Done')

# clean up the connection to the scope and target
scope.dis()
target.dis()

STM32F3 Target

from __future__ import print_function, division

import time
import logging
import os
from collections import namedtuple
import csv

import numpy as np

import chipwhisperer as cw
from chipwhisperer.tests.tools_for_tests import FIRMWARE_DIR
from chipwhisperer.capture.api.programmers import STM32FProgrammer
from scripting_utils import GlitchResultsDisplay

logging.basicConfig(level=logging.WARN)
scope = cw.scope()
target = cw.target(scope)

# setup parameters needed for glitch the stm32f
scope.glitch.clk_src = 'clkgen'

scope.gain.gain = 45
scope.adc.samples = 5000
scope.adc.offset = 0
scope.adc.basic_mode = "rising_edge"
scope.clock.clkgen_freq = 7370000
scope.clock.adc_src = "clkgen_x4"
scope.trigger.triggers = "tio4"
scope.io.tio1 = "serial_rx"
scope.io.tio2 = "serial_tx"
scope.io.hs2 = "glitch"

target.go_cmd = ""
target.key_cmd = ""

# program the stm32f with the built hex file
programmer = STM32FProgrammer()
programmer.scope = scope
programmer._logging = None
programmer.open()
programmer.find()
programmer.erase()
glitch_simple_firmware_dir = os.path.join(FIRMWARE_DIR, 'glitch-simple')
glitch_simple_hex = os.path.join(glitch_simple_firmware_dir, r"glitchsimple-CW308_STM32F3.hex")
programmer.program(glitch_simple_hex, memtype="flash", verify=True)
programmer.close()

# format output table
headers = ['target output', 'width', 'offset', 'success']
glitch_display = GlitchResultsDisplay(headers)

# set glitch parameters
# trigger glitches with external trigger
scope.glitch.trigger_src = 'ext_single'
scope.glitch.repeat = 105

traces = []
outputs = []
widths = []
offsets = []

# named tuples to make it easier to change the scope of the test
Range = namedtuple('Range', ['min', 'max', 'step'])
width_range = Range(-40, 40, 0.39*5)
offset_range = Range(-40, 40, 0.39*5)

# glitch cycle
scope.glitch.width = width_range.min
open('glitch_out.csv', 'w').close()
f = open('glitch_out.csv', 'ab')
writer = csv.writer(f)
target.init()
while scope.glitch.width < width_range.max:
    scope.glitch.offset = offset_range.min
    while scope.glitch.offset < offset_range.max:
        # call before trace things here

        # flush the garbage from the computer's target read buffer
        target.ser.flush()

        # run aux stuff that should run before the scope arms here

        # target enters reset state
        scope.io.nrst = 'low'

        scope.arm()

        # run aux stuff that should run after the scope arms here

        # target exits reset state and starts execution
        scope.io.nrst = 'high'

        timeout = 50
        # wait for target to finish
        while target.isDone() is False and timeout:
            timeout -= 1
            time.sleep(0.01)

        try:
            ret = scope.capture()
            if ret:
                logging.warning('Timeout happened during acquisition')
        except IOError as e:
            logging.error('IOError: %s' % str(e))

        # get the results from the scope
        trace = scope.getLastTrace()
        # read from the targets buffer
        output = target.ser.read(32, timeout=100)
        traces.append(trace)
        outputs.append(output)
        widths.append(scope.glitch.width)
        offsets.append(scope.glitch.width)

        # for table display purposes
        success = '1234' in repr(output) # check for glitch success (depends on targets active firmware)
        data = [repr(output), scope.glitch.width, scope.glitch.offset, success]
        glitch_display.add_data(data)
        writer.writerow(data)

        # run aux stuff that should happen after trace here
        scope.glitch.offset += offset_range.step
    scope.glitch.width += width_range.step
f.close()
traces = np.asarray(traces)
# the rest of the data is available with the outputs, widths, and offsets lists
glitch_display.display_table()
print('Done')