Unsuccessful voltage glitching with Husky/A35

Intro

I have been attempting to crowbar glitch AES encryption on the Husky CW312-A35 target using chipwhisperer-jupyter/demos/PA_HW_CW305_1-Attacking_AES_on_an_FPGA.ipynb at master · newaetech/chipwhisperer-jupyter · GitHub as the base notebook. I have had 0 successful glitch attempts and would like some help/guidance on this after thoroughly exploring the forum and related Jupyter notebooks.

Notable Behavior/Characteristics

  • After hooking up my hardware to an oscilloscope, it seems that the Husky may not be inserting the glitch, as the Vcc line is not dropping at all but rather is staying at a constant 3.3V. However, Husky power traces suggest that the glitch IS being inserted, as changes in the glitch settings have an obvious effect on the outputted trace.
  • Per Running SOLN_LAB_3_3 with a Husky doesn’t yield correct results - Embedded Security / ChipWhisperer Hardware - NewAE Forum, I have confirmed that I have a Husky V3 that is missing a capacitor on C49. Could this be affecting my AES encryption glitch attempts, even though I am not actually running an AES attack itself?
  • In the glitch loop, I am pretty much getting 100% normal occurrences, meaning I am getting 0 successful glitches and 0 device resets. I have swept through width = 0 to width = scope.glitch.phase_shift_steps // 2 and the same for offset (where scope.glitch.phase_shift_steps = 4592). I have also swept through ext_offset = 1 to ext_offset = 31 and have played around with a few different values for scope.glitch.repeat.

Setup Code

Below is some set up code that I am using that I have amalgamated from various forum posts and CW Jupyter notebooks.

# Set up ADC/trigger
scope.adc.samples = 500 #80 # Number of samples to record in a single capture. Max for Husky is 131070
scope.adc.offset = 3 # The number of samples to wait before recording data after seeing a trigger event. Must be a 32 bit unsigned integer; that is, in the range [0, 2**32)
scope.adc.basic_mode = "rising_edge" # Type of event to use as a trigger (only applies to the ADC capture - the glitch module is always a rising edge trigger). 'rising_edge' == triggers when line transitions from low to high
scope.trigger.triggers = "tio4" # The logical input into the trigger module. 'tio4' == target I/O pin 4
scope.io.tio1 = "serial_rx" # The function of the target I/O 1 pin. 'serial_rx' == UART input
scope.io.tio2 = "serial_tx" # The function of the target I/O 2 pin. 'serial_tx' == UART output
scope.trigger.module = 'basic'

# Set up target clock
scope.clock.clkgen_freq = 7.37e6
scope.io.hs2 = 'clkgen'
scope.clock.clkgen_src = 'system' # The input for the Husky’s PLL, which generates clocks for the target and the ADC. 'system' == an onboard crystal
scope.clock.adc_mul = 4 # Sets a new ADC clock frequency by multiplying this value by clkgen_freq
scope.clock.reset_dcms() # Reset the CLKGEN DCM, then the ADC DCM. NOTE: This is documented as ONLY a CW Lite/Pro feature, but it still works with the Husky?

# Set up glitch
scope.glitch.enabled = True # Whether the Xilinx MMCMs used to generate glitches are powered on or not. ULTRA MEGA POWER HUNGRY
scope.glitch.clk_src = "pll" # The clock signal that the glitch DCM is using as input.; 'pll' == Husky's onboard PLL clock
scope.io.glitch_hp = True # Whether or not to use the high power MOSFET to short the power rail to ground when the glitch module's output is active
scope.io.glitch_lp = False # Whether or not to use the low power MOSFET to short the power rail to ground when the glitch module's output is active
scope.clock.pll.update_fpga_vco(600e6) # Change the number of phase shift steps per target clock period
scope.glitch.output = "glitch_only" # The type of output produced by the glitch module. "glitch_only" == Output only the glitch pulses - do not use the clock.
scope.glitch.trigger_src = "ext_single" # The trigger signal for the glitch pulses. “ext_single” == Use the trigger module. Once the scope is armed, one set of glitch events is emitted when the trigger condition is satisfied. Subsequent trigger conditions are ignored unless the scope is re-armed.

This code is called after programming the FPGA with ss2_aes_wrapper.bit, and is mixed in throughout with the following:

# Testing SS2 after setting up target clock
import time
time.sleep(0.1)
target._ss2_test_echo()

# Ensure ADC clock is locked
for i in range(5):
    scope.clock.reset_adc()
    time.sleep(1)
    if scope.clock.adc_locked:
        break 
assert (scope.clock.adc_locked), "ADC failed to lock"

# Disable ADC gain errors
scope.adc.lo_gain_errors_disabled = True # Disable error notification for captures that use less than a quarter of the ADC's dynamic range
scope.adc.clip_errors_disabled = True # Disable error notification for ADC clipping

# Ignore warnings
import warnings
warnings.filterwarnings('ignore')

# Disable logging
cw.set_all_log_levels(cw.logging.CRITICAL)

Glitch Loop

