Worksheet for "Introduction to Hardware (In)Security with the ChipWhisperer-Nano"

Nathan Jones, Mindstorms Engineering, LLC

This worksheet provides three different tracks depending on what hardware and software you have:

Introduction to ChipWhisperer

Exercise 0: Get the necessary software

Workshop track: ⚡️ / 💻

If you don't want to mess around with any hardware or software, then you can skip this exercise! Don't worry, you can still follow along with any exercises marked with a 📝.

Using CW-Nano

Follow the instructions here to install the ChipWhisperer software for your operating system or for a virtual machine. This ensures that you have the necessary files and software to program the STM32 target on the CW-Nano.

It also installs the ChipWhisperer Python library and gives you access to a number of high quality Jupyter notebooks to continue your hardware hacking learning journey long after this workshop ends.

You'll now be able to follow along with any exercises marked with a ⚡️.

Using simulated data

If you don't have the CW-Nano or have any trouble connecting to the CW-Nano you do have, then simply clone the chipwhisperer Git repository. Then, create a Python virtual environment and use requirements.txt to install the libraries we'll be using today (see below). You'll be able to follow along with any exercises marked with a 💻 or 📝.

You'll now be able to follow along with any exercises marked with a 💻.

I've installed the necessary software (or am opting to complete just the pen and paper exercises)

Exercise 1: Program the CW-Nano

Workshop track: ⚡️

  1. Open firmware/mcu/simpleserial-base/simpleserial-base.c in your code editor of choice.

  2. In get_pt(), in between trigger_high() and trigger_low(), place the following code. Then save the file.

  3. Navigate to the folder eoc_files and start a Python session.

  4. Connect to the CW-Nano using the chipwhisperer Python library.

  5. Compile the simpleserial-base program either from another terminal window

    or using the helper file after setting the value of FIRMWARE_ROOT in intro_to_hw_security.py.

  6. Flash the program also using the chipwhisperer Python library

    or using the helper file.

I programmed the on-board STM32 and saw Verified flash OK in the Python REPL.

Exercise 2: Get a power trace

Workshop track: ⚡️ / 💻

Using CW-Nano

  1. Use get_traces from the helper file.

  2. ⚠️NOTE: If you see (ChipWhisperer Target WARNING|File SimpleSerial.py:454) Unexpected start to command: then this just means that the target was erroneously in the middle of processing a message when we started capturing our trace, which we can fix by resetting the target and then recapturing the trace.

  3. Inspect the result.

You can also do this manually.

  1. Create a bytearray to send to the target.

    or

  2. Then run capture_trace, which arms the scope, sends our message, and stores the power trace (when triggered) along with the reply from the target.

Using pre-captured data

If you don't have the CW-Nano, import the pre-captured data.

  1. Start by navigating to the folder eoc_files and starting a Python session.

  2. Then import the libraries and the data.

I've procured a power trace.

Exercise 3: Plot a power trace

Workshop track: ⚡️ / 💻

Create a simple plot of the power trace using plot(trace, file) from the helper file. E.g.

The helper file also provides the function plot_with_hover(trace, file), which adds a hovering textbox with x and y data for the nearest data point underneath the current mouse position.

These functions use holoviews to plot our traces, which you could use directly if you wanted (example below).

You can also use something like matplotlib to plot the power traces (example below), though holoviews handles large data sets better and has more natural controls for panning and zooming.

I've created a plot of my power trace.

Simple Power Analysis (SPA)

Exercise 4: Differentiate MCU operations