As far as the glitch loop goes, I am using a modified version of the glitch loop found in chipwhisperer-jupyter/courses/fault101/SOLN_Fault 2_1 - Introduction to Voltage Glitching.ipynb at master · newaetech/chipwhisperer-jupyter · GitHub. The edits are based on page 20 of NAE0010_Whitepaper_CW305_AES_SCA_Attack.pdf (newae.com). Below is the main body of the glitch loop I am using. Prior to this, I am setting up a key/text pair to be written to the FPGA. After this, I am comparing the “glitched” output to what I know is the correct, encrypted output. I have two different text values that I write to the FPGA that are alternated on each glitch attempt.

target.fpga_write(target.REG_CRYPT_KEY, key)

for glitch_setting in gc.glitch_values():
    scope.glitch.offset = glitch_setting[1]
    scope.glitch.width = glitch_setting[0]
    scope.glitch.ext_offset = glitch_setting[2]
    
    if glitch_setting[3] == 1:
        total_successes += successes
        if (successes > 0):
            print("successes = {}, resets = {}, offset = {}, width = {}, ext_offset = {}".format(successes, resets, scope.glitch.offset, scope.glitch.width, scope.glitch.ext_offset))
            total_successes += successes
        successes = 0
        resets = 0
        if total_successes > MAX_SUCCESSES:
            break
            
    reboot_flush()
    target.flush()
    
    if scope.adc.state:
        # can detect crash here (fast) before timing out (slow)
        print("Trigger still high!")
        gc.add("reset")

        #Device is slow to boot?
        reboot_flush()
        resets += 1

    # Do glitch loop
    if (turn == 0):
        text = [0, 0x1f, 3, 4, 3, 1, 9, 12, 7, 7, 7, 6, 0x99, 0x1e, 0, 0]
    else:
        text = [0x9b, 1, 1, 1, 0x44, 1, 1, 1, 0x33, 1, 0x45, 1, 0x20, 1, 1, 1]
    
    # Arm scope
    scope.arm()

    # Write plaintext
    target.fpga_write(target.REG_CRYPT_TEXTIN, text)

    # Manually trigger tio4
    scope.io.tio4 = 'gpio_low'
    scope.io.tio4 = 'gpio_high'
    scope.io.tio4 = 'gpio_low'

    target.fpga_write(target.REG_CRYPT_GO, [0x01])

    output = bytearray(target.fpga_read(target.REG_CRYPT_CIPHEROUT, 16))
    
    # Capture the trace
    ret = scope.capture()
    
    # organize and store the data
    wave = scope.get_last_trace()
    trace = cw.Trace(wave, text, output, key)

    #scope.io.vglitch_reset()

Questions

  1. I have been hooking the oscilloscope up to the 3.3V Vcc header on the CW313 support board as well as trying it on IOVref on the PCIe slot (3.3V Vcc is not accessible on the PCIe slot, but IOVref is tied to Vcc so I chose to hook onto that). I got this information from chipwhisperer-target-cw308t/CW312T_XC7A35T/NAE-CW312T0XC7A35-04.PDF at main · newaetech/chipwhisperer-target-cw308t · GitHub. I have also been using an outdated NAE-CW313-02_Schematic.PDF, as it was the only one I could find. Are there differences between this one and the current version 3 of this board that are affecting the successes of my Vcc probing? Am I probing Vcc at the correct point to see a glitch that would be inserted?
  2. Could there be problems with my hardware setup? I have SMA cables connected between the CW313 and the Husky for both the crowbar and pos lines. I have JP1, JP2, and JP4 shorted on the CW313.
  3. Is there anything that you can tell is wrong with the code that I have provided? I am aware that I have a lack of fundamental conceptual knowledge of AES encryption/attacks, as well as glitching as a whole, but from what I can tell I am setting everything up correctly and I am writing/reading to/from the FPGA correctly.
  4. Is there anything that I am just completely missing that is preventing me from getting this to work?

Please let me know if you need any clarification or further explanation on anything that I have mentioned above. Any help/advice is appreciated, as I am running out of ideas of what to try to get this working. Thank you!

It could be that the glitch is being inserted, but that it’s very small. Any notable glitch should clip the power trace for quite a few cycles. It’s much better to glitch based on the effects that you’re seeing. Increase your glitch size until you start resetting the target.

No, this only affects power analysis.

You may need to switch scope.glitch.output to enable only mode, which will pull VCC low for a full clock cycle (or multiple if you increase scope.glitch.repeat). Note that offset and width have no effect in this mode.

Alex

Thanks for your response. After trying enable_only for scope.glitch.output, I see no change. I also tried increasing scope.glitch.repeat. This also had no effect.

In that case, the glitch is almost certainly not happening. Your triggering setup looks weird here. It looks like you’re trying to trigger the glitch manually (which you should do via scope.glitch.manual_trigger() instead), which won’t work for something like this. You need to use the trigger output from the FPGA to time the glitch as there’s going to be a tonne of jitter introduced by trying to trigger from Python.

Hm okay. How does one use the trigger output from the FPGA to time the glitch?