Workshop track: ⚡️ / 💻 / 📝

  1. Open up the plot you made in the last exercise (mul2_loop.html) and the 5 files named ex4_trace#.html (e.g. ex4_trace0.html, ex4_trace1.html, etc)

  2. Match the power traces to the five code snippets below.

    1. Empty

    2. Multiplication by 2

    3. Division by 3

    4. Table look-up (16 times)

    5. Multiple or divide (16 times)

  3. Remember!

    1. The scope starts collecting data immediately before the start of each math operation, but it will collect data well past when it finishes, continuing on with the rest of the application that's waiting for more incoming serial data. Look for the beginning of a similar, repeated feature at the end of every power trace which identifies the serial loop and, therefore, where each operation ends.

    2. For each power trace, try to identify repeating features within the region where you think the operation is occurring. How many different features do you see? How many of each feature are there? These features correspond to specific operations happening inside the MCU. Each code snippet also has a unique set of operations. Which snippet of code seems to best match a trace with a given number of features and feature types?

    3. A few notes about the microarchitecture of the STM32F0:

      1. The STM32F042 on the CW-Nano does have a hardware multiplier (that completes in a single clock-cycle), but it does not have a hardware divider.

      2. Multiplications/divisions by 2 are probably implemented with simple left-/right-shifts.

      3. Conditional branches are very power-hungry (requiring a pipeline flush if the branch was mis-predicted).

      4. Check Compiler Explorer to see what assembly instructions are actually being emitted for each piece of code!

Record your answers below.

  • Empty corresponds to:
  • Multiplication by 2 corresponds to:
  • Division by 3 corresponds to:
  • Table look-up corresponds to:
  • Multiply or divide corresponds to:
Have extra time? Take a stab at answering these questions to deepen your understanding!
  1. The hint in step 3 mentions that multiplications by 2 are probably implemented as left-shifts. Using Compiler Explorer, look up the ARM Cortex-M0 assembly emitted for A *= 2 vs A *= 3. Based on what you see, predict whether those two traces would look identical or different on the CW-Nano — and why. Then modify the firmware to use A *= 3 and check your prediction. I think the traces will look identical different What was the result?
  2. True or False: "On a microcontroller with a hardware multiplier, you cannot distinguish between A *= 2 and A *= 3 using SPA." Justify your answer. True False
    AnswerFalse in general — the compiler likely emits LSL for ×2 and MUL for ×3, which are different instructions with different power profiles, even if both complete in one cycle.
  3. You want to hide a secret value used in a branch condition to prevent SPA. Which of the following is the most effective countermeasure? (a) Use a faster clock speed (b) Add random delays between operations (c) Replace the branch with a branchless, constant-time implementation (d) Use a longer key
    Answer(c); This results in a power trace that looked the same regardless of the secret value. None of the other answers change the fact that the two branches based on the secret value will have distinctly different features in the power trace.

Differential Power Analysis (DPA)

Exercise 5: Identify the effect of a single bit in an MCU's power trace

Workshop track: ⚡️ / 💻 / 📝

  1. Get 2500 traces of the target performing the "table look-up" code from Exercise 4

    1. For the CW-Nano (⚡️):

      1. Program the target with the "table look-up" code.

        1. Replace the "mul2_loop" code from Exercise 1 in with the table look-up code from Exercise 4. The complete definitions for the variables sbox and key are found in step 2 below (underneath the table of data).

        2. Build and flash the program.

      2. Collect 2500 traces using the helper file

    2. Software only (💻): Load the sample data

    3. Pen and paper (📝): Use the data below, which is a subset taken directly from the sampled data above. You can also find this data in ex5.csv.

      Trace #In[0]x = 22x = 23x = 24x = 25x = 26
      830x20-0.003906-0.2031250.261719-0.0156250.183594
      1000xA50.039062-0.1953120.277344-0.0195310.183594
      14720x13-0.003906-0.2109380.277344-0.0156250.1875
      15150x710.039062-0.1406250.4335940.0039060.246094
      16110x670.054688-0.1328120.4257810.0039060.214844
      17100x710.035156-0.1406250.421875-0.0039060.214844
  2. Create a variable in your Python REPL that matches the table and key from the code (below).

  3. Create two lists of traces, one with all of the traces where the LSB of the first output byte was 0 (sbox[ textin[0] ^ key[0] ] & 1 == 0) and the other where the LSB of the first output byte was 1 (sbox[ textin[0] ^ key[0] ] & 1 == 1).

    Alternatively, you can use the textout part of each trace.

  4. Take the per-sample average of each group (i.e. avg_ones[0] is the average of sample 0 of every trace in the ones group), subtract one average from the other, and take the per-sample absolute value.

  5. You can also do steps 3 and 4 in a single line.

  6. Plot diff.

  7. Zoom in on the portion of the graph where the MCU is performing the table look-up (Review the techniques from Exercise 4 if you aren't sure how to locate that section of the graph!).

    1. What's the magnitude of the largest peak?

    2. At what sample number is it?

    3. How much larger than the next largest peak is it?

I've plotted diff.

  • There is a peak in the graph at sample # of magnitude . It's times larger than the next largest peak during the table look-up operation.

  • In your own words, explain the significance of the peak you are seeing.

Have extra time? Take a stab at answering these questions to deepen your understanding!
  1. Instead of testing the first byte of the output (sbox[textin[0]^key[0]]&1==1) test another byte, such as the second (sbox[textin[1]^key[1]]&1==1) or third (sbox[textin[2]^key[2]]&1==1). Does the peak change in size or position at all? Can you infer or confirm anything from this about how long each loop iteration takes or whether any of the bytes are better targets for DoM than any others?
  2. Describe two scenarios that could produce a false negative — no visible peak at the expected sample location, even though you used the correct key byte and the correlation is real.

    Possible answers • too few traces • ADC does not have high enough sample rate and bandwidth, or is not clocked to the MCU • using a narrow sample window that misses the actual lookup • jitter in triggering that misaligns traces before averaging • the wrong leakage model for this target
  3. Describe two scenarios that could produce a false positive — a peak in the DoM graph at the sample where the table look-up happens, even though you used the wrong key byte. Under what conditions is a false positive most dangerous (i.e. most likely to fool you)?

    Possible answers • small number of traces so noise hasn't been averaged away • a wrong key guess that happens to correlate weakly with the correct one due to the structure of the S-box • a wrong key guess that happens to consistently trigger a different branch to be taken or hardware circuit to be activated • the input data represented only a subset of all possible inputs, such that the outputs seem to be correlated to a wrong key guess

Exercise 6: Use Hamming weight (HW) to sort power traces

Workshop track: ⚡️ / 💻 / 📝

  1. Using the traces from the last exercise, sort them based on whether the Hamming weight (the counts of 1s) of the first output byte was >4 or <=4.

  2. Take their per-sample average and find their difference, then plot it.

  3. Considering the resulting graph:

    1. What's the largest peak in the graph?

    2. What's the magnitude of that peak?

    3. How much larger than the next largest peak is it?

    4. How do these values compare to those from the last exercise?

I've plotted diff.

  • There is a peak in the graph at sample # of magnitude . It's times larger than the next largest peak during the table look-up operation. It's times larger than the peak from the last exercise.

  • In your own words, explain the difference between sorting based on LSB and by Hamming weight.

Have extra time? Take a stab at answering these questions to deepen your understanding!
  1. Instead of splitting traces into HW > 4 vs HW ≤ 4 (which is approximately a median split), try HW ≥ 7 vs HW ≤ 1 (the extremes of the distribution). Compare the peak magnitude to what you got in Exercise 6. Why might taking only the most extreme traces produce a stronger signal, even though you're using fewer of them? What's the tradeoff?
  2. Go back to sorting the traces based on a single bit position, but one other than bit 0 — try bit 3, then bit 7. Is the peak magnitude the same, larger, or smaller than what you got in Exercise 5? What does this tell you about whether the HW model is capturing something real, or whether one bit position is special?

    Possible explanationIf the peaks are similar in magnitude, it suggests the HW model's advantage in Ex 6 is from aggregating information from all 8 bits, not from any one bit being special.

Exercise 7: Compare two key guesses

Workshop track: ⚡️ / 💻 / 📝

  1. Assume you had an incorrect value for key[0] (pick one at random) and then sort the traces based on the LSB or HW of the output of the table look-up using that value.

  2. As in Exercise 5, take the per-sample average of these two groups, subtract one from the other, and plot the result.

  3. Compare the peak you found in Exercise 6 with the largest peak you can find in this graph.

I've plotted diff based on an incorrect key.

  • The largest peak in this graph has a magnitude of and is times larger than the next largest peak. The magnitude is times smaller than the peak from Exercise 6 and its ratio with the next largest peak is also times smaller than in Exercise 6.

Have extra time? Take a stab at answering these questions to deepen your understanding!
  1. Repeat the incorrect-key DoM with 6 different randomly chosen wrong values of key[0]. Record the peak magnitude for each. How much do the peaks vary between wrong guesses? What does this suggest about how confident you can be that any single runner-up is actually wrong?

    Possible explanationThe peaks vary in size since some key guesses result in sbox outputs that somewhat correlate to the output. It's hard to know from looking at a single peak value if it's the true maximum value for that set of traces or one of the smaller ones that partly correlates to the output.

Exercise 8: Find a mystery byte

Workshop track: ⚡️ / 💻 / 📝

  1. Get 2500 traces of the target performing AES encryption.

    1. For the CW-Nano (⚡️):

      1. Program the target with the "simpleserial-aes" firmware.

      2. Collect 2500 traces using the helper file

    2. Software only (💻): Load the sample data

    3. Pen and paper (📝): Use the data below, which is a subset taken directly from the sampled data above, and read "Hint for pen and paper solvers". You can also find this data in ex8.csv.

      Trace #In[0]x = 484x = 485x = 486x = 487x = 488x = 489x = 490
      270xDF-0.089843750.0273440.2421880.0585940.0117190.1679690.05078125
      8980x57-0.13671875-0.031250.179688-0.011719-0.0585940.1210940.04296875
      10480x1B-0.15625-0.0390620.167969-0.015625-0.0742190.1171880.03515625
      10720x59-0.13671875-0.0351560.183594-0.011719-0.0546880.1093750.02734375
      10890x56-0.10156250.0351560.2695310.1015620.0429690.1796880.05859375
      15640x4A-0.121093750.0117190.2304690.0703120.0195310.1523440.05078125
      17910x79-0.14453125-0.0234380.175781-0.019531-0.0664060.1328120.046875
      23980x27-0.13671875-0.031250.2148440.0664060.0195310.1523440.05078125
      Hint for pen and paper solversThe value of key[0] is one of the following: 0x00, 0x2B, 0x2C, or 0xCA. Instead of testing every possible value of key[0], as is described below, test only the values just given.
  2. Conduct DoM for each of the 256 possible values of key[0]. In pseudocode:

  3. Which key guess was associated with the largest peak?

The key guess with the highest peak was for key value .

Have extra time? Take a stab at answering these questions to deepen your understanding!
  1. Look at your saved peak values for all 256 key guesses and plot them as a bar chart (key guess on the x-axis, peak value on y-axis). What does the distribution of wrong-guess peaks look like? Is the correct guess clearly separated, or does it sit close to a cluster of other high-scoring guesses? What would this look like if you repeated the exercise with only 500 traces?
  2. The DoM attack examines only key[0] and only the first byte of the S-box output. If AES used a simple identity function (no S-box; e.g. out[i] = in[i] ^ key[i]) instead of the actual S-box substitution, would the DoM attack still work? Would it be stronger, weaker, or about the same?
    AnswerWith the XOR-only identity function, bit j of out[i] depends only on bit j of in[i] and bit j of key[i], with no cross-bit mixing. This means the DoM signal from sorting by bit j contains information about only one bit of the key at a time. You'd recover the key one bit per sort rather than one byte per sort. The S-box's nonlinearity is what lets a single S-box output byte expose the entire byte of key: changing any one bit of the key input changes roughly half the output bits, so the correlation between HW(sbox[in ⊕ key]) and power is much stronger than the correlation between HW(in ⊕ key) and power.

Exercise 9: Use DPA to recover an AES key

Workshop track: ⚡️ / 💻

  1. Repeat Exercise 8 for all 16 bytes of the AES key.

The AES key is .

Closing thoughts

What are three things you want to remember about today's workshop?