I’m a less familiar with FPGA targets, but target.fpga_write(target.REG_CRYPT_GO, [0x01]) should start the AES encryption operation, which should set the trigger high in the default AES bitstream. You should also be checking the result of scope.capture() here, as a non-zero value indicates that the trigger never happened.

You’ll also want to avoid setting scope.io.tio4. I’m not sure if that’s what you’re doing with scope_to_trigger, but in general you should avoid trying to drive the output pin of an FPGA/microcontroller via another FPGA/microcontroller.

Gotcha; that is working for my trigger. However, after a tad bit of troubleshooting, it seems that with enable_only, the glitch will either report as a success or the target.fpga_write(target.REG_CRYPT_GO, [0x01]) will return a TypeError:
'NoneType' object is not iterable. When this happens, LED2 on the CW313 illuminates red. Does this indicate that the FPGA reset/crashed? I am not yet convinced that it is truly a success when it reports as such.

When I use glitch_only with width and offset settings close to the max, I am still seeing only normal occurrences.

You’ll also want to avoid setting scope.io.tio4. I’m not sure if that’s what you’re doing with scope_to_trigger , but in general you should avoid trying to drive the output pin of an FPGA/microcontroller via another FPGA/microcontroller.

Noted. Yes, sorry, scope_to_trigger is a local variable in a function that I have that would manually toggle pins. I forgot to change that to just scope when I ripped it out to put it in this post. For anyone else that comes across this, I have fixed it in my original post.

Can you post the full stack trace? It could be that the FPGA crashed in that case, but it’s hard to tell just from the final error.

As for whether or not you’re getting a success, it’s a bit difficult to say for certain, but I think it’s likely so long as you’re getting something that seems valid (aka not all 0s or FFs) and you’re glitching during the encryption operation.

If you’re seeing successes with enable_only, glitch_only won’t be close to being strong enough to get glitches, as it’s limited to 50% of a clock cycle.

Alex

Sure, here it is:

It is worth noting that I have not changed anything in my set up, but today I am only seeing normal occurrences unless I increase scope.glitch.repeat to 8, at which point I get the error above. I am not seeing any reported successes. This somewhat makes sense to me since this is increasing the strength of the glitch and crashing (?) the FPGA, but I am not sure why it isn’t reporting any successes anymore with lower repeat values.

Just an update, I’ve realized that I was setting scope.glitch.ext_offset way too high (like at 10+), which I think was making the glitch be inserted well after the instruction for the AES encryption was done. For anyone who looks at this, I noticed the below behaviors for my settings in case it helps as a starting point:

scope.glitch.ext_offset = 0 and scope.glitch.repeat set to either 6 or 7 work best for me. scope.glitch.repeat = 5 is too low and does not result in any successes, and it being set to 8 causes the aforementioned TypeError.

With scope.glitch.ext_offset = 1, scope.glitch.repeat set to 6 or 7 gives some successes, but is not reliable and ultimately results in the TypeError. scope.glitch.repeat = 5 results in only normal occurrences, and it set to 8 also causes the TypeError.

In summary, the best settings for me are scope.glitch.output = "enable_only", scope.glitch.ext_offset = 0, and scope.glitch.repeat set to either 6 or 7.

@Alex_Dewar, I do have a few follow up/conceptual questions if you don’t mind answering them:

  1. Does this TypeError (see the previous reply for the stack trace) suggest that the FPGA is crashing because of too strong of a glitch? Or, is something else the root cause of this error?
  2. In my Jupyter notebook, I have it looping so that it captures multiple traces and tries to glitch multiple times with the same settings. Each time the loop “loops” and has a success, the glitched encryption that I read out is the same exact byte array (this repeated output is different each time I run the loop). Is it normal for multiple AES encryption glitching attempts with the same settings to result in the same byte array?
  3. Why are glitch settings so different for everyone? I would think that since every kit comes with the same cables/boards that everyone’s glitch settings would be somewhat similar. Is it due to the electrical fabrication tolerances within all of these things? Is it because there are three versions of the Husky and the CW313 support board, each having (presumably) something different with the electrical design/characteristics? I’m sure the answer isn’t very black and white, but it’d be interesting to hear what the general cause of this is.

Thank you for all of your help!

It means you’re not getting any response from the FPGA, so that probably means it’s crashed.

It’s hard to say. You may want to try rolling the steps of AES back to see if you can figure out what effect your glitch is having.

I’m not sure who else you’re comparing to here. There will be a bit of variance from board to board, but I’m not aware of a large dataset of glitching on the A35 that you can compare to.

I’m not sure who else you’re comparing to here. There will be a bit of variance from board to board, but I’m not aware of a large dataset of glitching on the A35 that you can compare to.

I thought I remember seeing in some of your documentation/demos/courses (can’t remember exactly which right now) that successful glitch settings vary widely based on your hardware setup? I could just be misremembering, though; is that the case?

This is mostly referring to things like using a different target board or a different length SMA cable, different shunt resistor, etc. If you hold most of that constant, things should be fairly consistent, though will still be differences due to things like differences in the glitch MOSFETs.

1 Like